dsh-continual-evolve 0.7.1 → 0.7.2
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 +13 -6
- package/README.zh.md +8 -6
- package/lib/auto.d.ts +11 -5
- package/lib/auto.js +119 -81
- package/lib/command.d.ts +2 -3
- package/lib/command.js +19 -17
- package/lib/fate.d.ts +1 -1
- package/lib/fate.js +4 -4
- package/lib/index.d.ts +4 -4
- package/lib/index.js +27 -24
- package/lib/planner.d.ts +5 -0
- package/lib/planner.js +1 -1
- package/lib/review-scheduler.d.ts +37 -0
- package/lib/review-scheduler.js +110 -0
- package/lib/review.d.ts +3 -1
- package/lib/review.js +1 -1
- package/lib/runtime.d.ts +13 -13
- package/lib/runtime.js +41 -37
- package/lib/turn-snapshot.d.ts +58 -0
- package/lib/turn-snapshot.js +188 -0
- package/package.json +1 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { sessionEventsOf } from "./inject.js";
|
|
2
|
+
import { projectKeyOf } from "./project.js";
|
|
3
|
+
/**
|
|
4
|
+
* Capture the current durable surface and select only rows after `cursor`.
|
|
5
|
+
* A seq-bearing session uses `seq:<n>`; older/test surfaces fall back to an
|
|
6
|
+
* event-count cursor so the scheduler still has a deterministic boundary.
|
|
7
|
+
*/
|
|
8
|
+
export async function captureTurnSnapshot(ctx, agent, opts) {
|
|
9
|
+
const allEvents = await readSurface(ctx, agent);
|
|
10
|
+
const cursor = boundaryCursor(allEvents);
|
|
11
|
+
const events = eventsAfterCursor(allEvents, opts.cursor);
|
|
12
|
+
const eligibility = evaluateSnapshotEligibility(events, isInternalAgent(agent));
|
|
13
|
+
const projectKey = projectKeyOf(agent);
|
|
14
|
+
return {
|
|
15
|
+
sessionId: agent.id,
|
|
16
|
+
...(projectKey ? { projectKey } : {}),
|
|
17
|
+
turn: opts.turn,
|
|
18
|
+
reason: opts.reason,
|
|
19
|
+
cursor,
|
|
20
|
+
events,
|
|
21
|
+
trajectory: eligibility.eligible ? serializeSnapshotEvents(events, opts.maxChars) : "",
|
|
22
|
+
userText: eligibility.userText,
|
|
23
|
+
sourceSeqs: directUserSeqs(events),
|
|
24
|
+
eligible: eligibility.eligible,
|
|
25
|
+
...(eligibility.reason ? { skipReason: eligibility.reason } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Decide whether the incremental rows deserve a model call. This intentionally
|
|
30
|
+
* does not decide whether a fact is valuable; that remains the review model's
|
|
31
|
+
* job. It only removes mechanical noise before the scheduler spends tokens.
|
|
32
|
+
*/
|
|
33
|
+
export function evaluateSnapshotEligibility(events, internalAgent = false) {
|
|
34
|
+
if (internalAgent)
|
|
35
|
+
return { eligible: false, reason: "internal-agent", userText: "" };
|
|
36
|
+
if (events.length === 0)
|
|
37
|
+
return { eligible: false, reason: "no-new-events", userText: "" };
|
|
38
|
+
if (containsDirectMemoryWrite(events))
|
|
39
|
+
return { eligible: false, reason: "direct-memory-write", userText: "" };
|
|
40
|
+
const userText = directUserText(events);
|
|
41
|
+
if (userText.length < 3)
|
|
42
|
+
return { eligible: false, reason: "no-user-prose", userText };
|
|
43
|
+
return { eligible: true, userText };
|
|
44
|
+
}
|
|
45
|
+
/** True when an assistant row records an explicit evolve memory mutation. */
|
|
46
|
+
export function containsDirectMemoryWrite(events) {
|
|
47
|
+
for (const event of events) {
|
|
48
|
+
if (!event || typeof event !== "object")
|
|
49
|
+
continue;
|
|
50
|
+
const row = event;
|
|
51
|
+
if (row.type === "tool/call" || row.type === "tool-call") {
|
|
52
|
+
if (isMemoryMutationName(readNestedString(row.data, ["name", "tool", "toolName"])))
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (row.type === "assistant/message" && containsMemoryMutationName(row.data))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
/** Serialize only user/assistant text, retaining the bounded tail. */
|
|
61
|
+
export function serializeSnapshotEvents(events, maxChars) {
|
|
62
|
+
const lines = [];
|
|
63
|
+
for (const raw of events) {
|
|
64
|
+
if (!raw || typeof raw !== "object")
|
|
65
|
+
continue;
|
|
66
|
+
const event = raw;
|
|
67
|
+
if (event.type === "user/message" && !isDirectUserEvent(raw))
|
|
68
|
+
continue;
|
|
69
|
+
const role = event.type === "user/message" ? "user" : event.type === "assistant/message" ? "assistant" : null;
|
|
70
|
+
if (role === null)
|
|
71
|
+
continue;
|
|
72
|
+
const text = contentText(event.data?.content).trim();
|
|
73
|
+
if (text.length > 0)
|
|
74
|
+
lines.push(`${role}: ${text}`);
|
|
75
|
+
}
|
|
76
|
+
const joined = lines.join("\n");
|
|
77
|
+
return joined.length <= maxChars ? joined : joined.slice(-maxChars);
|
|
78
|
+
}
|
|
79
|
+
/** A DSH subagent header is an internal source when its origin says so. */
|
|
80
|
+
export function isInternalAgent(agent) {
|
|
81
|
+
const header = agent?.session?.header;
|
|
82
|
+
return header?.origin === "subagent" || header?.origin === "internal" || header?.isSubagent === true;
|
|
83
|
+
}
|
|
84
|
+
async function readSurface(ctx, agent) {
|
|
85
|
+
const reader = ctx.sessionQuery;
|
|
86
|
+
if (reader) {
|
|
87
|
+
const snapshot = await reader.readSurface(agent.id);
|
|
88
|
+
return Array.isArray(snapshot.events) ? snapshot.events : [];
|
|
89
|
+
}
|
|
90
|
+
return sessionEventsOf(agent);
|
|
91
|
+
}
|
|
92
|
+
function boundaryCursor(events) {
|
|
93
|
+
const seqs = events.map(eventSeq).filter((seq) => seq !== undefined);
|
|
94
|
+
return seqs.length > 0 ? `seq:${Math.max(...seqs)}` : `index:${events.length}`;
|
|
95
|
+
}
|
|
96
|
+
function eventsAfterCursor(events, cursor) {
|
|
97
|
+
if (cursor === undefined)
|
|
98
|
+
return events;
|
|
99
|
+
if (cursor.startsWith("seq:")) {
|
|
100
|
+
const boundary = Number(cursor.slice(4));
|
|
101
|
+
if (!Number.isFinite(boundary))
|
|
102
|
+
return events;
|
|
103
|
+
return events.filter((event) => {
|
|
104
|
+
const seq = eventSeq(event);
|
|
105
|
+
return seq === undefined || seq > boundary;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (cursor.startsWith("index:")) {
|
|
109
|
+
const boundary = Number(cursor.slice(6));
|
|
110
|
+
return Number.isInteger(boundary) && boundary >= 0 ? events.slice(boundary) : events;
|
|
111
|
+
}
|
|
112
|
+
return events;
|
|
113
|
+
}
|
|
114
|
+
function directUserText(events) {
|
|
115
|
+
return events
|
|
116
|
+
.filter(isDirectUserEvent)
|
|
117
|
+
.map((event) => contentText(event.data?.content).trim())
|
|
118
|
+
.filter(Boolean)
|
|
119
|
+
.join("\n");
|
|
120
|
+
}
|
|
121
|
+
function directUserSeqs(events) {
|
|
122
|
+
return events
|
|
123
|
+
.filter(isDirectUserEvent)
|
|
124
|
+
.map(eventSeq)
|
|
125
|
+
.filter((seq) => seq !== undefined);
|
|
126
|
+
}
|
|
127
|
+
function isDirectUserEvent(event) {
|
|
128
|
+
if (!event || typeof event !== "object")
|
|
129
|
+
return false;
|
|
130
|
+
const row = event;
|
|
131
|
+
if (row.type !== "user/message")
|
|
132
|
+
return false;
|
|
133
|
+
if (row.data?.visibility === "model-only")
|
|
134
|
+
return false;
|
|
135
|
+
if (row.data?.source?.synthetic === true)
|
|
136
|
+
return false;
|
|
137
|
+
if (row.data?.source?.kind !== undefined && row.data.source.kind !== "user")
|
|
138
|
+
return false;
|
|
139
|
+
return contentText(row.data?.content).trim().length > 0;
|
|
140
|
+
}
|
|
141
|
+
function contentText(content) {
|
|
142
|
+
if (typeof content === "string")
|
|
143
|
+
return content;
|
|
144
|
+
if (!Array.isArray(content))
|
|
145
|
+
return "";
|
|
146
|
+
return content
|
|
147
|
+
.map((block) => {
|
|
148
|
+
if (typeof block === "string")
|
|
149
|
+
return block;
|
|
150
|
+
if (!block || typeof block !== "object")
|
|
151
|
+
return "";
|
|
152
|
+
const value = block;
|
|
153
|
+
if (value.synthetic === true || value.ignored === true || (value.type !== undefined && value.type !== "text"))
|
|
154
|
+
return "";
|
|
155
|
+
return typeof value.text === "string" ? value.text : "";
|
|
156
|
+
})
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join(" ");
|
|
159
|
+
}
|
|
160
|
+
function eventSeq(event) {
|
|
161
|
+
if (!event || typeof event !== "object")
|
|
162
|
+
return undefined;
|
|
163
|
+
const seq = event.seq;
|
|
164
|
+
return typeof seq === "number" && Number.isInteger(seq) ? seq : undefined;
|
|
165
|
+
}
|
|
166
|
+
function containsMemoryMutationName(value) {
|
|
167
|
+
if (typeof value === "string")
|
|
168
|
+
return isMemoryMutationName(value);
|
|
169
|
+
if (Array.isArray(value))
|
|
170
|
+
return value.some(containsMemoryMutationName);
|
|
171
|
+
if (!value || typeof value !== "object")
|
|
172
|
+
return false;
|
|
173
|
+
return Object.values(value).some(containsMemoryMutationName);
|
|
174
|
+
}
|
|
175
|
+
function isMemoryMutationName(value) {
|
|
176
|
+
return value === "evolve_add" || value === "evolve_update" || value === "evolve_delete";
|
|
177
|
+
}
|
|
178
|
+
function readNestedString(value, keys) {
|
|
179
|
+
if (!value || typeof value !== "object")
|
|
180
|
+
return undefined;
|
|
181
|
+
const row = value;
|
|
182
|
+
for (const key of keys) {
|
|
183
|
+
if (typeof row[key] === "string")
|
|
184
|
+
return row[key];
|
|
185
|
+
}
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=turn-snapshot.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Continual self-evolution plugin for DeepSeek Harness: versioned, auditable, rollback-safe harness state (prompt notes, memories, skills, subagent specs) refined from session trajectories.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|