dsh-continual-evolve 0.11.0 → 0.12.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 +35 -115
- package/README.zh.md +26 -93
- package/cordis.patch.yml +2 -2
- package/lib/index.d.ts +44 -225
- package/lib/index.js +30 -239
- package/lib/memory-guide.d.ts +13 -14
- package/lib/memory-guide.js +32 -21
- package/lib/memory-section.d.ts +77 -0
- package/lib/memory-section.js +189 -0
- package/package.json +2 -17
- package/lib/apply.d.ts +0 -22
- package/lib/apply.js +0 -157
- package/lib/approval.d.ts +0 -73
- package/lib/approval.js +0 -127
- package/lib/audit-log.d.ts +0 -19
- package/lib/audit-log.js +0 -52
- package/lib/auto.d.ts +0 -236
- package/lib/auto.js +0 -773
- package/lib/autocase.d.ts +0 -38
- package/lib/autocase.js +0 -76
- package/lib/benchmark-command.d.ts +0 -9
- package/lib/benchmark-command.js +0 -357
- package/lib/benchmark.d.ts +0 -151
- package/lib/benchmark.js +0 -250
- package/lib/command-util.d.ts +0 -70
- package/lib/command-util.js +0 -123
- package/lib/command.d.ts +0 -13
- package/lib/command.js +0 -134
- package/lib/consolidate.d.ts +0 -90
- package/lib/consolidate.js +0 -180
- package/lib/copy.d.ts +0 -37
- package/lib/copy.js +0 -58
- package/lib/declines.d.ts +0 -103
- package/lib/declines.js +0 -179
- package/lib/evaluate.d.ts +0 -74
- package/lib/evaluate.js +0 -271
- package/lib/evolve-event.d.ts +0 -40
- package/lib/evolve-event.js +0 -60
- package/lib/extraction-phase.d.ts +0 -132
- package/lib/extraction-phase.js +0 -236
- package/lib/failures.d.ts +0 -42
- package/lib/failures.js +0 -171
- package/lib/fate.d.ts +0 -130
- package/lib/fate.js +0 -350
- package/lib/goal-command.d.ts +0 -7
- package/lib/goal-command.js +0 -37
- package/lib/goal.d.ts +0 -72
- package/lib/goal.js +0 -72
- package/lib/inject.d.ts +0 -90
- package/lib/inject.js +0 -164
- package/lib/injection-caps.d.ts +0 -4
- package/lib/injection-caps.js +0 -4
- package/lib/injection-directory.d.ts +0 -29
- package/lib/injection-directory.js +0 -78
- package/lib/injection-rank.d.ts +0 -85
- package/lib/injection-rank.js +0 -161
- package/lib/listener.d.ts +0 -45
- package/lib/listener.js +0 -199
- package/lib/llm-text.d.ts +0 -104
- package/lib/llm-text.js +0 -140
- package/lib/logfile.d.ts +0 -71
- package/lib/logfile.js +0 -159
- package/lib/maintenance-commands.d.ts +0 -23
- package/lib/maintenance-commands.js +0 -136
- package/lib/memory-agent.d.ts +0 -93
- package/lib/memory-agent.js +0 -300
- package/lib/memory-apply.d.ts +0 -44
- package/lib/memory-apply.js +0 -202
- package/lib/memory-benchmark.d.ts +0 -76
- package/lib/memory-benchmark.js +0 -170
- package/lib/memory-index.d.ts +0 -114
- package/lib/memory-index.js +0 -197
- package/lib/memory-manifest.d.ts +0 -16
- package/lib/memory-manifest.js +0 -70
- package/lib/message-source.d.ts +0 -23
- package/lib/message-source.js +0 -12
- package/lib/mount-command.d.ts +0 -10
- package/lib/mount-command.js +0 -48
- package/lib/mount.d.ts +0 -42
- package/lib/mount.js +0 -212
- package/lib/notify.d.ts +0 -48
- package/lib/notify.js +0 -57
- package/lib/plan-command.d.ts +0 -12
- package/lib/plan-command.js +0 -38
- package/lib/plan.d.ts +0 -16
- package/lib/plan.js +0 -126
- package/lib/planner.d.ts +0 -61
- package/lib/planner.js +0 -144
- package/lib/pool.d.ts +0 -7
- package/lib/pool.js +0 -25
- package/lib/prefix-cache.d.ts +0 -79
- package/lib/prefix-cache.js +0 -168
- package/lib/project.d.ts +0 -28
- package/lib/project.js +0 -66
- package/lib/projection.d.ts +0 -22
- package/lib/projection.js +0 -112
- package/lib/promotion.d.ts +0 -94
- package/lib/promotion.js +0 -171
- package/lib/recall.d.ts +0 -85
- package/lib/recall.js +0 -142
- package/lib/record-language.d.ts +0 -85
- package/lib/record-language.js +0 -152
- package/lib/render.d.ts +0 -13
- package/lib/render.js +0 -80
- package/lib/review-scheduler.d.ts +0 -38
- package/lib/review-scheduler.js +0 -183
- package/lib/review.d.ts +0 -60
- package/lib/review.js +0 -145
- package/lib/rollback.d.ts +0 -9
- package/lib/rollback.js +0 -68
- package/lib/rubric.d.ts +0 -29
- package/lib/rubric.js +0 -119
- package/lib/runtime-commands.d.ts +0 -14
- package/lib/runtime-commands.js +0 -115
- package/lib/runtime.d.ts +0 -24
- package/lib/runtime.js +0 -87
- package/lib/score.d.ts +0 -64
- package/lib/score.js +0 -191
- package/lib/search.d.ts +0 -83
- package/lib/search.js +0 -136
- package/lib/service.d.ts +0 -63
- package/lib/service.js +0 -253
- package/lib/skill-render.d.ts +0 -23
- package/lib/skill-render.js +0 -69
- package/lib/skill.d.ts +0 -15
- package/lib/skill.js +0 -80
- package/lib/skillquality.d.ts +0 -85
- package/lib/skillquality.js +0 -320
- package/lib/source.d.ts +0 -30
- package/lib/source.js +0 -43
- package/lib/state.d.ts +0 -26
- package/lib/state.js +0 -151
- package/lib/store.d.ts +0 -61
- package/lib/store.js +0 -164
- package/lib/token-usage.d.ts +0 -68
- package/lib/token-usage.js +0 -211
- package/lib/tool.d.ts +0 -34
- package/lib/tool.js +0 -320
- package/lib/turn-snapshot.d.ts +0 -78
- package/lib/turn-snapshot.js +0 -252
- package/lib/types.d.ts +0 -218
- package/lib/types.js +0 -133
- package/lib/usage.d.ts +0 -36
- package/lib/usage.js +0 -98
- package/lib/validate.d.ts +0 -28
- package/lib/validate.js +0 -181
- package/lib/wrapup-candidates.d.ts +0 -127
- package/lib/wrapup-candidates.js +0 -161
- package/lib/wrapup-command.d.ts +0 -10
- package/lib/wrapup-command.js +0 -243
- package/lib/wrapup-proposals.d.ts +0 -101
- package/lib/wrapup-proposals.js +0 -281
- package/lib/wrapup.d.ts +0 -56
- package/lib/wrapup.js +0 -105
package/lib/listener.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { goalServiceOf } from "./goal.js";
|
|
2
|
-
import { isGateEnabled } from "./runtime.js";
|
|
3
|
-
import { createReviewScheduler } from "./review-scheduler.js";
|
|
4
|
-
import { captureTurnSnapshot } from "./turn-snapshot.js";
|
|
5
|
-
import { appendReviewRecord, writeArmedMarker } from "./audit-log.js";
|
|
6
|
-
import { runMemoryExtractionPhase, stateFor, } from "./extraction-phase.js";
|
|
7
|
-
/** Default session-close drain budget: an in-flight extraction gets 15s to settle. */
|
|
8
|
-
export const SESSION_CLOSE_DRAIN_MS_DEFAULT = 15000;
|
|
9
|
-
/**
|
|
10
|
-
* Resolve the session-close drain budget: absent/invalid → default, 0 →
|
|
11
|
-
* immediate abort (legacy). Negative or non-finite values fail back to the
|
|
12
|
-
* default rather than silently disabling the drain.
|
|
13
|
-
*/
|
|
14
|
-
export function resolveSessionCloseDrainMs(raw) {
|
|
15
|
-
if (raw === undefined)
|
|
16
|
-
return SESSION_CLOSE_DRAIN_MS_DEFAULT;
|
|
17
|
-
if (typeof raw !== "number" || !Number.isFinite(raw) || raw < 0)
|
|
18
|
-
return SESSION_CLOSE_DRAIN_MS_DEFAULT;
|
|
19
|
-
return Math.floor(raw);
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Bounded session-close drain for one scheduler: wait for in-flight work up
|
|
23
|
-
* to drainMs, then abort the owner signal and drop pending captures. Never
|
|
24
|
-
* rejects — disposal must not hang the host on a stuck model call, and the
|
|
25
|
-
* abort below is idempotent when the drain already settled.
|
|
26
|
-
*/
|
|
27
|
-
export function drainSchedulerOnDispose(scheduler, drainMs) {
|
|
28
|
-
if (drainMs <= 0) {
|
|
29
|
-
try {
|
|
30
|
-
scheduler.shutdown();
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// shutdown is flag-setting plus abort: nothing observable to do
|
|
34
|
-
}
|
|
35
|
-
return Promise.resolve();
|
|
36
|
-
}
|
|
37
|
-
let timer;
|
|
38
|
-
const timeout = new Promise((resolve) => {
|
|
39
|
-
timer = setTimeout(resolve, drainMs);
|
|
40
|
-
});
|
|
41
|
-
const settle = () => {
|
|
42
|
-
if (timer !== undefined)
|
|
43
|
-
clearTimeout(timer);
|
|
44
|
-
try {
|
|
45
|
-
scheduler.shutdown();
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
// same as above — the abort is best-effort by design
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
return Promise.race([scheduler.drain(), timeout]).then(settle, settle);
|
|
52
|
-
}
|
|
53
|
-
export function registerAutoReview(ctx, engine, config) {
|
|
54
|
-
const perSession = new Map();
|
|
55
|
-
const schedulers = new Map();
|
|
56
|
-
const captureTails = new Map();
|
|
57
|
-
const disposedSessions = new Set();
|
|
58
|
-
const logger = ctx.logger("continual-evolve");
|
|
59
|
-
const defaultEnabled = config.enabledByDefault ?? true;
|
|
60
|
-
const record = (entry) => {
|
|
61
|
-
appendReviewRecord(engine.baseDir, entry, config.reviewsRetain, (cause) => logger.warn(`failed to record auto-review: ${cause instanceof Error ? cause.message : String(cause)}`));
|
|
62
|
-
};
|
|
63
|
-
const schedulerFor = (agent, state) => {
|
|
64
|
-
const existing = schedulers.get(agent.id);
|
|
65
|
-
if (existing)
|
|
66
|
-
return existing;
|
|
67
|
-
const scheduler = createReviewScheduler(async ({ snapshot, signal }) => {
|
|
68
|
-
if (!isGateEnabled(engine.baseDir, defaultEnabled))
|
|
69
|
-
return "aborted";
|
|
70
|
-
if (!snapshot.eligible) {
|
|
71
|
-
record({
|
|
72
|
-
sessionId: snapshot.sessionId,
|
|
73
|
-
reason: snapshot.reason,
|
|
74
|
-
turnsSinceLastReview: state.turns - state.lastReviewAt,
|
|
75
|
-
outcome: "skipped",
|
|
76
|
-
rationale: `snapshot ${snapshot.cursor} skipped: ${snapshot.skipReason ?? "not eligible"}`,
|
|
77
|
-
});
|
|
78
|
-
return "no-op";
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
await runMemoryExtractionPhase(ctx, engine, agent, config, state, snapshot, record, signal);
|
|
82
|
-
return "success";
|
|
83
|
-
}
|
|
84
|
-
catch (cause) {
|
|
85
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
86
|
-
logger.warn(`memory extraction failed for ${agent.id}: ${message}`);
|
|
87
|
-
record({
|
|
88
|
-
sessionId: agent.id,
|
|
89
|
-
reason: snapshot.reason,
|
|
90
|
-
turnsSinceLastReview: state.turns - state.lastReviewAt,
|
|
91
|
-
outcome: "failed",
|
|
92
|
-
rationale: `extraction error: ${message}`,
|
|
93
|
-
});
|
|
94
|
-
return "error";
|
|
95
|
-
}
|
|
96
|
-
}, (snapshot) => snapshot.cursor);
|
|
97
|
-
schedulers.set(agent.id, scheduler);
|
|
98
|
-
return scheduler;
|
|
99
|
-
};
|
|
100
|
-
const captureAndSchedule = (agent, state, reason) => {
|
|
101
|
-
if (disposedSessions.has(agent.id) || !isGateEnabled(engine.baseDir, defaultEnabled))
|
|
102
|
-
return;
|
|
103
|
-
const previousCapture = captureTails.get(agent.id) ?? Promise.resolve();
|
|
104
|
-
const captureTask = previousCapture.then(async () => {
|
|
105
|
-
if (disposedSessions.has(agent.id) || !isGateEnabled(engine.baseDir, defaultEnabled))
|
|
106
|
-
return;
|
|
107
|
-
const cursor = schedulerFor(agent, state).getCursor();
|
|
108
|
-
const snapshot = await captureTurnSnapshot(ctx, agent, {
|
|
109
|
-
turn: state.completedTurn,
|
|
110
|
-
reason,
|
|
111
|
-
...(cursor !== undefined ? { cursor } : {}),
|
|
112
|
-
maxChars: config.maxInputChars,
|
|
113
|
-
...(config.memoryMinUserWords !== undefined ? { minUserWords: config.memoryMinUserWords } : {}),
|
|
114
|
-
});
|
|
115
|
-
if (!disposedSessions.has(agent.id))
|
|
116
|
-
schedulerFor(agent, state).schedule(snapshot);
|
|
117
|
-
}).catch((cause) => {
|
|
118
|
-
const message = cause instanceof Error ? cause.message : String(cause);
|
|
119
|
-
logger.warn(`memory snapshot failed for ${agent.id}: ${message}`);
|
|
120
|
-
record({
|
|
121
|
-
sessionId: agent.id,
|
|
122
|
-
reason,
|
|
123
|
-
turnsSinceLastReview: state.turns - state.lastReviewAt,
|
|
124
|
-
outcome: "failed",
|
|
125
|
-
rationale: `snapshot error: ${message}`,
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
captureTails.set(agent.id, captureTask);
|
|
129
|
-
};
|
|
130
|
-
// DSH emits this awaited boundary when a turn is about to close. It only
|
|
131
|
-
// advances the mechanical counters — extraction is moment-driven, so a
|
|
132
|
-
// successful turn never costs an LLM call by itself.
|
|
133
|
-
ctx.on("agent/turn-stopping", (payload) => {
|
|
134
|
-
const agent = payload.agent;
|
|
135
|
-
if (!agent) {
|
|
136
|
-
logger.warn(`memory extraction listener: agent/turn-stopping payload missing agent; skipping count`);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
const state = stateFor(perSession, agent.id);
|
|
140
|
-
state.turns += 1;
|
|
141
|
-
if (typeof payload.turn === "number")
|
|
142
|
-
state.completedTurn = Math.max(state.completedTurn, payload.turn);
|
|
143
|
-
});
|
|
144
|
-
// The idle transition is the safe point at which to read the durable
|
|
145
|
-
// surface. It probes the goal phase (mechanical, zero LLM) and fires the
|
|
146
|
-
// goal-blocked moment when the streak reaches the threshold.
|
|
147
|
-
ctx.on("agent/status", (payload) => {
|
|
148
|
-
const agent = payload.agent;
|
|
149
|
-
if (!agent || payload.status !== "idle")
|
|
150
|
-
return;
|
|
151
|
-
if (config.goalBlockedWrapupTurns <= 0)
|
|
152
|
-
return;
|
|
153
|
-
const state = stateFor(perSession, agent.id);
|
|
154
|
-
const goal = goalServiceOf(ctx)?.get(agent);
|
|
155
|
-
if (goal?.phase !== "blocked") {
|
|
156
|
-
state.goalBlockStreak = 0;
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
state.goalBlockStreak += 1;
|
|
160
|
-
if (state.goalBlockStreak < config.goalBlockedWrapupTurns)
|
|
161
|
-
return;
|
|
162
|
-
state.goalBlockStreak = 0; // one moment per streak; declines re-arm after the next full streak
|
|
163
|
-
logger.info(`goal-blocked moment [${agent.id}]: ${config.goalBlockedWrapupTurns} consecutive blocked rounds → memory extraction`);
|
|
164
|
-
captureAndSchedule(agent, state, "goal_blocked");
|
|
165
|
-
});
|
|
166
|
-
// Boot-time diagnostic: proves the listener was registered even when its
|
|
167
|
-
// runtime default is off, distinguishing "not registered" from "off".
|
|
168
|
-
writeArmedMarker(engine.baseDir, `automatic evolution listener registered (mode=memory-only; default=${defaultEnabled ? "on" : "off"}; moments=compaction/goal-blocked/session-close-drain/manual-wrapup)`, (cause) => logger.warn(`failed to write armed marker: ${cause instanceof Error ? cause.message : String(cause)}`));
|
|
169
|
-
ctx.on("agent/disposed", (payload) => {
|
|
170
|
-
const agent = payload.agent;
|
|
171
|
-
if (!agent)
|
|
172
|
-
return;
|
|
173
|
-
disposedSessions.add(agent.id);
|
|
174
|
-
const scheduler = schedulers.get(agent.id);
|
|
175
|
-
schedulers.delete(agent.id);
|
|
176
|
-
captureTails.delete(agent.id);
|
|
177
|
-
perSession.delete(agent.id);
|
|
178
|
-
if (scheduler) {
|
|
179
|
-
// Bounded session-close drain: an in-flight extraction gets drainMs
|
|
180
|
-
// to settle so its result is not lost on session close; afterwards
|
|
181
|
-
// the owner signal aborts. Fire-and-forget — disposal must never
|
|
182
|
-
// hang the host, and the helper never rejects.
|
|
183
|
-
void drainSchedulerOnDispose(scheduler, resolveSessionCloseDrainMs(config.sessionCloseDrainMs));
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
ctx.on("session/event", (session, event) => {
|
|
187
|
-
// Compaction moment: extract before the pre-compaction trajectory is
|
|
188
|
-
// summarized away. Cold sessions (no live agent) are ignored.
|
|
189
|
-
if (event.type !== "compaction/start")
|
|
190
|
-
return;
|
|
191
|
-
const agents = ctx.agents;
|
|
192
|
-
const agent = agents?.get(session.id);
|
|
193
|
-
if (!agent)
|
|
194
|
-
return;
|
|
195
|
-
const state = stateFor(perSession, agent.id);
|
|
196
|
-
captureAndSchedule(agent, state, "compact");
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
//# sourceMappingURL=listener.js.map
|
package/lib/llm-text.d.ts
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unified LLM text call: a shared streaming-text helper used by the review
|
|
3
|
-
* gate, planner, and wrap-up assessor. Eliminates ~120 lines of duplicated
|
|
4
|
-
* BlockAssembler + finish-state-check + text-extraction boilerplate.
|
|
5
|
-
*
|
|
6
|
-
* Every caller needs the same sequence:
|
|
7
|
-
* provider/model validation → stream → assemble → check finish → extract text
|
|
8
|
-
* This module owns that sequence; callers keep only their prompt construction
|
|
9
|
-
* and JSON parsing.
|
|
10
|
-
*/
|
|
11
|
-
import type { Context } from "@deepseek-ai/cordis";
|
|
12
|
-
import { type ContentBlock, type GenerateOptions, type LlmResolvedModelInfo, type Message, type ReasoningEffortId, type RequestMessage, type TokenUsage, type ToolSchema } from "@deepseek-ai/dsh-llm";
|
|
13
|
-
/** Terminal outcome of one shared direct LLM call. */
|
|
14
|
-
export type StreamTextOutcome = "success" | "max-tokens" | "error" | "aborted" | "empty";
|
|
15
|
-
/** Host-owned session identity type used by provider routing metadata. */
|
|
16
|
-
export type LlmSessionId = NonNullable<GenerateOptions["sessionId"]>;
|
|
17
|
-
/** Carry the host Agent id across the plugin's direct-call boundary. */
|
|
18
|
-
export declare function asLlmSessionId(value: string): LlmSessionId;
|
|
19
|
-
/** Provider usage and terminal result observed after the stream settles. */
|
|
20
|
-
export interface StreamTextObservation {
|
|
21
|
-
usage?: TokenUsage;
|
|
22
|
-
outcome: StreamTextOutcome;
|
|
23
|
-
/** Exact capability-derived effort used for this call; absent when no metadata was exposed. */
|
|
24
|
-
reasoningEffort?: ReasoningEffortId;
|
|
25
|
-
}
|
|
26
|
-
/** Diagnostic observer invoked before streamText returns or throws. */
|
|
27
|
-
export type StreamTextUsageObserver = (observation: StreamTextObservation) => void;
|
|
28
|
-
export interface StreamTextOptions {
|
|
29
|
-
provider: string;
|
|
30
|
-
model: string;
|
|
31
|
-
system: string;
|
|
32
|
-
prompt: string;
|
|
33
|
-
maxTokens?: number;
|
|
34
|
-
signal?: AbortSignal | undefined;
|
|
35
|
-
/** Host session identity forwarded to adapters for request routing. */
|
|
36
|
-
sessionId?: LlmSessionId;
|
|
37
|
-
/**
|
|
38
|
-
* Route A session prefix: session-derived messages sent before the
|
|
39
|
-
* trailing caller message so providers with prompt caching can serve
|
|
40
|
-
* the shared context at cache-read price. Empty/omitted keeps the
|
|
41
|
-
* legacy single-message request.
|
|
42
|
-
*/
|
|
43
|
-
prefixMessages?: readonly Message[];
|
|
44
|
-
/**
|
|
45
|
-
* Observe the provider-reported usage and terminal outcome after the
|
|
46
|
-
* stream settles. The callback runs before success/error return and is
|
|
47
|
-
* diagnostic-only: throwing from it never changes the model call result.
|
|
48
|
-
*/
|
|
49
|
-
onUsage?: StreamTextUsageObserver;
|
|
50
|
-
}
|
|
51
|
-
/** One provider request for a bounded internal agent/tool loop. */
|
|
52
|
-
export interface StreamModelTurnOptions {
|
|
53
|
-
provider: string;
|
|
54
|
-
model: string;
|
|
55
|
-
messages: readonly RequestMessage[];
|
|
56
|
-
/** One-shot system slot. Omit when messages already carry the system prompt. */
|
|
57
|
-
system?: string;
|
|
58
|
-
/** Closed tool schemas for this request; omitted means no tools. */
|
|
59
|
-
tools?: readonly ToolSchema[];
|
|
60
|
-
maxTokens?: number;
|
|
61
|
-
signal?: AbortSignal | undefined;
|
|
62
|
-
/** Host session identity forwarded to adapters for request routing. */
|
|
63
|
-
sessionId?: LlmSessionId;
|
|
64
|
-
/** Require at least one text block; tool-only turns must leave this false. */
|
|
65
|
-
requireText?: boolean;
|
|
66
|
-
/** Require visible text or a tool call; reasoning-only agent turns are empty. */
|
|
67
|
-
requireTextOrToolCall?: boolean;
|
|
68
|
-
/** Same diagnostic observer contract as {@link StreamTextOptions.onUsage}. */
|
|
69
|
-
onUsage?: StreamTextUsageObserver;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Select the lowest reasoning effort an exact model advertises.
|
|
73
|
-
*
|
|
74
|
-
* An enabled effort wins over a declared closing effort. The adapter-preferred
|
|
75
|
-
* first enabled effort is the lowest enabled level it exposes; a closing effort
|
|
76
|
-
* is only a fallback when the model publishes no enabled level. No capability
|
|
77
|
-
* list means no caller-supplied effort.
|
|
78
|
-
*
|
|
79
|
-
* @param modelInfo - exact resolved-model metadata, when the provider exposes it.
|
|
80
|
-
* @returns The selected provider-owned effort id, or `undefined` when unknown.
|
|
81
|
-
*/
|
|
82
|
-
export declare function selectLowestReasoningEffort(modelInfo: Pick<LlmResolvedModelInfo, "reasoning"> | undefined): ReasoningEffortId | undefined;
|
|
83
|
-
/**
|
|
84
|
-
* Stream a single-turn text completion through `ctx.llm`. The request uses the
|
|
85
|
-
* exact provider/model's lowest advertised reasoning effort, omitting the field
|
|
86
|
-
* when the model exposes no reasoning metadata.
|
|
87
|
-
*
|
|
88
|
-
* @returns The concatenated text blocks from the response.
|
|
89
|
-
* @throws On provider error, abort, max-token truncation, or empty output.
|
|
90
|
-
*/
|
|
91
|
-
/**
|
|
92
|
-
* Stream one provider/model turn and return its assembled content blocks.
|
|
93
|
-
* This is the shared primitive for text callers and bounded tool loops; finish
|
|
94
|
-
* state, capability-derived effort, cancellation, and usage observation stay
|
|
95
|
-
* inside this boundary.
|
|
96
|
-
*
|
|
97
|
-
* @param ctx - DSH context providing the LLM runtime.
|
|
98
|
-
* @param opts - Exact route, messages, optional tools, and output requirements.
|
|
99
|
-
* @returns The assembled content blocks; caller decides how to interpret them.
|
|
100
|
-
* @throws On provider error, abort, max-token truncation, or capability lookup failure.
|
|
101
|
-
*/
|
|
102
|
-
export declare function streamModelTurn(ctx: Context, opts: StreamModelTurnOptions): Promise<ContentBlock[]>;
|
|
103
|
-
export declare function streamText(ctx: Context, opts: StreamTextOptions): Promise<string>;
|
|
104
|
-
//# sourceMappingURL=llm-text.d.ts.map
|
package/lib/llm-text.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { BlockAssembler, createUserMessage, } from "@deepseek-ai/dsh-llm";
|
|
2
|
-
import { EVOLVE_MESSAGE_SOURCE } from "./message-source.js";
|
|
3
|
-
/** Carry the host Agent id across the plugin's direct-call boundary. */
|
|
4
|
-
export function asLlmSessionId(value) {
|
|
5
|
-
return value;
|
|
6
|
-
}
|
|
7
|
-
const CLOSED_REASONING_EFFORTS = new Set(["disabled", "off", "none"]);
|
|
8
|
-
/**
|
|
9
|
-
* Select the lowest reasoning effort an exact model advertises.
|
|
10
|
-
*
|
|
11
|
-
* An enabled effort wins over a declared closing effort. The adapter-preferred
|
|
12
|
-
* first enabled effort is the lowest enabled level it exposes; a closing effort
|
|
13
|
-
* is only a fallback when the model publishes no enabled level. No capability
|
|
14
|
-
* list means no caller-supplied effort.
|
|
15
|
-
*
|
|
16
|
-
* @param modelInfo - exact resolved-model metadata, when the provider exposes it.
|
|
17
|
-
* @returns The selected provider-owned effort id, or `undefined` when unknown.
|
|
18
|
-
*/
|
|
19
|
-
export function selectLowestReasoningEffort(modelInfo) {
|
|
20
|
-
const efforts = modelInfo?.reasoning?.efforts;
|
|
21
|
-
if (efforts !== undefined && !Array.isArray(efforts)) {
|
|
22
|
-
throw new Error("evolve: invalid model reasoning metadata");
|
|
23
|
-
}
|
|
24
|
-
if (!efforts || efforts.length === 0)
|
|
25
|
-
return undefined;
|
|
26
|
-
for (const effort of efforts) {
|
|
27
|
-
if (typeof effort?.id !== "string") {
|
|
28
|
-
throw new Error("evolve: invalid model reasoning metadata");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return efforts.find((effort) => !CLOSED_REASONING_EFFORTS.has(effort.id))?.id ?? efforts[0]?.id;
|
|
32
|
-
}
|
|
33
|
-
async function resolveReasoningEffort(ctx, opts) {
|
|
34
|
-
// The current DSH host exposes this method. Keep a capability-less test or
|
|
35
|
-
// older host usable by treating the absent query as unknown metadata.
|
|
36
|
-
if (typeof ctx.llm.resolveModelInfo !== "function")
|
|
37
|
-
return undefined;
|
|
38
|
-
const modelInfo = await ctx.llm.resolveModelInfo(opts.provider, opts.model);
|
|
39
|
-
return selectLowestReasoningEffort(modelInfo);
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Stream a single-turn text completion through `ctx.llm`. The request uses the
|
|
43
|
-
* exact provider/model's lowest advertised reasoning effort, omitting the field
|
|
44
|
-
* when the model exposes no reasoning metadata.
|
|
45
|
-
*
|
|
46
|
-
* @returns The concatenated text blocks from the response.
|
|
47
|
-
* @throws On provider error, abort, max-token truncation, or empty output.
|
|
48
|
-
*/
|
|
49
|
-
/**
|
|
50
|
-
* Stream one provider/model turn and return its assembled content blocks.
|
|
51
|
-
* This is the shared primitive for text callers and bounded tool loops; finish
|
|
52
|
-
* state, capability-derived effort, cancellation, and usage observation stay
|
|
53
|
-
* inside this boundary.
|
|
54
|
-
*
|
|
55
|
-
* @param ctx - DSH context providing the LLM runtime.
|
|
56
|
-
* @param opts - Exact route, messages, optional tools, and output requirements.
|
|
57
|
-
* @returns The assembled content blocks; caller decides how to interpret them.
|
|
58
|
-
* @throws On provider error, abort, max-token truncation, or capability lookup failure.
|
|
59
|
-
*/
|
|
60
|
-
export async function streamModelTurn(ctx, opts) {
|
|
61
|
-
const assembler = new BlockAssembler();
|
|
62
|
-
let outcome = "error";
|
|
63
|
-
let reasoningEffort;
|
|
64
|
-
try {
|
|
65
|
-
reasoningEffort = await resolveReasoningEffort(ctx, opts);
|
|
66
|
-
for await (const chunk of ctx.llm.stream({
|
|
67
|
-
provider: opts.provider,
|
|
68
|
-
model: opts.model,
|
|
69
|
-
messages: [...opts.messages],
|
|
70
|
-
...(opts.system === undefined ? {} : { system: opts.system }),
|
|
71
|
-
...(opts.tools === undefined ? {} : { tools: [...opts.tools] }),
|
|
72
|
-
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
73
|
-
maxTokens: opts.maxTokens ?? 8000,
|
|
74
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
75
|
-
...(opts.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
76
|
-
})) {
|
|
77
|
-
assembler.push(chunk);
|
|
78
|
-
}
|
|
79
|
-
const finish = assembler.finish;
|
|
80
|
-
if (finish.kind === "error") {
|
|
81
|
-
outcome = "error";
|
|
82
|
-
throw new Error(`evolve: LLM call failed: ${finish.failure?.message ?? "unknown"}`);
|
|
83
|
-
}
|
|
84
|
-
if (finish.kind === "aborted") {
|
|
85
|
-
outcome = "aborted";
|
|
86
|
-
throw new Error("evolve: LLM call aborted");
|
|
87
|
-
}
|
|
88
|
-
if (finish.kind === "max-tokens") {
|
|
89
|
-
outcome = "max-tokens";
|
|
90
|
-
throw new Error("evolve: LLM output budget exhausted (max-tokens)");
|
|
91
|
-
}
|
|
92
|
-
const blocks = assembler.blocks();
|
|
93
|
-
const hasText = blocks.some((block) => block.type === "text" && block.text.length > 0);
|
|
94
|
-
const hasToolCall = blocks.some((block) => block.type === "tool-call");
|
|
95
|
-
const missingRequiredOutput = (opts.requireText === true && !hasText)
|
|
96
|
-
|| (opts.requireTextOrToolCall === true && !hasText && !hasToolCall);
|
|
97
|
-
outcome = blocks.length === 0 || missingRequiredOutput ? "empty" : "success";
|
|
98
|
-
return blocks;
|
|
99
|
-
}
|
|
100
|
-
finally {
|
|
101
|
-
try {
|
|
102
|
-
opts.onUsage?.({
|
|
103
|
-
...(assembler.usage ? { usage: assembler.usage } : {}),
|
|
104
|
-
outcome,
|
|
105
|
-
...(reasoningEffort === undefined ? {} : { reasoningEffort }),
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// Usage observation is diagnostic; its failure must not alter the model call.
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
export async function streamText(ctx, opts) {
|
|
114
|
-
const blocks = await streamModelTurn(ctx, {
|
|
115
|
-
provider: opts.provider,
|
|
116
|
-
model: opts.model,
|
|
117
|
-
system: opts.system,
|
|
118
|
-
messages: [
|
|
119
|
-
...(opts.prefixMessages ?? []),
|
|
120
|
-
createUserMessage({
|
|
121
|
-
content: [{ type: "text", text: opts.prompt }],
|
|
122
|
-
source: EVOLVE_MESSAGE_SOURCE,
|
|
123
|
-
}),
|
|
124
|
-
],
|
|
125
|
-
requireText: true,
|
|
126
|
-
...(opts.maxTokens === undefined ? {} : { maxTokens: opts.maxTokens }),
|
|
127
|
-
...(opts.signal ? { signal: opts.signal } : {}),
|
|
128
|
-
...(opts.sessionId ? { sessionId: opts.sessionId } : {}),
|
|
129
|
-
...(opts.onUsage ? { onUsage: opts.onUsage } : {}),
|
|
130
|
-
});
|
|
131
|
-
const text = blocks
|
|
132
|
-
.filter((block) => block.type === "text")
|
|
133
|
-
.map((block) => block.text)
|
|
134
|
-
.join("\n");
|
|
135
|
-
if (text.length === 0) {
|
|
136
|
-
throw new Error("evolve: LLM produced no text output");
|
|
137
|
-
}
|
|
138
|
-
return text;
|
|
139
|
-
}
|
|
140
|
-
//# sourceMappingURL=llm-text.js.map
|
package/lib/logfile.d.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin-owned file logging: a cordis logger exporter that appends every
|
|
3
|
-
* log message — from this plugin or any other — to a JSONL file under the
|
|
4
|
-
* evolve store. Logging becomes a property of the plugin itself: no extra
|
|
5
|
-
* component to install, and it works no matter how `dsh web` is launched
|
|
6
|
-
* (foreground terminal, nohup, restart scripts, ...). The official
|
|
7
|
-
* `cordis-plugin-logger-console` remains an optional add-on for live
|
|
8
|
-
* terminal output; this file exporter is the baseline that always exists.
|
|
9
|
-
*
|
|
10
|
-
* - file: `<baseDir>/evolve/plugin.log` (0600, JSONL)
|
|
11
|
-
* - rotation: when the file exceeds `logMaxBytes` it is renamed to
|
|
12
|
-
* `plugin.log.1` (replacing the previous `.1`)
|
|
13
|
-
* - level: `levels.default` = logLevel (0=error, 1=info, 2=warn, 3=debug)
|
|
14
|
-
* - failure containment: a write error is swallowed — logging never
|
|
15
|
-
* disturbs the agent loop
|
|
16
|
-
*/
|
|
17
|
-
import type { Context } from "@deepseek-ai/cordis";
|
|
18
|
-
/** Name of the plugin log file under `<baseDir>/evolve/`. */
|
|
19
|
-
export declare const PLUGIN_LOG_FILE_NAME = "plugin.log";
|
|
20
|
-
/** Default rotation threshold (5 MiB). */
|
|
21
|
-
export declare const DEFAULT_LOG_MAX_BYTES: number;
|
|
22
|
-
export interface FileLoggerConfig {
|
|
23
|
-
/** 0=error, 1=info, 2=warn, 3=debug. */
|
|
24
|
-
logLevel?: number;
|
|
25
|
-
/** Rotate the log when it exceeds this many bytes. */
|
|
26
|
-
logMaxBytes?: number;
|
|
27
|
-
}
|
|
28
|
-
/** Full path of the plugin log file. */
|
|
29
|
-
export declare function pluginLogFilePath(baseDir: string): string;
|
|
30
|
-
/** JSON-safe rendering of a log message's arguments. */
|
|
31
|
-
export declare function renderArgs(args: readonly unknown[]): unknown[];
|
|
32
|
-
/** One JSONL record as written to the log file. */
|
|
33
|
-
export declare function logRecord(message: {
|
|
34
|
-
ts: number;
|
|
35
|
-
type: string;
|
|
36
|
-
name: string;
|
|
37
|
-
args: readonly unknown[];
|
|
38
|
-
}): string;
|
|
39
|
-
/** Human-readable rendering of one stored JSONL line (unparseable lines pass through). */
|
|
40
|
-
export declare function formatLogLine(line: string): string;
|
|
41
|
-
/**
|
|
42
|
-
* The distinct session ids mentioned in one stored log line, drawn from the
|
|
43
|
-
* rendered message and the raw args (unparseable lines fall back to a raw
|
|
44
|
-
* text scan). Exact-token matching, so `session-abc` never matches
|
|
45
|
-
* `session-abcd`.
|
|
46
|
-
*/
|
|
47
|
-
export declare function sessionIdsInLine(line: string): string[];
|
|
48
|
-
/**
|
|
49
|
-
* Keep only the lines mentioning the given session id (exact token match).
|
|
50
|
-
* An empty/whitespace session id filters everything out — the caller should
|
|
51
|
-
* validate the argument before calling.
|
|
52
|
-
*/
|
|
53
|
-
export declare function filterLogBySession(lines: readonly string[], sessionId: string): string[];
|
|
54
|
-
/** Append one line, rotating the file first when it exceeds maxBytes. */
|
|
55
|
-
export declare function appendOrRotate(path: string, maxBytes: number, line: string): void;
|
|
56
|
-
/**
|
|
57
|
-
* Register the file exporter on the context. Returns the exporter so tests
|
|
58
|
-
* can drive it directly. The exporter is disposed with the plugin's scope.
|
|
59
|
-
*/
|
|
60
|
-
export declare function registerFileLogger(ctx: Context, baseDir: string, config?: FileLoggerConfig): {
|
|
61
|
-
levels: {
|
|
62
|
-
default: number;
|
|
63
|
-
};
|
|
64
|
-
export(message: {
|
|
65
|
-
ts: number;
|
|
66
|
-
type: string;
|
|
67
|
-
name: string;
|
|
68
|
-
args: readonly unknown[];
|
|
69
|
-
}): void;
|
|
70
|
-
};
|
|
71
|
-
//# sourceMappingURL=logfile.d.ts.map
|
package/lib/logfile.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { Logger } from "@deepseek-ai/cordis";
|
|
2
|
-
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
/** Name of the plugin log file under `<baseDir>/evolve/`. */
|
|
5
|
-
export const PLUGIN_LOG_FILE_NAME = "plugin.log";
|
|
6
|
-
/** Default rotation threshold (5 MiB). */
|
|
7
|
-
export const DEFAULT_LOG_MAX_BYTES = 5 * 1024 * 1024;
|
|
8
|
-
/** Full path of the plugin log file. */
|
|
9
|
-
export function pluginLogFilePath(baseDir) {
|
|
10
|
-
return join(baseDir, "evolve", PLUGIN_LOG_FILE_NAME);
|
|
11
|
-
}
|
|
12
|
-
/** JSON-safe rendering of a log message's arguments. */
|
|
13
|
-
export function renderArgs(args) {
|
|
14
|
-
return args.map((arg) => {
|
|
15
|
-
if (arg instanceof Error) {
|
|
16
|
-
return { name: arg.name, message: arg.message, stack: arg.stack };
|
|
17
|
-
}
|
|
18
|
-
if (typeof arg === "object" && arg !== null) {
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(JSON.stringify(arg));
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return String(arg);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return arg;
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/** One JSONL record as written to the log file. */
|
|
30
|
-
export function logRecord(message) {
|
|
31
|
-
// `message` is the printf-rendered text (cordis handles %s/%o/...), so
|
|
32
|
-
// humans and machines both get a usable line; `args` keeps the raw
|
|
33
|
-
// payload for tooling. Errors render as their stack (JSON.stringify of
|
|
34
|
-
// an Error is just {}).
|
|
35
|
-
const rendered = Logger.format({
|
|
36
|
-
formatters: {
|
|
37
|
-
o: (value) => (value instanceof Error ? value.stack ?? value.message : JSON.stringify(value)),
|
|
38
|
-
},
|
|
39
|
-
maxLength: 10240,
|
|
40
|
-
export: () => { },
|
|
41
|
-
}, {
|
|
42
|
-
sn: 0,
|
|
43
|
-
ts: message.ts,
|
|
44
|
-
name: message.name,
|
|
45
|
-
type: message.type,
|
|
46
|
-
level: 0,
|
|
47
|
-
args: message.args,
|
|
48
|
-
});
|
|
49
|
-
return JSON.stringify({
|
|
50
|
-
ts: new Date(message.ts).toISOString(),
|
|
51
|
-
type: message.type,
|
|
52
|
-
name: message.name,
|
|
53
|
-
args: renderArgs(message.args),
|
|
54
|
-
message: rendered,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
/** Human-readable rendering of one stored JSONL line (unparseable lines pass through). */
|
|
58
|
-
export function formatLogLine(line) {
|
|
59
|
-
try {
|
|
60
|
-
const record = JSON.parse(line);
|
|
61
|
-
const ts = record.ts ?? "";
|
|
62
|
-
const type = record.type ? `[${record.type[0]?.toUpperCase() ?? "?"}]` : "[?]";
|
|
63
|
-
const name = record.name ?? "";
|
|
64
|
-
const body = typeof record.message === "string" && record.message.length > 0 ? record.message : "";
|
|
65
|
-
const args = body
|
|
66
|
-
? ""
|
|
67
|
-
: Array.isArray(record.args)
|
|
68
|
-
? record.args.map((arg) => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg))).join(" ")
|
|
69
|
-
: "";
|
|
70
|
-
return `${ts} ${type} ${name} ${body || args}`.trimEnd();
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return line;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
/** A session id as it appears in log text (dsh `session-<hex-uuid>` ids). */
|
|
77
|
-
const SESSION_TOKEN_RE = /\bsession-[0-9a-fA-F-]+\b/g;
|
|
78
|
-
/**
|
|
79
|
-
* The distinct session ids mentioned in one stored log line, drawn from the
|
|
80
|
-
* rendered message and the raw args (unparseable lines fall back to a raw
|
|
81
|
-
* text scan). Exact-token matching, so `session-abc` never matches
|
|
82
|
-
* `session-abcd`.
|
|
83
|
-
*/
|
|
84
|
-
export function sessionIdsInLine(line) {
|
|
85
|
-
const ids = [];
|
|
86
|
-
const collect = (text) => {
|
|
87
|
-
for (const match of text.matchAll(SESSION_TOKEN_RE)) {
|
|
88
|
-
ids.push(match[0]);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
try {
|
|
92
|
-
const record = JSON.parse(line);
|
|
93
|
-
if (typeof record.message === "string")
|
|
94
|
-
collect(record.message);
|
|
95
|
-
if (Array.isArray(record.args)) {
|
|
96
|
-
for (const arg of record.args) {
|
|
97
|
-
collect(typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
collect(line);
|
|
103
|
-
}
|
|
104
|
-
return [...new Set(ids)];
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Keep only the lines mentioning the given session id (exact token match).
|
|
108
|
-
* An empty/whitespace session id filters everything out — the caller should
|
|
109
|
-
* validate the argument before calling.
|
|
110
|
-
*/
|
|
111
|
-
export function filterLogBySession(lines, sessionId) {
|
|
112
|
-
const needle = sessionId.trim();
|
|
113
|
-
if (!needle) {
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
return lines.filter((line) => sessionIdsInLine(line).includes(needle));
|
|
117
|
-
}
|
|
118
|
-
/** Append one line, rotating the file first when it exceeds maxBytes. */
|
|
119
|
-
export function appendOrRotate(path, maxBytes, line) {
|
|
120
|
-
const dir = dirname(path);
|
|
121
|
-
if (!existsSync(path)) {
|
|
122
|
-
mkdirSync(dir, { recursive: true });
|
|
123
|
-
// 0600: the log can carry session context and must not be world-readable
|
|
124
|
-
writeFileSync(path, "", { encoding: "utf8", mode: 0o600 });
|
|
125
|
-
}
|
|
126
|
-
if (maxBytes > 0) {
|
|
127
|
-
try {
|
|
128
|
-
if (statSync(path).size > maxBytes) {
|
|
129
|
-
renameSync(path, `${path}.1`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
// stat/rename failures fall through to the append attempt
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
appendFileSync(path, `${line}\n`, "utf8");
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Register the file exporter on the context. Returns the exporter so tests
|
|
140
|
-
* can drive it directly. The exporter is disposed with the plugin's scope.
|
|
141
|
-
*/
|
|
142
|
-
export function registerFileLogger(ctx, baseDir, config = {}) {
|
|
143
|
-
const path = pluginLogFilePath(baseDir);
|
|
144
|
-
const maxBytes = config.logMaxBytes ?? DEFAULT_LOG_MAX_BYTES;
|
|
145
|
-
const exporter = {
|
|
146
|
-
levels: { default: config.logLevel ?? 1 },
|
|
147
|
-
export(message) {
|
|
148
|
-
try {
|
|
149
|
-
appendOrRotate(path, maxBytes, logRecord(message));
|
|
150
|
-
}
|
|
151
|
-
catch {
|
|
152
|
-
// logging must never disturb the agent loop
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
ctx.logger.exporter(exporter);
|
|
157
|
-
return exporter;
|
|
158
|
-
}
|
|
159
|
-
//# sourceMappingURL=logfile.js.map
|