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.
Files changed (154) hide show
  1. package/README.md +35 -115
  2. package/README.zh.md +26 -93
  3. package/cordis.patch.yml +2 -2
  4. package/lib/index.d.ts +44 -225
  5. package/lib/index.js +30 -239
  6. package/lib/memory-guide.d.ts +13 -14
  7. package/lib/memory-guide.js +32 -21
  8. package/lib/memory-section.d.ts +77 -0
  9. package/lib/memory-section.js +189 -0
  10. package/package.json +2 -17
  11. package/lib/apply.d.ts +0 -22
  12. package/lib/apply.js +0 -157
  13. package/lib/approval.d.ts +0 -73
  14. package/lib/approval.js +0 -127
  15. package/lib/audit-log.d.ts +0 -19
  16. package/lib/audit-log.js +0 -52
  17. package/lib/auto.d.ts +0 -236
  18. package/lib/auto.js +0 -773
  19. package/lib/autocase.d.ts +0 -38
  20. package/lib/autocase.js +0 -76
  21. package/lib/benchmark-command.d.ts +0 -9
  22. package/lib/benchmark-command.js +0 -357
  23. package/lib/benchmark.d.ts +0 -151
  24. package/lib/benchmark.js +0 -250
  25. package/lib/command-util.d.ts +0 -70
  26. package/lib/command-util.js +0 -123
  27. package/lib/command.d.ts +0 -13
  28. package/lib/command.js +0 -134
  29. package/lib/consolidate.d.ts +0 -90
  30. package/lib/consolidate.js +0 -180
  31. package/lib/copy.d.ts +0 -37
  32. package/lib/copy.js +0 -58
  33. package/lib/declines.d.ts +0 -103
  34. package/lib/declines.js +0 -179
  35. package/lib/evaluate.d.ts +0 -74
  36. package/lib/evaluate.js +0 -271
  37. package/lib/evolve-event.d.ts +0 -40
  38. package/lib/evolve-event.js +0 -60
  39. package/lib/extraction-phase.d.ts +0 -132
  40. package/lib/extraction-phase.js +0 -236
  41. package/lib/failures.d.ts +0 -42
  42. package/lib/failures.js +0 -171
  43. package/lib/fate.d.ts +0 -130
  44. package/lib/fate.js +0 -350
  45. package/lib/goal-command.d.ts +0 -7
  46. package/lib/goal-command.js +0 -37
  47. package/lib/goal.d.ts +0 -72
  48. package/lib/goal.js +0 -72
  49. package/lib/inject.d.ts +0 -90
  50. package/lib/inject.js +0 -164
  51. package/lib/injection-caps.d.ts +0 -4
  52. package/lib/injection-caps.js +0 -4
  53. package/lib/injection-directory.d.ts +0 -29
  54. package/lib/injection-directory.js +0 -78
  55. package/lib/injection-rank.d.ts +0 -85
  56. package/lib/injection-rank.js +0 -161
  57. package/lib/listener.d.ts +0 -45
  58. package/lib/listener.js +0 -199
  59. package/lib/llm-text.d.ts +0 -104
  60. package/lib/llm-text.js +0 -140
  61. package/lib/logfile.d.ts +0 -71
  62. package/lib/logfile.js +0 -159
  63. package/lib/maintenance-commands.d.ts +0 -23
  64. package/lib/maintenance-commands.js +0 -136
  65. package/lib/memory-agent.d.ts +0 -93
  66. package/lib/memory-agent.js +0 -300
  67. package/lib/memory-apply.d.ts +0 -44
  68. package/lib/memory-apply.js +0 -202
  69. package/lib/memory-benchmark.d.ts +0 -76
  70. package/lib/memory-benchmark.js +0 -170
  71. package/lib/memory-index.d.ts +0 -114
  72. package/lib/memory-index.js +0 -197
  73. package/lib/memory-manifest.d.ts +0 -16
  74. package/lib/memory-manifest.js +0 -70
  75. package/lib/message-source.d.ts +0 -23
  76. package/lib/message-source.js +0 -12
  77. package/lib/mount-command.d.ts +0 -10
  78. package/lib/mount-command.js +0 -48
  79. package/lib/mount.d.ts +0 -42
  80. package/lib/mount.js +0 -212
  81. package/lib/notify.d.ts +0 -48
  82. package/lib/notify.js +0 -57
  83. package/lib/plan-command.d.ts +0 -12
  84. package/lib/plan-command.js +0 -38
  85. package/lib/plan.d.ts +0 -16
  86. package/lib/plan.js +0 -126
  87. package/lib/planner.d.ts +0 -61
  88. package/lib/planner.js +0 -144
  89. package/lib/pool.d.ts +0 -7
  90. package/lib/pool.js +0 -25
  91. package/lib/prefix-cache.d.ts +0 -79
  92. package/lib/prefix-cache.js +0 -168
  93. package/lib/project.d.ts +0 -28
  94. package/lib/project.js +0 -66
  95. package/lib/projection.d.ts +0 -22
  96. package/lib/projection.js +0 -112
  97. package/lib/promotion.d.ts +0 -94
  98. package/lib/promotion.js +0 -171
  99. package/lib/recall.d.ts +0 -85
  100. package/lib/recall.js +0 -142
  101. package/lib/record-language.d.ts +0 -85
  102. package/lib/record-language.js +0 -152
  103. package/lib/render.d.ts +0 -13
  104. package/lib/render.js +0 -80
  105. package/lib/review-scheduler.d.ts +0 -38
  106. package/lib/review-scheduler.js +0 -183
  107. package/lib/review.d.ts +0 -60
  108. package/lib/review.js +0 -145
  109. package/lib/rollback.d.ts +0 -9
  110. package/lib/rollback.js +0 -68
  111. package/lib/rubric.d.ts +0 -29
  112. package/lib/rubric.js +0 -119
  113. package/lib/runtime-commands.d.ts +0 -14
  114. package/lib/runtime-commands.js +0 -115
  115. package/lib/runtime.d.ts +0 -24
  116. package/lib/runtime.js +0 -87
  117. package/lib/score.d.ts +0 -64
  118. package/lib/score.js +0 -191
  119. package/lib/search.d.ts +0 -83
  120. package/lib/search.js +0 -136
  121. package/lib/service.d.ts +0 -63
  122. package/lib/service.js +0 -253
  123. package/lib/skill-render.d.ts +0 -23
  124. package/lib/skill-render.js +0 -69
  125. package/lib/skill.d.ts +0 -15
  126. package/lib/skill.js +0 -80
  127. package/lib/skillquality.d.ts +0 -85
  128. package/lib/skillquality.js +0 -320
  129. package/lib/source.d.ts +0 -30
  130. package/lib/source.js +0 -43
  131. package/lib/state.d.ts +0 -26
  132. package/lib/state.js +0 -151
  133. package/lib/store.d.ts +0 -61
  134. package/lib/store.js +0 -164
  135. package/lib/token-usage.d.ts +0 -68
  136. package/lib/token-usage.js +0 -211
  137. package/lib/tool.d.ts +0 -34
  138. package/lib/tool.js +0 -320
  139. package/lib/turn-snapshot.d.ts +0 -78
  140. package/lib/turn-snapshot.js +0 -252
  141. package/lib/types.d.ts +0 -218
  142. package/lib/types.js +0 -133
  143. package/lib/usage.d.ts +0 -36
  144. package/lib/usage.js +0 -98
  145. package/lib/validate.d.ts +0 -28
  146. package/lib/validate.js +0 -181
  147. package/lib/wrapup-candidates.d.ts +0 -127
  148. package/lib/wrapup-candidates.js +0 -161
  149. package/lib/wrapup-command.d.ts +0 -10
  150. package/lib/wrapup-command.js +0 -243
  151. package/lib/wrapup-proposals.d.ts +0 -101
  152. package/lib/wrapup-proposals.js +0 -281
  153. package/lib/wrapup.d.ts +0 -56
  154. 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