@klarkxy/dsh-self-improvement 0.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/LICENSE +41 -0
- package/README.md +35 -0
- package/cordis.patch.yml +5 -0
- package/docs/README.zh-CN.md +23 -0
- package/lib/client.inner.cjs +897 -0
- package/lib/client.inner.cjs.map +1 -0
- package/lib/client.js +1067 -0
- package/lib/contracts.d.ts +103 -0
- package/lib/contracts.js +42 -0
- package/lib/contracts.js.map +1 -0
- package/lib/index.d.ts +118 -0
- package/lib/index.js +1207 -0
- package/lib/index.js.map +1 -0
- package/package.json +108 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,1207 @@
|
|
|
1
|
+
import { CHAT_EVENTS_SLOT, EXTRACT_PURPOSE, LESSON_INJECTION_SECTION, LESSON_SCHEMA_TAG, MAX_PROJECT_ID_CHARS, MEMORY_UNAVAILABLE_MESSAGE, SELF_IMPROVEMENT_ACTIVATE_ID, SELF_IMPROVEMENT_PLUGIN, SELF_IMPROVEMENT_RPC_CHANNEL, SELF_IMPROVEMENT_SOURCE_KIND, evidenceWatermark, fail, projectIdFromCwd, sessionCwd } from "./contracts.js";
|
|
2
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
3
|
+
import { registerHostRpc } from "@klarkxy/dsh-ai-services/host-rpc";
|
|
4
|
+
import { defineDomain, domainTable } from "@deepseek-ai/dsh-storage-domain";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/detect.ts
|
|
7
|
+
const HUMAN_CORRECTION = /^(?:不对(?:[,,::\s]|$)|不是这样|你搞错了|纠正[::]|更正[::]|不要再|以后不要|我说的是|应该改成|that's wrong|that is wrong|you got it wrong|correction:|don't do that again|do not do that again|never do that again|i meant\b|i said\b)/i;
|
|
8
|
+
function asRecord(value) {
|
|
9
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
10
|
+
}
|
|
11
|
+
function excerptOf(text) {
|
|
12
|
+
const trimmed = text.replace(/\s+/g, " ").trim();
|
|
13
|
+
return trimmed.length <= 240 ? trimmed : `${trimmed.slice(0, 237)}...`;
|
|
14
|
+
}
|
|
15
|
+
function textFromContent(content) {
|
|
16
|
+
if (!Array.isArray(content)) return typeof content === "string" ? content : "";
|
|
17
|
+
const parts = [];
|
|
18
|
+
for (const block of content) {
|
|
19
|
+
const row = asRecord(block);
|
|
20
|
+
if (!row) continue;
|
|
21
|
+
if (row.type === "text" && typeof row.text === "string") parts.push(row.text);
|
|
22
|
+
else if (row.type === "tool-result" && Array.isArray(row.content)) parts.push(textFromContent(row.content));
|
|
23
|
+
}
|
|
24
|
+
return parts.join("\n");
|
|
25
|
+
}
|
|
26
|
+
function sourceKind(data) {
|
|
27
|
+
const source = asRecord(asRecord(data)?.source);
|
|
28
|
+
return typeof source?.kind === "string" ? source.kind : void 0;
|
|
29
|
+
}
|
|
30
|
+
function isHumanUserMessage(event) {
|
|
31
|
+
return event.type === "user/message" && sourceKind(event.data) === "user";
|
|
32
|
+
}
|
|
33
|
+
function lastHumanRequestText(events) {
|
|
34
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
35
|
+
const event = events[index];
|
|
36
|
+
if (!event || !isHumanUserMessage(event)) continue;
|
|
37
|
+
const text = textFromContent(asRecord(event.data)?.content).trim();
|
|
38
|
+
if (text) return text;
|
|
39
|
+
}
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
function requestTextFromMessages(messages) {
|
|
43
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
44
|
+
const row = asRecord(messages[index]);
|
|
45
|
+
if (!row) continue;
|
|
46
|
+
if (asRecord(row.source)?.kind !== "user") continue;
|
|
47
|
+
const text = textFromContent(row.content).trim();
|
|
48
|
+
if (text) return text;
|
|
49
|
+
}
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
function toolCallName(events, callId) {
|
|
53
|
+
for (const event of events) {
|
|
54
|
+
if (event.type !== "tool/call") continue;
|
|
55
|
+
const row = asRecord(event.data);
|
|
56
|
+
if (row && String(row.callId) === callId && typeof row.name === "string") return row.name;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function toolResultInfo(event) {
|
|
60
|
+
if (event.type !== "tool/result") return void 0;
|
|
61
|
+
const row = asRecord(event.data);
|
|
62
|
+
const message = asRecord(row?.message);
|
|
63
|
+
const content = Array.isArray(message?.content) ? message.content : [];
|
|
64
|
+
const block = asRecord(content[0]);
|
|
65
|
+
const callId = String(block?.toolCallId ?? asRecord(message?.source)?.callId ?? "");
|
|
66
|
+
if (!callId) return void 0;
|
|
67
|
+
return {
|
|
68
|
+
callId,
|
|
69
|
+
isError: block?.isError === true || Boolean(row?.error),
|
|
70
|
+
text: textFromContent(content)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function detectLessonTriggers(events, sessionId, afterSeq = -1) {
|
|
74
|
+
const triggers = [];
|
|
75
|
+
const seen = /* @__PURE__ */ new Set();
|
|
76
|
+
const range = events.filter((event) => event.seq > afterSeq).sort((a, b) => a.seq - b.seq);
|
|
77
|
+
const priorActivity = events.some((event) => event.seq <= afterSeq && (event.type === "assistant/message" || event.type === "tool/result" || event.type === "tool/call")) || range.some((event) => event.type === "assistant/message" || event.type === "tool/call" || event.type === "tool/result");
|
|
78
|
+
for (const event of range) {
|
|
79
|
+
if (!isHumanUserMessage(event) || !priorActivity) continue;
|
|
80
|
+
const text = textFromContent(asRecord(event.data)?.content).trim();
|
|
81
|
+
if (!text || !HUMAN_CORRECTION.test(text)) continue;
|
|
82
|
+
const evidence = [{
|
|
83
|
+
sessionId,
|
|
84
|
+
seq: event.seq,
|
|
85
|
+
kind: "user",
|
|
86
|
+
excerpt: excerptOf(text)
|
|
87
|
+
}];
|
|
88
|
+
const key = evidence.map((item) => `${item.seq}:${item.kind}`).join("|");
|
|
89
|
+
if (seen.has(key)) continue;
|
|
90
|
+
seen.add(key);
|
|
91
|
+
triggers.push({
|
|
92
|
+
kind: "human-correction",
|
|
93
|
+
evidence,
|
|
94
|
+
titleHint: excerptOf(text.split(/[。.!?\n]/, 1)[0] ?? text),
|
|
95
|
+
contentHint: text
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const results = range.flatMap((event) => {
|
|
99
|
+
const info = toolResultInfo(event);
|
|
100
|
+
return info ? [{
|
|
101
|
+
event,
|
|
102
|
+
...info,
|
|
103
|
+
name: toolCallName(events, info.callId)
|
|
104
|
+
}] : [];
|
|
105
|
+
});
|
|
106
|
+
const pendingFailure = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const row of results) {
|
|
108
|
+
const name = row.name ?? row.callId;
|
|
109
|
+
if (row.isError) {
|
|
110
|
+
if (!pendingFailure.has(name)) pendingFailure.set(name, {
|
|
111
|
+
seq: row.event.seq,
|
|
112
|
+
text: row.text,
|
|
113
|
+
name
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const failure = pendingFailure.get(name);
|
|
118
|
+
if (!failure) continue;
|
|
119
|
+
pendingFailure.delete(name);
|
|
120
|
+
const evidence = [{
|
|
121
|
+
sessionId,
|
|
122
|
+
seq: failure.seq,
|
|
123
|
+
kind: "tool",
|
|
124
|
+
excerpt: excerptOf(failure.text || `${name} failed`)
|
|
125
|
+
}, {
|
|
126
|
+
sessionId,
|
|
127
|
+
seq: row.event.seq,
|
|
128
|
+
kind: "tool",
|
|
129
|
+
excerpt: excerptOf(row.text || `${name} succeeded`)
|
|
130
|
+
}];
|
|
131
|
+
const key = evidence.map((item) => `${item.seq}:${item.kind}`).join("|");
|
|
132
|
+
if (seen.has(key)) continue;
|
|
133
|
+
seen.add(key);
|
|
134
|
+
triggers.push({
|
|
135
|
+
kind: "verified-tool-fix",
|
|
136
|
+
evidence,
|
|
137
|
+
toolName: name,
|
|
138
|
+
titleHint: `工具 ${name} 失败后已核实修复`,
|
|
139
|
+
contentHint: `工具 ${name} 曾失败,随后同名调用返回非错误结果。失败摘录:${evidence[0]?.excerpt ?? ""}。成功摘录:${evidence[1]?.excerpt ?? ""}。`
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
return triggers;
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/extract.ts
|
|
146
|
+
/** Matches Memory `newMemoryRecordSchema` field caps so create does not fail as 记忆条目格式无效. */
|
|
147
|
+
const TITLE_MAX = 160;
|
|
148
|
+
const CONTENT_MAX = 4e3;
|
|
149
|
+
const TAG_MAX = 40;
|
|
150
|
+
const EXCEPTION_MAX = 200;
|
|
151
|
+
const EXCERPT_MAX = 400;
|
|
152
|
+
const LIST_MAX = 16;
|
|
153
|
+
const SKIP = { skip: true };
|
|
154
|
+
function clip(value, max) {
|
|
155
|
+
return value.trim().slice(0, max);
|
|
156
|
+
}
|
|
157
|
+
function schemaTags() {
|
|
158
|
+
return [LESSON_SCHEMA_TAG].filter((tag) => tag.length > 0 && tag.length <= TAG_MAX).slice(0, LIST_MAX);
|
|
159
|
+
}
|
|
160
|
+
function schemaEvidence(refs) {
|
|
161
|
+
return refs.slice(0, LIST_MAX).map((ref) => {
|
|
162
|
+
const excerpt = ref.excerpt ? clip(ref.excerpt, EXCERPT_MAX) : void 0;
|
|
163
|
+
return excerpt ? {
|
|
164
|
+
sessionId: ref.sessionId,
|
|
165
|
+
seq: ref.seq,
|
|
166
|
+
kind: ref.kind,
|
|
167
|
+
excerpt
|
|
168
|
+
} : {
|
|
169
|
+
sessionId: ref.sessionId,
|
|
170
|
+
seq: ref.seq,
|
|
171
|
+
kind: ref.kind
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function parseExtraction(text) {
|
|
176
|
+
const trimmed = text.trim();
|
|
177
|
+
if (!trimmed) return void 0;
|
|
178
|
+
const body = (trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1] ?? trimmed).trim();
|
|
179
|
+
const start = body.indexOf("{");
|
|
180
|
+
const end = body.lastIndexOf("}");
|
|
181
|
+
if (start < 0 || end <= start) return void 0;
|
|
182
|
+
let parsed;
|
|
183
|
+
try {
|
|
184
|
+
parsed = JSON.parse(body.slice(start, end + 1));
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
189
|
+
const row = parsed;
|
|
190
|
+
if (row.skip === true) return SKIP;
|
|
191
|
+
if (typeof row.title !== "string" || typeof row.content !== "string") return void 0;
|
|
192
|
+
const title = clip(row.title, TITLE_MAX);
|
|
193
|
+
const content = clip(row.content, CONTENT_MAX);
|
|
194
|
+
if (!title || !content) return void 0;
|
|
195
|
+
return {
|
|
196
|
+
title,
|
|
197
|
+
content,
|
|
198
|
+
exceptions: Array.isArray(row.exceptions) ? row.exceptions.filter((item) => typeof item === "string").map((item) => clip(item, EXCEPTION_MAX)).filter(Boolean).slice(0, LIST_MAX) : []
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function draftFromTrigger(trigger) {
|
|
202
|
+
return {
|
|
203
|
+
title: clip(trigger.titleHint, TITLE_MAX) || "教训",
|
|
204
|
+
content: clip(trigger.contentHint, CONTENT_MAX),
|
|
205
|
+
exceptions: []
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const EXTRACT_SYSTEM = [
|
|
209
|
+
"Extract at most one durable lesson from the supplied original-session evidence.",
|
|
210
|
+
"Use only explicit human corrections or a tool failure later verified by a non-error result for the same tool.",
|
|
211
|
+
"Do not infer success from later silence or from the assistant claiming it learned or fixed anything.",
|
|
212
|
+
"If evidence is insufficient, reply with JSON {\"skip\":true}.",
|
|
213
|
+
"Otherwise reply with JSON {\"title\":string,\"content\":string,\"exceptions\":string[]} only."
|
|
214
|
+
].join(" ");
|
|
215
|
+
function candidateRecord(draft, trigger, projectId) {
|
|
216
|
+
return {
|
|
217
|
+
scope: {
|
|
218
|
+
kind: "project",
|
|
219
|
+
projectId
|
|
220
|
+
},
|
|
221
|
+
kind: "lesson",
|
|
222
|
+
status: "candidate",
|
|
223
|
+
title: clip(draft.title, TITLE_MAX) || "教训",
|
|
224
|
+
content: clip(draft.content, CONTENT_MAX),
|
|
225
|
+
tags: schemaTags(),
|
|
226
|
+
evidence: schemaEvidence(trigger.evidence),
|
|
227
|
+
exceptions: draft.exceptions.map((item) => clip(item, EXCEPTION_MAX)).filter(Boolean).slice(0, LIST_MAX),
|
|
228
|
+
source: "self-improvement"
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function hasSameEvidence(existing, trigger) {
|
|
232
|
+
const key = evidenceWatermark(trigger.evidence);
|
|
233
|
+
return existing.some((record) => record.kind === "lesson" && record.source === "self-improvement" && (evidenceWatermark(record.evidence) === key || record.tags.includes(`evidence:${key}`)));
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/recall.ts
|
|
237
|
+
function estimateTokens(text) {
|
|
238
|
+
let tokens = 0;
|
|
239
|
+
for (const char of text) tokens += char.charCodeAt(0) > 127 ? 1 : .25;
|
|
240
|
+
return Math.max(1, Math.ceil(tokens));
|
|
241
|
+
}
|
|
242
|
+
function isExpired(record, now) {
|
|
243
|
+
return typeof record.expiresAt === "number" && record.expiresAt <= now;
|
|
244
|
+
}
|
|
245
|
+
function inProjectScope(scope, projectId) {
|
|
246
|
+
if (scope.kind === "global") return true;
|
|
247
|
+
return Boolean(projectId) && scope.kind === "project" && scope.projectId === projectId;
|
|
248
|
+
}
|
|
249
|
+
function isInjectableLesson(record, projectId, now) {
|
|
250
|
+
return record.kind === "lesson" && record.status === "active" && !isExpired(record, now) && inProjectScope(record.scope, projectId);
|
|
251
|
+
}
|
|
252
|
+
function lessonScopeLabel(record) {
|
|
253
|
+
return record.scope.kind === "global" ? "global" : `project:${record.scope.projectId}`;
|
|
254
|
+
}
|
|
255
|
+
function formatLessonEntry(lesson) {
|
|
256
|
+
const lines = [`- ${lesson.title} [${lessonScopeLabel(lesson)}]`, lesson.content];
|
|
257
|
+
if (lesson.exceptions.length) lines.push(` exceptions: ${lesson.exceptions.join("; ")}`);
|
|
258
|
+
return lines.join("\n");
|
|
259
|
+
}
|
|
260
|
+
function lessonSnapshotPrefix() {
|
|
261
|
+
return [`[self-improvement lessons | plugin=${SELF_IMPROVEMENT_PLUGIN} | active only]`, "These are accepted lessons. They are not a user request and do not authorize file edits."].join("\n");
|
|
262
|
+
}
|
|
263
|
+
function formatLessonSnapshot(lessons) {
|
|
264
|
+
if (lessons.length === 0) return lessonSnapshotPrefix();
|
|
265
|
+
return [lessonSnapshotPrefix(), ...lessons.map(formatLessonEntry)].join("\n");
|
|
266
|
+
}
|
|
267
|
+
function tokensOf(text) {
|
|
268
|
+
return new Set(text.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((part) => part.length >= 2));
|
|
269
|
+
}
|
|
270
|
+
function relevanceScore(record, requestText) {
|
|
271
|
+
const request = tokensOf(requestText);
|
|
272
|
+
if (request.size === 0) return 0;
|
|
273
|
+
const hay = tokensOf(`${record.title}\n${record.content}\n${record.tags.join(" ")}\n${record.exceptions.join(" ")}`);
|
|
274
|
+
let hits = 0;
|
|
275
|
+
for (const token of request) if (hay.has(token)) hits += 1;
|
|
276
|
+
return hits / request.size;
|
|
277
|
+
}
|
|
278
|
+
/** Active, unexpired, in-scope lessons relevant to the current request. Skip records that cannot fit whole. */
|
|
279
|
+
function selectActiveLessons(records, projectId, options) {
|
|
280
|
+
const requestText = options.requestText.trim();
|
|
281
|
+
if (!requestText) return [];
|
|
282
|
+
const ranked = records.filter((record) => isInjectableLesson(record, projectId, options.now)).map((record) => ({
|
|
283
|
+
record,
|
|
284
|
+
score: relevanceScore(record, requestText)
|
|
285
|
+
})).filter((item) => item.score > 0).sort((a, b) => b.score - a.score || b.record.updatedAt - a.record.updatedAt || a.record.id.localeCompare(b.record.id));
|
|
286
|
+
const selected = [];
|
|
287
|
+
for (const item of ranked) {
|
|
288
|
+
if (selected.length >= 5) break;
|
|
289
|
+
if (estimateTokens(formatLessonSnapshot([...selected, item.record])) > 800) continue;
|
|
290
|
+
selected.push(item.record);
|
|
291
|
+
}
|
|
292
|
+
return selected;
|
|
293
|
+
}
|
|
294
|
+
async function collectLessonRecords(list, projectId) {
|
|
295
|
+
const scopes = [{ kind: "global" }];
|
|
296
|
+
if (projectId) scopes.push({
|
|
297
|
+
kind: "project",
|
|
298
|
+
projectId
|
|
299
|
+
});
|
|
300
|
+
const rows = await Promise.all(scopes.map((scope) => list(scope)));
|
|
301
|
+
const byId = /* @__PURE__ */ new Map();
|
|
302
|
+
for (const record of rows.flat()) byId.set(record.id, record);
|
|
303
|
+
return [...byId.values()];
|
|
304
|
+
}
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/inject.ts
|
|
307
|
+
function isSelfImprovementLessonMessage(message) {
|
|
308
|
+
if (!message || typeof message !== "object") return false;
|
|
309
|
+
const row = message;
|
|
310
|
+
if (row.source?.kind !== "plugin:@klarkxy/dsh-self-improvement" || row.source.plugin !== "@klarkxy/dsh-self-improvement") return false;
|
|
311
|
+
return row.source.form === "snapshot" && Boolean(row.source.sections?.some((section) => section.name === "dsh-self-improvement:lessons"));
|
|
312
|
+
}
|
|
313
|
+
function lessonInjectPayload(lessons) {
|
|
314
|
+
const text = formatLessonSnapshot(lessons);
|
|
315
|
+
return {
|
|
316
|
+
source: {
|
|
317
|
+
kind: SELF_IMPROVEMENT_SOURCE_KIND,
|
|
318
|
+
plugin: SELF_IMPROVEMENT_PLUGIN,
|
|
319
|
+
form: "snapshot",
|
|
320
|
+
sections: [{
|
|
321
|
+
name: LESSON_INJECTION_SECTION,
|
|
322
|
+
text
|
|
323
|
+
}]
|
|
324
|
+
},
|
|
325
|
+
content: [{
|
|
326
|
+
type: "text",
|
|
327
|
+
text
|
|
328
|
+
}]
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function createLessonInjectionMessage(lessons) {
|
|
332
|
+
return createUserMessage(lessonInjectPayload(lessons));
|
|
333
|
+
}
|
|
334
|
+
function injectLessonMessages(decision, lessons) {
|
|
335
|
+
if (decision.kind !== "enter") return decision;
|
|
336
|
+
const without = decision.messages.filter((message) => !isSelfImprovementLessonMessage(message));
|
|
337
|
+
if (lessons.length === 0) return {
|
|
338
|
+
...decision,
|
|
339
|
+
messages: without
|
|
340
|
+
};
|
|
341
|
+
return {
|
|
342
|
+
...decision,
|
|
343
|
+
messages: [createLessonInjectionMessage(lessons), ...without]
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/skills.ts
|
|
348
|
+
function yamlScalar(value) {
|
|
349
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
350
|
+
return JSON.stringify(value);
|
|
351
|
+
}
|
|
352
|
+
function skillDownloadName(id, title) {
|
|
353
|
+
return `self-improvement-${title.replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "skill"}-${id.replace(/[^a-zA-Z0-9_-]/g, "").slice(0, 12) || "id"}.md`;
|
|
354
|
+
}
|
|
355
|
+
function skillNameFromTitle(title) {
|
|
356
|
+
return title.replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-+|-+$/g, "").slice(0, 64).toLowerCase() || "self-improvement-lesson";
|
|
357
|
+
}
|
|
358
|
+
function skillDescriptionFromLessons(lessons) {
|
|
359
|
+
const first = lessons[0];
|
|
360
|
+
return (first ? `${first.title}: ${first.content}` : "Accepted self-improvement lessons.").replace(/[\r\n]+/g, " ").trim().slice(0, 200) || "Accepted self-improvement lessons.";
|
|
361
|
+
}
|
|
362
|
+
function skillSourcesFromLessons(lessons) {
|
|
363
|
+
return lessons.map((lesson) => ({
|
|
364
|
+
id: lesson.id,
|
|
365
|
+
revision: lesson.revision,
|
|
366
|
+
status: lesson.status,
|
|
367
|
+
scope: lesson.scope,
|
|
368
|
+
expiresAt: lesson.expiresAt
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
function sourcesMatchLive(sources, lessons, now) {
|
|
372
|
+
if (sources.length === 0) return false;
|
|
373
|
+
const byId = new Map(lessons.map((lesson) => [lesson.id, lesson]));
|
|
374
|
+
for (const source of sources) {
|
|
375
|
+
const live = byId.get(source.id);
|
|
376
|
+
if (!live || live.kind !== "lesson") return false;
|
|
377
|
+
if (live.revision !== source.revision) return false;
|
|
378
|
+
if (live.status !== "active" || source.status !== "active") return false;
|
|
379
|
+
if (isExpired(live, now)) return false;
|
|
380
|
+
if (live.scope.kind !== source.scope.kind) return false;
|
|
381
|
+
if (live.scope.kind === "project" && source.scope.kind === "project" && live.scope.projectId !== source.scope.projectId) return false;
|
|
382
|
+
}
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
function provenanceYaml(lessons) {
|
|
386
|
+
const lines = ["provenance:"];
|
|
387
|
+
for (const lesson of lessons) {
|
|
388
|
+
lines.push(` - lessonId: ${yamlScalar(lesson.id)}`);
|
|
389
|
+
lines.push(` revision: ${lesson.revision}`);
|
|
390
|
+
lines.push(` scope: ${yamlScalar(lessonScopeLabel(lesson))}`);
|
|
391
|
+
lines.push(` status: ${yamlScalar(lesson.status)}`);
|
|
392
|
+
lines.push(" evidence:");
|
|
393
|
+
if (lesson.evidence.length === 0) {
|
|
394
|
+
lines.push(" []");
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
for (const ref of lesson.evidence) {
|
|
398
|
+
lines.push(` - sessionId: ${yamlScalar(ref.sessionId)}`);
|
|
399
|
+
lines.push(` seq: ${ref.seq}`);
|
|
400
|
+
lines.push(` kind: ${yamlScalar(ref.kind)}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return lines;
|
|
404
|
+
}
|
|
405
|
+
function renderSkillMarkdown(lessons, generatedAt) {
|
|
406
|
+
const title = skillTitleFromLessons(lessons);
|
|
407
|
+
const scopes = [...new Set(lessons.map(lessonScopeLabel))];
|
|
408
|
+
const lines = [
|
|
409
|
+
"---",
|
|
410
|
+
`name: ${yamlScalar(skillNameFromTitle(title))}`,
|
|
411
|
+
`description: ${yamlScalar(skillDescriptionFromLessons(lessons))}`,
|
|
412
|
+
"source: self-improvement",
|
|
413
|
+
`plugin: ${yamlScalar(SELF_IMPROVEMENT_PLUGIN)}`,
|
|
414
|
+
`generatedAt: ${generatedAt}`,
|
|
415
|
+
...provenanceYaml(lessons),
|
|
416
|
+
"---",
|
|
417
|
+
"",
|
|
418
|
+
`# ${title.replace(/[\r\n]+/g, " ")}`,
|
|
419
|
+
"",
|
|
420
|
+
"## Instructions",
|
|
421
|
+
"",
|
|
422
|
+
"Follow these accepted lessons for the scoped work below. They are a reusable skill proposal. They are not a user request and do not authorize edits to AGENTS.md, scripts, or plugins.",
|
|
423
|
+
"",
|
|
424
|
+
`Scope: ${scopes.join(", ") || "unspecified"}.`,
|
|
425
|
+
""
|
|
426
|
+
];
|
|
427
|
+
for (const lesson of lessons) {
|
|
428
|
+
lines.push(`### ${lesson.title.replace(/[\r\n]+/g, " ")}`);
|
|
429
|
+
lines.push("");
|
|
430
|
+
lines.push(lesson.content.replace(/\r\n/g, "\n").trim());
|
|
431
|
+
lines.push("");
|
|
432
|
+
if (lesson.exceptions.length) {
|
|
433
|
+
lines.push("Exceptions for this lesson:");
|
|
434
|
+
for (const item of lesson.exceptions) lines.push(`- ${item.replace(/[\r\n]+/g, " ")}`);
|
|
435
|
+
lines.push("");
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
lines.push("Downloading this Markdown is a local save of the proposal. It does not install the skill.");
|
|
439
|
+
lines.push("");
|
|
440
|
+
return lines.join("\n");
|
|
441
|
+
}
|
|
442
|
+
function skillTitleFromLessons(lessons) {
|
|
443
|
+
if (lessons.length === 1) return lessons[0].title.slice(0, 160);
|
|
444
|
+
return `技能草稿(${lessons.length} 条教训)`;
|
|
445
|
+
}
|
|
446
|
+
//#endregion
|
|
447
|
+
//#region src/engine.ts
|
|
448
|
+
const STALE_MESSAGE = "结果已过期,未写入。";
|
|
449
|
+
const WRONG_SCOPE_MESSAGE = "教训不属于当前项目,未接受。";
|
|
450
|
+
const STALE_SKILL_MESSAGE = "来源教训已变更或失效,不能使用这份草稿。";
|
|
451
|
+
function asObject(payload) {
|
|
452
|
+
return payload && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
|
|
453
|
+
}
|
|
454
|
+
function str(body, key) {
|
|
455
|
+
return typeof body[key] === "string" ? body[key] : "";
|
|
456
|
+
}
|
|
457
|
+
function int(body, key) {
|
|
458
|
+
return typeof body[key] === "number" && Number.isInteger(body[key]) ? body[key] : void 0;
|
|
459
|
+
}
|
|
460
|
+
function sessionIdOf(session) {
|
|
461
|
+
return String(session.id ?? "");
|
|
462
|
+
}
|
|
463
|
+
var SelfImprovementEngine = class {
|
|
464
|
+
options;
|
|
465
|
+
generation = 1;
|
|
466
|
+
active = true;
|
|
467
|
+
lifetime = new AbortController();
|
|
468
|
+
storageFailed = false;
|
|
469
|
+
chain = Promise.resolve();
|
|
470
|
+
extractJobs = /* @__PURE__ */ new Map();
|
|
471
|
+
aiScope;
|
|
472
|
+
seenProjects = /* @__PURE__ */ new Set();
|
|
473
|
+
now;
|
|
474
|
+
constructor(options) {
|
|
475
|
+
this.options = options;
|
|
476
|
+
this.now = options.now ?? Date.now;
|
|
477
|
+
}
|
|
478
|
+
getGeneration() {
|
|
479
|
+
return this.generation;
|
|
480
|
+
}
|
|
481
|
+
isActive() {
|
|
482
|
+
return this.active;
|
|
483
|
+
}
|
|
484
|
+
dispose() {
|
|
485
|
+
this.active = false;
|
|
486
|
+
this.generation += 1;
|
|
487
|
+
this.lifetime.abort();
|
|
488
|
+
this.aiScope?.dispose();
|
|
489
|
+
this.aiScope = void 0;
|
|
490
|
+
}
|
|
491
|
+
memoryOrError() {
|
|
492
|
+
const memory = this.options.memory();
|
|
493
|
+
if (!memory) return fail("MEMORY_UNAVAILABLE", MEMORY_UNAVAILABLE_MESSAGE);
|
|
494
|
+
return memory;
|
|
495
|
+
}
|
|
496
|
+
asMemory(value) {
|
|
497
|
+
return typeof value.list === "function";
|
|
498
|
+
}
|
|
499
|
+
requireActive() {
|
|
500
|
+
if (this.active) return void 0;
|
|
501
|
+
return fail("DISABLED", "自我改进已关闭,摘录和教训注入已停止。");
|
|
502
|
+
}
|
|
503
|
+
async persist(work) {
|
|
504
|
+
try {
|
|
505
|
+
const value = await work();
|
|
506
|
+
this.storageFailed = false;
|
|
507
|
+
return value;
|
|
508
|
+
} catch (error) {
|
|
509
|
+
this.storageFailed = true;
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
currentSignal(signal) {
|
|
514
|
+
return signal ? AbortSignal.any([signal, this.lifetime.signal]) : this.lifetime.signal;
|
|
515
|
+
}
|
|
516
|
+
rememberProject(projectId) {
|
|
517
|
+
this.seenProjects.add(projectId);
|
|
518
|
+
}
|
|
519
|
+
knownProjectIds(extra) {
|
|
520
|
+
const ids = new Set(this.seenProjects);
|
|
521
|
+
if (extra) ids.add(extra);
|
|
522
|
+
for (const [, row] of this.options.watermarks.entries()) if (row.projectId) ids.add(row.projectId);
|
|
523
|
+
return [...ids];
|
|
524
|
+
}
|
|
525
|
+
sameMemory(expected) {
|
|
526
|
+
return this.options.memory() === expected;
|
|
527
|
+
}
|
|
528
|
+
beginWork() {
|
|
529
|
+
const disabled = this.requireActive();
|
|
530
|
+
if (disabled) return disabled;
|
|
531
|
+
const memory = this.memoryOrError();
|
|
532
|
+
if (!this.asMemory(memory)) return memory;
|
|
533
|
+
return {
|
|
534
|
+
generation: this.generation,
|
|
535
|
+
memory
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
isWork(value) {
|
|
539
|
+
return "memory" in value;
|
|
540
|
+
}
|
|
541
|
+
/** Linearize disable/replace against captured generation + Memory. Never start a new write after this fails. */
|
|
542
|
+
revalidate(generation, memory) {
|
|
543
|
+
if (this.active && this.generation === generation && this.sameMemory(memory)) return void 0;
|
|
544
|
+
if (!this.active) return this.requireActive();
|
|
545
|
+
if (!this.sameMemory(memory)) return fail("MEMORY_UNAVAILABLE", MEMORY_UNAVAILABLE_MESSAGE);
|
|
546
|
+
return fail("SUPERSEDED", STALE_MESSAGE);
|
|
547
|
+
}
|
|
548
|
+
failed(value) {
|
|
549
|
+
return typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
550
|
+
}
|
|
551
|
+
mutationGuards(generation, memory) {
|
|
552
|
+
return {
|
|
553
|
+
signal: this.lifetime.signal,
|
|
554
|
+
isCurrent: () => !this.revalidate(generation, memory)
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
isGuardRejection(error) {
|
|
558
|
+
if (!error || typeof error !== "object") return false;
|
|
559
|
+
const name = "name" in error ? String(error.name) : "";
|
|
560
|
+
const code = "code" in error ? String(error.code) : "";
|
|
561
|
+
return name === "AbortError" || name === "TimeoutError" || code === "ABORT_ERR" || code === "ABORTED" || code === "MEMORY_CANCELLED" || code === "MEMORY_DISABLED" || code === "CANCELLED";
|
|
562
|
+
}
|
|
563
|
+
/** Invoke a Memory/Skill write only while still current. Queued Memory work receives captured generation/identity + lifetime abort. An already-started write is not rolled back; the RPC still fails if we are no longer current after it. */
|
|
564
|
+
async mutateWhileCurrent(generation, memory, run) {
|
|
565
|
+
const before = this.revalidate(generation, memory);
|
|
566
|
+
if (before) return before;
|
|
567
|
+
try {
|
|
568
|
+
const value = await run(this.mutationGuards(generation, memory));
|
|
569
|
+
const after = this.revalidate(generation, memory);
|
|
570
|
+
if (after) return after;
|
|
571
|
+
return value;
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return this.rejectionOrThrow(error, generation, memory);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
rejectionOrThrow(error, generation, memory) {
|
|
577
|
+
const stale = this.revalidate(generation, memory);
|
|
578
|
+
if (stale) return stale;
|
|
579
|
+
if (this.isGuardRejection(error) || this.lifetime.signal.aborted) return fail("CANCELLED", "操作已取消。");
|
|
580
|
+
throw error;
|
|
581
|
+
}
|
|
582
|
+
async listAllLessons(projectId, expectedMemory) {
|
|
583
|
+
const memory = expectedMemory ?? this.options.memory();
|
|
584
|
+
if (!memory) return [];
|
|
585
|
+
const scopes = [{ kind: "global" }, ...this.knownProjectIds(projectId).map((id) => ({
|
|
586
|
+
kind: "project",
|
|
587
|
+
projectId: id
|
|
588
|
+
}))];
|
|
589
|
+
const rows = await Promise.all(scopes.map((scope) => memory.list({
|
|
590
|
+
scope,
|
|
591
|
+
kinds: ["lesson"]
|
|
592
|
+
})));
|
|
593
|
+
if (!this.sameMemory(memory)) return [];
|
|
594
|
+
const byId = /* @__PURE__ */ new Map();
|
|
595
|
+
for (const record of rows.flat()) if (record.kind === "lesson") byId.set(record.id, record);
|
|
596
|
+
return [...byId.values()];
|
|
597
|
+
}
|
|
598
|
+
currentRequestText(session, decision) {
|
|
599
|
+
if (decision?.kind === "enter") {
|
|
600
|
+
const fromDecision = requestTextFromMessages(decision.messages);
|
|
601
|
+
if (fromDecision) return fromDecision;
|
|
602
|
+
}
|
|
603
|
+
return lastHumanRequestText(session.snapshotEvents());
|
|
604
|
+
}
|
|
605
|
+
async recordsForInjection(projectId, requestText, expectedMemory) {
|
|
606
|
+
const generation = this.generation;
|
|
607
|
+
const memory = expectedMemory ?? this.options.memory();
|
|
608
|
+
if (!memory || !this.active) return [];
|
|
609
|
+
const listed = await collectLessonRecords((scope) => memory.list({
|
|
610
|
+
scope,
|
|
611
|
+
kinds: ["lesson"]
|
|
612
|
+
}), projectId);
|
|
613
|
+
if (!this.active || this.generation !== generation || !this.sameMemory(memory)) return [];
|
|
614
|
+
return selectActiveLessons(listed, projectId, {
|
|
615
|
+
now: this.now(),
|
|
616
|
+
requestText
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
async applyPreStep(session, decision, signal) {
|
|
620
|
+
signal.throwIfAborted();
|
|
621
|
+
if (!this.active || decision.kind !== "enter") return decision;
|
|
622
|
+
const generation = this.generation;
|
|
623
|
+
const memory = this.options.memory();
|
|
624
|
+
if (!memory) return injectLessonMessages(decision, []);
|
|
625
|
+
const projectId = projectIdFromCwd(sessionCwd(session));
|
|
626
|
+
const requestText = this.currentRequestText(session, decision);
|
|
627
|
+
const lessons = await this.recordsForInjection(projectId, requestText, memory);
|
|
628
|
+
if (!this.active || this.generation !== generation || !this.sameMemory(memory)) return injectLessonMessages(decision, []);
|
|
629
|
+
return injectLessonMessages(decision, lessons);
|
|
630
|
+
}
|
|
631
|
+
async writeWatermark(sessionId, seq, projectId) {
|
|
632
|
+
await this.persist(() => this.options.watermarks.put(sessionId, {
|
|
633
|
+
sessionId,
|
|
634
|
+
seq,
|
|
635
|
+
projectId,
|
|
636
|
+
updatedAt: this.now()
|
|
637
|
+
}));
|
|
638
|
+
}
|
|
639
|
+
ensureAi() {
|
|
640
|
+
if (this.aiScope?.active) return this.aiScope;
|
|
641
|
+
const ai = this.options.ai();
|
|
642
|
+
if (!ai) return void 0;
|
|
643
|
+
this.aiScope = ai.activate(SELF_IMPROVEMENT_ACTIVATE_ID);
|
|
644
|
+
this.aiScope.registerPurpose({
|
|
645
|
+
id: EXTRACT_PURPOSE,
|
|
646
|
+
label: "自我改进摘录",
|
|
647
|
+
defaultTarget: {
|
|
648
|
+
kind: "role",
|
|
649
|
+
role: "normal"
|
|
650
|
+
},
|
|
651
|
+
maxOutputTokens: 400,
|
|
652
|
+
maxInputChars: 8e3
|
|
653
|
+
});
|
|
654
|
+
return this.aiScope;
|
|
655
|
+
}
|
|
656
|
+
async draftTrigger(trigger, sessionId, sourceVersion, signal, generation) {
|
|
657
|
+
const scope = this.ensureAi();
|
|
658
|
+
if (!scope) return draftFromTrigger(trigger);
|
|
659
|
+
const result = await scope.run({
|
|
660
|
+
purpose: EXTRACT_PURPOSE,
|
|
661
|
+
sessionId,
|
|
662
|
+
sourceVersion,
|
|
663
|
+
system: EXTRACT_SYSTEM,
|
|
664
|
+
input: JSON.stringify({
|
|
665
|
+
kind: trigger.kind,
|
|
666
|
+
evidence: trigger.evidence,
|
|
667
|
+
hint: trigger.contentHint
|
|
668
|
+
}),
|
|
669
|
+
signal,
|
|
670
|
+
isCurrent: () => this.active && this.generation === generation && scope.active,
|
|
671
|
+
priority: "background"
|
|
672
|
+
});
|
|
673
|
+
if (!this.active || this.generation !== generation) return void 0;
|
|
674
|
+
if (result.receipt.status !== "success") return void 0;
|
|
675
|
+
const parsed = parseExtraction(result.text);
|
|
676
|
+
if (!parsed || "skip" in parsed) return void 0;
|
|
677
|
+
return parsed;
|
|
678
|
+
}
|
|
679
|
+
noteExtractStart(sessionId) {
|
|
680
|
+
this.extractJobs.set(sessionId, (this.extractJobs.get(sessionId) ?? 0) + 1);
|
|
681
|
+
}
|
|
682
|
+
noteExtractEnd(sessionId) {
|
|
683
|
+
const next = (this.extractJobs.get(sessionId) ?? 1) - 1;
|
|
684
|
+
if (next <= 0) this.extractJobs.delete(sessionId);
|
|
685
|
+
else this.extractJobs.set(sessionId, next);
|
|
686
|
+
}
|
|
687
|
+
isExtracting(sessionId) {
|
|
688
|
+
if (sessionId) return (this.extractJobs.get(sessionId) ?? 0) > 0;
|
|
689
|
+
for (const count of this.extractJobs.values()) if (count > 0) return true;
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
enqueue(run) {
|
|
693
|
+
const task = this.chain.then(run, run);
|
|
694
|
+
this.chain = task.then(() => {}, () => {});
|
|
695
|
+
return task;
|
|
696
|
+
}
|
|
697
|
+
/** Queue auto and manual extract on the existing chain. Counts rise synchronously at request entry. */
|
|
698
|
+
enqueueExtract(session, signal, mode) {
|
|
699
|
+
const sessionId = sessionIdOf(session);
|
|
700
|
+
this.noteExtractStart(sessionId);
|
|
701
|
+
return this.enqueue(async () => {
|
|
702
|
+
try {
|
|
703
|
+
return await this.runExtractFromSession(session, signal, mode);
|
|
704
|
+
} finally {
|
|
705
|
+
this.noteExtractEnd(sessionId);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
async extractFromSession(session, signal, mode = "auto") {
|
|
710
|
+
return this.enqueueExtract(session, signal, mode);
|
|
711
|
+
}
|
|
712
|
+
considerSession(session, signal) {
|
|
713
|
+
return this.enqueueExtract(session, signal, "auto").then(() => {}, () => {});
|
|
714
|
+
}
|
|
715
|
+
async runExtractFromSession(session, signal, mode) {
|
|
716
|
+
const disabled = this.requireActive();
|
|
717
|
+
if (disabled) return disabled;
|
|
718
|
+
const memory = this.memoryOrError();
|
|
719
|
+
if (!this.asMemory(memory)) return memory;
|
|
720
|
+
const sessionId = sessionIdOf(session);
|
|
721
|
+
if (!sessionId) return fail("INVALID", "缺少会话。");
|
|
722
|
+
const projectId = projectIdFromCwd(sessionCwd(session));
|
|
723
|
+
if (!projectId) return fail("WRONG_SCOPE", "无法确定当前项目,未写入候选。");
|
|
724
|
+
const generation = this.generation;
|
|
725
|
+
try {
|
|
726
|
+
const combined = this.currentSignal(signal);
|
|
727
|
+
combined.throwIfAborted();
|
|
728
|
+
const events = session.snapshotEvents();
|
|
729
|
+
const lastSeq = events.reduce((max, event) => Math.max(max, event.seq), -1);
|
|
730
|
+
const watermark = this.options.watermarks.get(sessionId)?.seq ?? -1;
|
|
731
|
+
const triggers = detectLessonTriggers(events, sessionId, mode === "manual" ? -1 : watermark);
|
|
732
|
+
if (triggers.length === 0) {
|
|
733
|
+
if (mode === "auto") {
|
|
734
|
+
const written = await this.mutateWhileCurrent(generation, memory, () => this.writeWatermark(sessionId, lastSeq, projectId));
|
|
735
|
+
if (this.failed(written)) return written;
|
|
736
|
+
}
|
|
737
|
+
const stale = this.revalidate(generation, memory);
|
|
738
|
+
if (stale) return stale;
|
|
739
|
+
return {
|
|
740
|
+
ok: true,
|
|
741
|
+
value: {
|
|
742
|
+
created: [],
|
|
743
|
+
skipped: 0
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
const existing = await this.listAllLessons(projectId, memory);
|
|
748
|
+
const staleList = this.revalidate(generation, memory);
|
|
749
|
+
if (staleList) return staleList;
|
|
750
|
+
const created = [];
|
|
751
|
+
let skipped = 0;
|
|
752
|
+
const sourceVersion = `${sessionId}:${watermark}:${lastSeq}:${generation}`;
|
|
753
|
+
for (const trigger of triggers) {
|
|
754
|
+
combined.throwIfAborted();
|
|
755
|
+
if (hasSameEvidence(existing.concat(created), trigger)) {
|
|
756
|
+
skipped += 1;
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const draft = await this.draftTrigger(trigger, sessionId, sourceVersion, combined, generation);
|
|
760
|
+
const staleDraft = this.revalidate(generation, memory);
|
|
761
|
+
if (staleDraft) return staleDraft;
|
|
762
|
+
if (!draft) {
|
|
763
|
+
skipped += 1;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const record = await this.mutateWhileCurrent(generation, memory, (guards) => memory.create(candidateRecord(draft, trigger, projectId), guards));
|
|
767
|
+
if (this.failed(record)) return record;
|
|
768
|
+
this.rememberProject(projectId);
|
|
769
|
+
const activated = await this.mutateWhileCurrent(generation, memory, (guards) => memory.update(record.id, { status: "active" }, record.revision, guards));
|
|
770
|
+
if (this.failed(activated)) {
|
|
771
|
+
skipped += 1;
|
|
772
|
+
existing.push(record);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
created.push(activated);
|
|
776
|
+
existing.push(activated);
|
|
777
|
+
}
|
|
778
|
+
const watermarkWrite = await this.mutateWhileCurrent(generation, memory, () => this.writeWatermark(sessionId, lastSeq, projectId));
|
|
779
|
+
if (this.failed(watermarkWrite)) return watermarkWrite;
|
|
780
|
+
return {
|
|
781
|
+
ok: true,
|
|
782
|
+
value: {
|
|
783
|
+
created,
|
|
784
|
+
skipped
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
} catch (error) {
|
|
788
|
+
return this.rejectionOrThrow(error, generation, memory);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async requireLesson(id, extraProjectId, memory, generation) {
|
|
792
|
+
const rows = await this.listAllLessons(extraProjectId, memory);
|
|
793
|
+
const stale = this.revalidate(generation, memory);
|
|
794
|
+
if (stale) return stale;
|
|
795
|
+
const record = rows.find((item) => item.id === id);
|
|
796
|
+
if (!record || record.kind !== "lesson") return fail("NOT_FOUND", "找不到该教训。");
|
|
797
|
+
return record;
|
|
798
|
+
}
|
|
799
|
+
async acceptLesson(id, expectedRevision, scope, currentProjectId) {
|
|
800
|
+
const work = this.beginWork();
|
|
801
|
+
if (!this.isWork(work)) return work;
|
|
802
|
+
const { generation, memory } = work;
|
|
803
|
+
const record = await this.requireLesson(id, currentProjectId, memory, generation);
|
|
804
|
+
if (!("id" in record)) return record;
|
|
805
|
+
const stale = this.revalidate(generation, memory);
|
|
806
|
+
if (stale) return stale;
|
|
807
|
+
if (record.revision !== expectedRevision) return fail("STALE", "记录已更新,请刷新后重试。");
|
|
808
|
+
if (isExpired(record, this.now())) return fail("STALE", "教训已过期。");
|
|
809
|
+
if (record.status !== "candidate" && record.status !== "active") return fail("INVALID", "只能接受候选或已生效的教训。");
|
|
810
|
+
if (scope === "project") {
|
|
811
|
+
if (record.scope.kind === "project" && currentProjectId && record.scope.projectId !== currentProjectId) return fail("WRONG_SCOPE", WRONG_SCOPE_MESSAGE);
|
|
812
|
+
const updated = await this.mutateWhileCurrent(generation, memory, (guards) => memory.update(id, { status: "active" }, expectedRevision, guards));
|
|
813
|
+
if (this.failed(updated)) return updated;
|
|
814
|
+
return {
|
|
815
|
+
ok: true,
|
|
816
|
+
value: updated
|
|
817
|
+
};
|
|
818
|
+
}
|
|
819
|
+
if (record.scope.kind === "global") {
|
|
820
|
+
const updated = await this.mutateWhileCurrent(generation, memory, (guards) => memory.update(id, { status: "active" }, expectedRevision, guards));
|
|
821
|
+
if (this.failed(updated)) return updated;
|
|
822
|
+
return {
|
|
823
|
+
ok: true,
|
|
824
|
+
value: updated
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
const promoted = await this.mutateWhileCurrent(generation, memory, (guards) => memory.promoteToGlobal(id, expectedRevision, guards));
|
|
828
|
+
if (this.failed(promoted)) return promoted;
|
|
829
|
+
return {
|
|
830
|
+
ok: true,
|
|
831
|
+
value: promoted
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
async setLessonStatus(id, expectedRevision, status, extraProjectId) {
|
|
835
|
+
const work = this.beginWork();
|
|
836
|
+
if (!this.isWork(work)) return work;
|
|
837
|
+
const { generation, memory } = work;
|
|
838
|
+
const record = await this.requireLesson(id, extraProjectId, memory, generation);
|
|
839
|
+
if (!("id" in record)) return record;
|
|
840
|
+
const stale = this.revalidate(generation, memory);
|
|
841
|
+
if (stale) return stale;
|
|
842
|
+
if (record.revision !== expectedRevision) return fail("STALE", "记录已更新,请刷新后重试。");
|
|
843
|
+
const updated = await this.mutateWhileCurrent(generation, memory, (guards) => memory.update(id, { status }, expectedRevision, guards));
|
|
844
|
+
if (this.failed(updated)) return updated;
|
|
845
|
+
return {
|
|
846
|
+
ok: true,
|
|
847
|
+
value: updated
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
skills() {
|
|
851
|
+
return [...this.options.skills.entries()].map(([, row]) => row).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
852
|
+
}
|
|
853
|
+
async liveSkillLessons(lessonIds, memory, generation) {
|
|
854
|
+
const records = await this.listAllLessons(void 0, memory);
|
|
855
|
+
const stale = this.revalidate(generation, memory);
|
|
856
|
+
if (stale) return stale;
|
|
857
|
+
const lessons = lessonIds.map((id) => records.find((item) => item.id === id));
|
|
858
|
+
if (lessons.some((item) => !item || item.kind !== "lesson")) return fail("INVALID", "找不到所选教训。");
|
|
859
|
+
return lessons;
|
|
860
|
+
}
|
|
861
|
+
async previewSkill(lessonIds) {
|
|
862
|
+
const work = this.beginWork();
|
|
863
|
+
if (!this.isWork(work)) return work;
|
|
864
|
+
const { generation, memory } = work;
|
|
865
|
+
if (lessonIds.length === 0) return fail("INVALID", "请选择已生效的教训。");
|
|
866
|
+
const lessons = await this.liveSkillLessons(lessonIds, memory, generation);
|
|
867
|
+
if (!Array.isArray(lessons)) return lessons;
|
|
868
|
+
const stale = this.revalidate(generation, memory);
|
|
869
|
+
if (stale) return stale;
|
|
870
|
+
const now = this.now();
|
|
871
|
+
if (lessons.some((item) => item.status !== "active" || isExpired(item, now))) return fail("INVALID", "只能从未过期且已生效的教训生成技能草稿。");
|
|
872
|
+
const record = {
|
|
873
|
+
id: crypto.randomUUID(),
|
|
874
|
+
revision: 0,
|
|
875
|
+
status: "preview",
|
|
876
|
+
lessonIds,
|
|
877
|
+
sources: skillSourcesFromLessons(lessons),
|
|
878
|
+
title: skillTitleFromLessons(lessons),
|
|
879
|
+
markdown: renderSkillMarkdown(lessons, now),
|
|
880
|
+
createdAt: now,
|
|
881
|
+
updatedAt: now,
|
|
882
|
+
exportState: "none",
|
|
883
|
+
audit: [{
|
|
884
|
+
at: now,
|
|
885
|
+
action: "preview"
|
|
886
|
+
}]
|
|
887
|
+
};
|
|
888
|
+
const stored = await this.mutateWhileCurrent(generation, memory, () => this.persist(() => this.options.skills.put(record.id, record)));
|
|
889
|
+
if (this.failed(stored)) return stored;
|
|
890
|
+
return {
|
|
891
|
+
ok: true,
|
|
892
|
+
value: record
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
skillOrError(id) {
|
|
896
|
+
const record = this.options.skills.get(id);
|
|
897
|
+
if (!record) return fail("NOT_FOUND", "找不到该技能草稿。");
|
|
898
|
+
return record;
|
|
899
|
+
}
|
|
900
|
+
async requireCurrentSkillSources(skill, memory, generation) {
|
|
901
|
+
const lessons = await this.liveSkillLessons(skill.lessonIds, memory, generation);
|
|
902
|
+
if (!Array.isArray(lessons)) return lessons;
|
|
903
|
+
const stale = this.revalidate(generation, memory);
|
|
904
|
+
if (stale) return stale;
|
|
905
|
+
if (!sourcesMatchLive(skill.sources, lessons, this.now())) return fail("STALE", STALE_SKILL_MESSAGE);
|
|
906
|
+
}
|
|
907
|
+
async patchSkill(id, expectedRevision, patch) {
|
|
908
|
+
const work = this.beginWork();
|
|
909
|
+
if (!this.isWork(work)) return work;
|
|
910
|
+
const { generation, memory } = work;
|
|
911
|
+
const current = this.skillOrError(id);
|
|
912
|
+
if (!("id" in current)) return current;
|
|
913
|
+
if (current.revision !== expectedRevision) return fail("STALE", "记录已更新,请刷新后重试。");
|
|
914
|
+
const next = {
|
|
915
|
+
...patch(current),
|
|
916
|
+
revision: current.revision + 1,
|
|
917
|
+
updatedAt: this.now()
|
|
918
|
+
};
|
|
919
|
+
const stored = await this.mutateWhileCurrent(generation, memory, () => this.persist(() => this.options.skills.put(id, next)));
|
|
920
|
+
if (this.failed(stored)) return stored;
|
|
921
|
+
return {
|
|
922
|
+
ok: true,
|
|
923
|
+
value: next
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
async acceptSkill(id, expectedRevision) {
|
|
927
|
+
const work = this.beginWork();
|
|
928
|
+
if (!this.isWork(work)) return work;
|
|
929
|
+
const { generation, memory } = work;
|
|
930
|
+
const current = this.skillOrError(id);
|
|
931
|
+
if (!("id" in current)) return current;
|
|
932
|
+
const stale = await this.requireCurrentSkillSources(current, memory, generation);
|
|
933
|
+
if (stale) return stale;
|
|
934
|
+
const after = this.revalidate(generation, memory);
|
|
935
|
+
if (after) return after;
|
|
936
|
+
return this.patchSkill(id, expectedRevision, (record) => ({
|
|
937
|
+
...record,
|
|
938
|
+
status: "accepted",
|
|
939
|
+
audit: [...record.audit, {
|
|
940
|
+
at: this.now(),
|
|
941
|
+
action: "accepted"
|
|
942
|
+
}]
|
|
943
|
+
}));
|
|
944
|
+
}
|
|
945
|
+
async prepareSkillExport(id, expectedRevision) {
|
|
946
|
+
const work = this.beginWork();
|
|
947
|
+
if (!this.isWork(work)) return work;
|
|
948
|
+
const { generation, memory } = work;
|
|
949
|
+
const current = this.skillOrError(id);
|
|
950
|
+
if (!("id" in current)) return current;
|
|
951
|
+
if (current.revision !== expectedRevision) return fail("STALE", "记录已更新,请刷新后重试。");
|
|
952
|
+
if (current.status !== "accepted" && current.status !== "preview") return fail("INVALID", "只能导出预览或已接受的技能草稿。");
|
|
953
|
+
const stale = await this.requireCurrentSkillSources(current, memory, generation);
|
|
954
|
+
if (stale) return stale;
|
|
955
|
+
const after = this.revalidate(generation, memory);
|
|
956
|
+
if (after) return after;
|
|
957
|
+
return {
|
|
958
|
+
ok: true,
|
|
959
|
+
value: {
|
|
960
|
+
filename: skillDownloadName(current.id, current.title),
|
|
961
|
+
markdown: current.markdown,
|
|
962
|
+
skill: current
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
async recordSkillExport(id, expectedRevision, filename) {
|
|
967
|
+
return this.patchSkill(id, expectedRevision, (record) => ({
|
|
968
|
+
...record,
|
|
969
|
+
exportState: "recorded",
|
|
970
|
+
exportedAt: this.now(),
|
|
971
|
+
exportFilename: filename,
|
|
972
|
+
audit: [...record.audit, {
|
|
973
|
+
at: this.now(),
|
|
974
|
+
action: "export",
|
|
975
|
+
detail: filename
|
|
976
|
+
}]
|
|
977
|
+
}));
|
|
978
|
+
}
|
|
979
|
+
async snapshot(sessionId) {
|
|
980
|
+
const session = sessionId ? this.options.sessionOf(sessionId) : void 0;
|
|
981
|
+
const projectId = projectIdFromCwd(sessionCwd(session));
|
|
982
|
+
const memoryAvailable = Boolean(this.options.memory());
|
|
983
|
+
return {
|
|
984
|
+
ok: true,
|
|
985
|
+
value: {
|
|
986
|
+
memoryAvailable,
|
|
987
|
+
memoryMessage: memoryAvailable ? void 0 : MEMORY_UNAVAILABLE_MESSAGE,
|
|
988
|
+
projectId,
|
|
989
|
+
generation: this.generation,
|
|
990
|
+
storageFailed: this.storageFailed,
|
|
991
|
+
lessons: memoryAvailable ? await this.listAllLessons(projectId) : [],
|
|
992
|
+
skills: this.skills(),
|
|
993
|
+
extracting: this.isExtracting(sessionId)
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
async call(endpoint, payload, signal) {
|
|
998
|
+
try {
|
|
999
|
+
signal.throwIfAborted();
|
|
1000
|
+
const body = asObject(payload);
|
|
1001
|
+
const sessionId = str(body, "sessionId");
|
|
1002
|
+
const projectId = projectIdFromCwd(sessionCwd(this.options.sessionOf(sessionId)));
|
|
1003
|
+
if (endpoint === "status") return await this.snapshot(sessionId || void 0);
|
|
1004
|
+
if (endpoint === "extract") {
|
|
1005
|
+
const session = this.options.sessionOf(sessionId);
|
|
1006
|
+
if (!session) return fail("NOT_FOUND", "找不到会话。");
|
|
1007
|
+
return await this.extractFromSession(session, signal, "manual");
|
|
1008
|
+
}
|
|
1009
|
+
if (endpoint === "inspect") {
|
|
1010
|
+
const work = this.beginWork();
|
|
1011
|
+
if (!this.isWork(work)) return work;
|
|
1012
|
+
const record = await this.requireLesson(str(body, "id"), projectId, work.memory, work.generation);
|
|
1013
|
+
return "id" in record ? {
|
|
1014
|
+
ok: true,
|
|
1015
|
+
value: record
|
|
1016
|
+
} : record;
|
|
1017
|
+
}
|
|
1018
|
+
if (endpoint === "accept") {
|
|
1019
|
+
const revision = int(body, "expectedRevision");
|
|
1020
|
+
if (!str(body, "id") || revision === void 0) return fail("INVALID", "缺少记录或版本。");
|
|
1021
|
+
const scope = str(body, "scope") === "global" ? "global" : "project";
|
|
1022
|
+
return await this.acceptLesson(str(body, "id"), revision, scope, projectId);
|
|
1023
|
+
}
|
|
1024
|
+
if (endpoint === "reject" || endpoint === "revoke") {
|
|
1025
|
+
const revision = int(body, "expectedRevision");
|
|
1026
|
+
if (!str(body, "id") || revision === void 0) return fail("INVALID", "缺少记录或版本。");
|
|
1027
|
+
return await this.setLessonStatus(str(body, "id"), revision, endpoint === "reject" ? "rejected" : "revoked", projectId);
|
|
1028
|
+
}
|
|
1029
|
+
if (endpoint === "skill.preview") {
|
|
1030
|
+
const lessonIds = Array.isArray(body.lessonIds) ? body.lessonIds.filter((id) => typeof id === "string") : [];
|
|
1031
|
+
return await this.previewSkill(lessonIds);
|
|
1032
|
+
}
|
|
1033
|
+
if (endpoint === "skill.accept") {
|
|
1034
|
+
const revision = int(body, "expectedRevision");
|
|
1035
|
+
if (!str(body, "id") || revision === void 0) return fail("INVALID", "缺少记录或版本。");
|
|
1036
|
+
return await this.acceptSkill(str(body, "id"), revision);
|
|
1037
|
+
}
|
|
1038
|
+
if (endpoint === "skill.reject" || endpoint === "skill.revoke") {
|
|
1039
|
+
const revision = int(body, "expectedRevision");
|
|
1040
|
+
if (!str(body, "id") || revision === void 0) return fail("INVALID", "缺少记录或版本。");
|
|
1041
|
+
const status = endpoint === "skill.reject" ? "rejected" : "revoked";
|
|
1042
|
+
return await this.patchSkill(str(body, "id"), revision, (current) => ({
|
|
1043
|
+
...current,
|
|
1044
|
+
status,
|
|
1045
|
+
audit: [...current.audit, {
|
|
1046
|
+
at: this.now(),
|
|
1047
|
+
action: status
|
|
1048
|
+
}]
|
|
1049
|
+
}));
|
|
1050
|
+
}
|
|
1051
|
+
if (endpoint === "skill.export") {
|
|
1052
|
+
const revision = int(body, "expectedRevision");
|
|
1053
|
+
if (!str(body, "id") || revision === void 0) return fail("INVALID", "缺少记录或版本。");
|
|
1054
|
+
return await this.prepareSkillExport(str(body, "id"), revision);
|
|
1055
|
+
}
|
|
1056
|
+
if (endpoint === "skill.exported") {
|
|
1057
|
+
const revision = int(body, "expectedRevision");
|
|
1058
|
+
const filename = str(body, "filename");
|
|
1059
|
+
if (!str(body, "id") || revision === void 0 || !filename) return fail("INVALID", "缺少记录、版本或文件名。");
|
|
1060
|
+
return await this.recordSkillExport(str(body, "id"), revision, filename);
|
|
1061
|
+
}
|
|
1062
|
+
if (endpoint === "skill.unexport") {
|
|
1063
|
+
const revision = int(body, "expectedRevision");
|
|
1064
|
+
if (!str(body, "id") || revision === void 0) return fail("INVALID", "缺少记录或版本。");
|
|
1065
|
+
return await this.patchSkill(str(body, "id"), revision, (current) => ({
|
|
1066
|
+
...current,
|
|
1067
|
+
exportState: "revoked",
|
|
1068
|
+
exportRevokedAt: this.now(),
|
|
1069
|
+
audit: [...current.audit, {
|
|
1070
|
+
at: this.now(),
|
|
1071
|
+
action: "unexport",
|
|
1072
|
+
detail: "in-app record only"
|
|
1073
|
+
}]
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
return fail("INVALID", "未知操作。");
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
if (this.isGuardRejection(error) || this.lifetime.signal.aborted) return fail("CANCELLED", "操作已取消。");
|
|
1079
|
+
if (this.storageFailed) return fail("STORAGE_FAILED", "保存失败,已保留上一次成功的状态。");
|
|
1080
|
+
return fail("INTERNAL", error instanceof Error ? error.message : "操作失败。");
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
};
|
|
1084
|
+
//#endregion
|
|
1085
|
+
//#region src/storage.ts
|
|
1086
|
+
const knowledgeScopeSchema = z.union([z.object({ kind: z.literal("global") }).strict(), z.object({
|
|
1087
|
+
kind: z.literal("project"),
|
|
1088
|
+
projectId: z.string().min(1).max(MAX_PROJECT_ID_CHARS)
|
|
1089
|
+
}).strict()]);
|
|
1090
|
+
const skillSourceSchema = z.object({
|
|
1091
|
+
id: z.string().min(1).max(80),
|
|
1092
|
+
revision: z.number().int().nonnegative(),
|
|
1093
|
+
status: z.enum([
|
|
1094
|
+
"candidate",
|
|
1095
|
+
"active",
|
|
1096
|
+
"rejected",
|
|
1097
|
+
"superseded",
|
|
1098
|
+
"revoked",
|
|
1099
|
+
"deleted"
|
|
1100
|
+
]),
|
|
1101
|
+
scope: knowledgeScopeSchema,
|
|
1102
|
+
expiresAt: z.number().int().nonnegative().optional()
|
|
1103
|
+
}).strict();
|
|
1104
|
+
const skillRecordSchema = z.object({
|
|
1105
|
+
id: z.string().min(1).max(80),
|
|
1106
|
+
revision: z.number().int().nonnegative(),
|
|
1107
|
+
status: z.enum([
|
|
1108
|
+
"preview",
|
|
1109
|
+
"accepted",
|
|
1110
|
+
"rejected",
|
|
1111
|
+
"revoked"
|
|
1112
|
+
]),
|
|
1113
|
+
lessonIds: z.array(z.string().min(1).max(80)).min(1).max(16),
|
|
1114
|
+
sources: z.array(skillSourceSchema).min(1).max(16),
|
|
1115
|
+
title: z.string().min(1).max(160),
|
|
1116
|
+
markdown: z.string().min(1).max(8e4),
|
|
1117
|
+
createdAt: z.number().int().nonnegative(),
|
|
1118
|
+
updatedAt: z.number().int().nonnegative(),
|
|
1119
|
+
exportState: z.enum([
|
|
1120
|
+
"none",
|
|
1121
|
+
"recorded",
|
|
1122
|
+
"revoked"
|
|
1123
|
+
]),
|
|
1124
|
+
exportedAt: z.number().int().nonnegative().optional(),
|
|
1125
|
+
exportRevokedAt: z.number().int().nonnegative().optional(),
|
|
1126
|
+
exportFilename: z.string().max(180).optional(),
|
|
1127
|
+
audit: z.array(z.object({
|
|
1128
|
+
at: z.number().int().nonnegative(),
|
|
1129
|
+
action: z.string().min(1).max(64),
|
|
1130
|
+
detail: z.string().max(500).optional()
|
|
1131
|
+
}).strict()).max(64)
|
|
1132
|
+
}).strict();
|
|
1133
|
+
const watermarkSchema = z.object({
|
|
1134
|
+
sessionId: z.string().min(1).max(200),
|
|
1135
|
+
seq: z.number().int().nonnegative(),
|
|
1136
|
+
projectId: z.string().min(1).max(MAX_PROJECT_ID_CHARS).optional(),
|
|
1137
|
+
updatedAt: z.number().int().nonnegative()
|
|
1138
|
+
}).strict();
|
|
1139
|
+
const selfImprovementDomain = defineDomain({
|
|
1140
|
+
name: "dsh_editor_self_improvement",
|
|
1141
|
+
version: 1,
|
|
1142
|
+
tables: {
|
|
1143
|
+
skills: domainTable(skillRecordSchema),
|
|
1144
|
+
watermarks: domainTable(watermarkSchema)
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
//#endregion
|
|
1148
|
+
//#region src/index.ts
|
|
1149
|
+
const name = "@klarkxy/dsh-self-improvement";
|
|
1150
|
+
const inject = [
|
|
1151
|
+
"storageDomain",
|
|
1152
|
+
"connection",
|
|
1153
|
+
"webServer"
|
|
1154
|
+
];
|
|
1155
|
+
function readService(ctx, key) {
|
|
1156
|
+
const record = ctx;
|
|
1157
|
+
try {
|
|
1158
|
+
const direct = record[key];
|
|
1159
|
+
if (direct !== void 0) return direct;
|
|
1160
|
+
} catch {}
|
|
1161
|
+
try {
|
|
1162
|
+
return record.get?.(key);
|
|
1163
|
+
} catch {
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function sessionOf(ctx, sessionId) {
|
|
1168
|
+
return readService(ctx, "sessions")?.get?.(sessionId);
|
|
1169
|
+
}
|
|
1170
|
+
function listen(ctx, name, handler) {
|
|
1171
|
+
const off = ctx.on.call(ctx, name, handler);
|
|
1172
|
+
return typeof off === "function" ? off : void 0;
|
|
1173
|
+
}
|
|
1174
|
+
async function apply(ctx) {
|
|
1175
|
+
const host = ctx;
|
|
1176
|
+
const domain = await host.storageDomain.open(selfImprovementDomain);
|
|
1177
|
+
const engine = new SelfImprovementEngine({
|
|
1178
|
+
memory: () => readService(ctx, "aiMemory"),
|
|
1179
|
+
ai: () => readService(ctx, "aiServices"),
|
|
1180
|
+
sessionOf: (id) => sessionOf(ctx, id),
|
|
1181
|
+
skills: domain.table("skills"),
|
|
1182
|
+
watermarks: domain.table("watermarks")
|
|
1183
|
+
});
|
|
1184
|
+
ctx.effect(() => async () => {
|
|
1185
|
+
engine.dispose();
|
|
1186
|
+
await domain.close();
|
|
1187
|
+
}, "self-improvement.dispose");
|
|
1188
|
+
ctx.provide("selfImprovement", engine);
|
|
1189
|
+
ctx.effect(() => registerHostRpc(host, SELF_IMPROVEMENT_RPC_CHANNEL, (endpoint, payload, signal) => engine.call(endpoint, payload, signal)), "self-improvement.rpc");
|
|
1190
|
+
ctx.effect(() => {
|
|
1191
|
+
const offStep = listen(ctx, "agent/pre-step", (async (payload, next) => {
|
|
1192
|
+
const decision = await next();
|
|
1193
|
+
return engine.applyPreStep(payload.agent.session, decision, payload.signal);
|
|
1194
|
+
}));
|
|
1195
|
+
const offStop = listen(ctx, "agent/turn-stopping", ((payload) => {
|
|
1196
|
+
engine.considerSession(payload.agent.session, payload.signal);
|
|
1197
|
+
}));
|
|
1198
|
+
return () => {
|
|
1199
|
+
offStep?.();
|
|
1200
|
+
offStop?.();
|
|
1201
|
+
};
|
|
1202
|
+
}, "self-improvement.hooks");
|
|
1203
|
+
}
|
|
1204
|
+
//#endregion
|
|
1205
|
+
export { CHAT_EVENTS_SLOT, SELF_IMPROVEMENT_RPC_CHANNEL, SelfImprovementEngine, apply, inject, listen, name };
|
|
1206
|
+
|
|
1207
|
+
//# sourceMappingURL=index.js.map
|