dsh-continual-evolve 0.11.0 → 0.12.1

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 (161) hide show
  1. package/README.md +42 -115
  2. package/README.zh.md +33 -93
  3. package/client/client.js +253 -0
  4. package/cordis.patch.yml +2 -2
  5. package/lib/card-routes.d.ts +32 -0
  6. package/lib/card-routes.js +92 -0
  7. package/lib/index.d.ts +65 -217
  8. package/lib/index.js +74 -234
  9. package/lib/known-workspaces.d.ts +22 -0
  10. package/lib/known-workspaces.js +42 -0
  11. package/lib/memory-guide.d.ts +13 -14
  12. package/lib/memory-guide.js +34 -21
  13. package/lib/memory-section.d.ts +77 -0
  14. package/lib/memory-section.js +189 -0
  15. package/lib/memory-snapshot.d.ts +54 -0
  16. package/lib/memory-snapshot.js +125 -0
  17. package/package.json +8 -17
  18. package/lib/apply.d.ts +0 -22
  19. package/lib/apply.js +0 -157
  20. package/lib/approval.d.ts +0 -73
  21. package/lib/approval.js +0 -127
  22. package/lib/audit-log.d.ts +0 -19
  23. package/lib/audit-log.js +0 -52
  24. package/lib/auto.d.ts +0 -236
  25. package/lib/auto.js +0 -773
  26. package/lib/autocase.d.ts +0 -38
  27. package/lib/autocase.js +0 -76
  28. package/lib/benchmark-command.d.ts +0 -9
  29. package/lib/benchmark-command.js +0 -357
  30. package/lib/benchmark.d.ts +0 -151
  31. package/lib/benchmark.js +0 -250
  32. package/lib/command-util.d.ts +0 -70
  33. package/lib/command-util.js +0 -123
  34. package/lib/command.d.ts +0 -13
  35. package/lib/command.js +0 -134
  36. package/lib/consolidate.d.ts +0 -90
  37. package/lib/consolidate.js +0 -180
  38. package/lib/copy.d.ts +0 -37
  39. package/lib/copy.js +0 -58
  40. package/lib/declines.d.ts +0 -103
  41. package/lib/declines.js +0 -179
  42. package/lib/evaluate.d.ts +0 -74
  43. package/lib/evaluate.js +0 -271
  44. package/lib/evolve-event.d.ts +0 -40
  45. package/lib/evolve-event.js +0 -60
  46. package/lib/extraction-phase.d.ts +0 -132
  47. package/lib/extraction-phase.js +0 -236
  48. package/lib/failures.d.ts +0 -42
  49. package/lib/failures.js +0 -171
  50. package/lib/fate.d.ts +0 -130
  51. package/lib/fate.js +0 -350
  52. package/lib/goal-command.d.ts +0 -7
  53. package/lib/goal-command.js +0 -37
  54. package/lib/goal.d.ts +0 -72
  55. package/lib/goal.js +0 -72
  56. package/lib/inject.d.ts +0 -90
  57. package/lib/inject.js +0 -164
  58. package/lib/injection-caps.d.ts +0 -4
  59. package/lib/injection-caps.js +0 -4
  60. package/lib/injection-directory.d.ts +0 -29
  61. package/lib/injection-directory.js +0 -78
  62. package/lib/injection-rank.d.ts +0 -85
  63. package/lib/injection-rank.js +0 -161
  64. package/lib/listener.d.ts +0 -45
  65. package/lib/listener.js +0 -199
  66. package/lib/llm-text.d.ts +0 -104
  67. package/lib/llm-text.js +0 -140
  68. package/lib/logfile.d.ts +0 -71
  69. package/lib/logfile.js +0 -159
  70. package/lib/maintenance-commands.d.ts +0 -23
  71. package/lib/maintenance-commands.js +0 -136
  72. package/lib/memory-agent.d.ts +0 -93
  73. package/lib/memory-agent.js +0 -300
  74. package/lib/memory-apply.d.ts +0 -44
  75. package/lib/memory-apply.js +0 -202
  76. package/lib/memory-benchmark.d.ts +0 -76
  77. package/lib/memory-benchmark.js +0 -170
  78. package/lib/memory-index.d.ts +0 -114
  79. package/lib/memory-index.js +0 -197
  80. package/lib/memory-manifest.d.ts +0 -16
  81. package/lib/memory-manifest.js +0 -70
  82. package/lib/message-source.d.ts +0 -23
  83. package/lib/message-source.js +0 -12
  84. package/lib/mount-command.d.ts +0 -10
  85. package/lib/mount-command.js +0 -48
  86. package/lib/mount.d.ts +0 -42
  87. package/lib/mount.js +0 -212
  88. package/lib/notify.d.ts +0 -48
  89. package/lib/notify.js +0 -57
  90. package/lib/plan-command.d.ts +0 -12
  91. package/lib/plan-command.js +0 -38
  92. package/lib/plan.d.ts +0 -16
  93. package/lib/plan.js +0 -126
  94. package/lib/planner.d.ts +0 -61
  95. package/lib/planner.js +0 -144
  96. package/lib/pool.d.ts +0 -7
  97. package/lib/pool.js +0 -25
  98. package/lib/prefix-cache.d.ts +0 -79
  99. package/lib/prefix-cache.js +0 -168
  100. package/lib/project.d.ts +0 -28
  101. package/lib/project.js +0 -66
  102. package/lib/projection.d.ts +0 -22
  103. package/lib/projection.js +0 -112
  104. package/lib/promotion.d.ts +0 -94
  105. package/lib/promotion.js +0 -171
  106. package/lib/recall.d.ts +0 -85
  107. package/lib/recall.js +0 -142
  108. package/lib/record-language.d.ts +0 -85
  109. package/lib/record-language.js +0 -152
  110. package/lib/render.d.ts +0 -13
  111. package/lib/render.js +0 -80
  112. package/lib/review-scheduler.d.ts +0 -38
  113. package/lib/review-scheduler.js +0 -183
  114. package/lib/review.d.ts +0 -60
  115. package/lib/review.js +0 -145
  116. package/lib/rollback.d.ts +0 -9
  117. package/lib/rollback.js +0 -68
  118. package/lib/rubric.d.ts +0 -29
  119. package/lib/rubric.js +0 -119
  120. package/lib/runtime-commands.d.ts +0 -14
  121. package/lib/runtime-commands.js +0 -115
  122. package/lib/runtime.d.ts +0 -24
  123. package/lib/runtime.js +0 -87
  124. package/lib/score.d.ts +0 -64
  125. package/lib/score.js +0 -191
  126. package/lib/search.d.ts +0 -83
  127. package/lib/search.js +0 -136
  128. package/lib/service.d.ts +0 -63
  129. package/lib/service.js +0 -253
  130. package/lib/skill-render.d.ts +0 -23
  131. package/lib/skill-render.js +0 -69
  132. package/lib/skill.d.ts +0 -15
  133. package/lib/skill.js +0 -80
  134. package/lib/skillquality.d.ts +0 -85
  135. package/lib/skillquality.js +0 -320
  136. package/lib/source.d.ts +0 -30
  137. package/lib/source.js +0 -43
  138. package/lib/state.d.ts +0 -26
  139. package/lib/state.js +0 -151
  140. package/lib/store.d.ts +0 -61
  141. package/lib/store.js +0 -164
  142. package/lib/token-usage.d.ts +0 -68
  143. package/lib/token-usage.js +0 -211
  144. package/lib/tool.d.ts +0 -34
  145. package/lib/tool.js +0 -320
  146. package/lib/turn-snapshot.d.ts +0 -78
  147. package/lib/turn-snapshot.js +0 -252
  148. package/lib/types.d.ts +0 -218
  149. package/lib/types.js +0 -133
  150. package/lib/usage.d.ts +0 -36
  151. package/lib/usage.js +0 -98
  152. package/lib/validate.d.ts +0 -28
  153. package/lib/validate.js +0 -181
  154. package/lib/wrapup-candidates.d.ts +0 -127
  155. package/lib/wrapup-candidates.js +0 -161
  156. package/lib/wrapup-command.d.ts +0 -10
  157. package/lib/wrapup-command.js +0 -243
  158. package/lib/wrapup-proposals.d.ts +0 -101
  159. package/lib/wrapup-proposals.js +0 -281
  160. package/lib/wrapup.d.ts +0 -56
  161. package/lib/wrapup.js +0 -105
package/lib/store.d.ts DELETED
@@ -1,61 +0,0 @@
1
- import type { HarnessScope, RefinementResult } from "./types.js";
2
- export declare const EVOLVE_DIR = "evolve";
3
- /**
4
- * Storage-hygiene defaults (#20): history used to grow append-only without
5
- * bound (dozens of full-state snapshot copies, unbounded JSONL). Retention
6
- * is count-based and generous — rollback only ever needs recent snapshots
7
- * and readers (`failures.ts` over reviews) need a working window, not the
8
- * full past. Tunable via the `historyRetain` plugin config.
9
- */
10
- export declare const DEFAULT_SNAPSHOT_RETAIN = 20;
11
- export { DEFAULT_REFINEMENTS_RETAIN, DEFAULT_REVIEWS_RETAIN } from "./types.js";
12
- export declare const DEFAULT_TOKEN_USAGE_RETAIN = 500;
13
- /** Resolved retention set: how many snapshots / JSONL tail lines to keep. */
14
- export interface HistoryRetention {
15
- /** Full-state snapshots kept per store (oldest pruned first). */
16
- snapshots: number;
17
- /** Tail lines kept in each store's refinements.jsonl. */
18
- refinements: number;
19
- /** Tail lines kept in the shared reviews.jsonl audit trail. */
20
- reviews: number;
21
- /** Tail lines kept in the direct-call token-usage.jsonl ledger. */
22
- tokenUsage: number;
23
- }
24
- /** Defaults applied for absent / non-positive retention fields (fail loud never: clamp, don't throw). */
25
- export declare function resolveHistoryRetention(raw?: Partial<HistoryRetention>): HistoryRetention;
26
- export interface StorePaths {
27
- /** Directory holding harness_state.json. */
28
- stateDir: string;
29
- /** Directory holding snapshots for this store. */
30
- snapshotsDir: string;
31
- /** JSONL path for applied refinement results. */
32
- resultsPath: string;
33
- }
34
- /**
35
- * Resolve store paths for a scope. For `project` the `sessionId` parameter
36
- * carries the project key (see {@link resolveProjectKey} in project.ts) —
37
- * it is sanitized to a single path segment so an externally supplied key
38
- * can never traverse out of `evolve/projects/`.
39
- */
40
- export declare function storePaths(baseDir: string, scope: HarnessScope, sessionId?: string): StorePaths;
41
- /** Snapshot the current state file before a mutation, if one exists. */
42
- export declare function snapshotBefore(paths: StorePaths, refinementId: string): void;
43
- /** Append an applied result to the store's JSONL history. */
44
- export declare function appendResult(paths: StorePaths, result: RefinementResult): void;
45
- /**
46
- * Prune a store's snapshots to the newest `retain` files (by mtime, oldest
47
- * first). Best-effort: a missing directory or an unlink failure never breaks
48
- * the apply path — the next apply retries.
49
- */
50
- export declare function pruneSnapshots(snapshotsDir: string, retain: number): void;
51
- /**
52
- * Truncate a JSONL file to its last `retain` non-empty lines. No-op when the
53
- * file is missing or already within budget — the common case costs one
54
- * read and no write.
55
- */
56
- export declare function pruneJsonlFile(path: string, retain: number): void;
57
- /** Reviews audit-trail path shared by the gate, fate, and tool events. */
58
- export declare function reviewsPath(baseDir: string): string;
59
- /** Read the applied results history; malformed lines are skipped, never fatal. */
60
- export declare function loadResults(paths: StorePaths): RefinementResult[];
61
- //# sourceMappingURL=store.d.ts.map
package/lib/store.js DELETED
@@ -1,164 +0,0 @@
1
- /**
2
- * Store layout and snapshot discipline for the evolution plugin.
3
- *
4
- * Layout (self-contained under the DSH home; no dependency on session
5
- * persistence internals):
6
- *
7
- * <dshHome>/evolve/global/harness_state.json cross-project store
8
- * <dshHome>/evolve/global/refinements.jsonl applied results (rollback source)
9
- * <dshHome>/evolve/projects/<slug-hash>/... per-project store
10
- * <dshHome>/evolve/local/<sessionId>/... per-session staging store
11
- *
12
- * Snapshot discipline is code-enforced: before any mutating apply, the
13
- * pre-apply state is copied to `snapshots/<refinementId>.json`. The model has
14
- * no way to skip it — it runs inside the service, not in a prompt.
15
- */
16
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
17
- import { DEFAULT_REFINEMENTS_RETAIN, DEFAULT_REVIEWS_RETAIN } from "./types.js";
18
- import { join } from "node:path";
19
- import { sanitizeProjectKey } from "./project.js";
20
- import { stateFilePath } from "./state.js";
21
- export const EVOLVE_DIR = "evolve";
22
- /**
23
- * Storage-hygiene defaults (#20): history used to grow append-only without
24
- * bound (dozens of full-state snapshot copies, unbounded JSONL). Retention
25
- * is count-based and generous — rollback only ever needs recent snapshots
26
- * and readers (`failures.ts` over reviews) need a working window, not the
27
- * full past. Tunable via the `historyRetain` plugin config.
28
- */
29
- export const DEFAULT_SNAPSHOT_RETAIN = 20;
30
- export { DEFAULT_REFINEMENTS_RETAIN, DEFAULT_REVIEWS_RETAIN } from "./types.js";
31
- export const DEFAULT_TOKEN_USAGE_RETAIN = 500;
32
- /** Defaults applied for absent / non-positive retention fields (fail loud never: clamp, don't throw). */
33
- export function resolveHistoryRetention(raw) {
34
- const pick = (value, fallback) => typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
35
- return {
36
- snapshots: pick(raw?.snapshots, DEFAULT_SNAPSHOT_RETAIN),
37
- refinements: pick(raw?.refinements, DEFAULT_REFINEMENTS_RETAIN),
38
- reviews: pick(raw?.reviews, DEFAULT_REVIEWS_RETAIN),
39
- tokenUsage: pick(raw?.tokenUsage, DEFAULT_TOKEN_USAGE_RETAIN),
40
- };
41
- }
42
- /**
43
- * Resolve store paths for a scope. For `project` the `sessionId` parameter
44
- * carries the project key (see {@link resolveProjectKey} in project.ts) —
45
- * it is sanitized to a single path segment so an externally supplied key
46
- * can never traverse out of `evolve/projects/`.
47
- */
48
- export function storePaths(baseDir, scope, sessionId) {
49
- const scopeDir = scope === "global" ? "global" : scope === "project" ? join("projects", sanitizeProjectKey(sessionId ?? "project")) : join("local", sessionId ?? "anonymous");
50
- const stateDir = join(baseDir, EVOLVE_DIR, scopeDir);
51
- return {
52
- stateDir,
53
- snapshotsDir: join(stateDir, "snapshots"),
54
- resultsPath: join(stateDir, "refinements.jsonl"),
55
- };
56
- }
57
- /** Snapshot the current state file before a mutation, if one exists. */
58
- export function snapshotBefore(paths, refinementId) {
59
- const statePath = stateFilePath(paths.stateDir);
60
- if (!existsSync(statePath)) {
61
- return;
62
- }
63
- mkdirSync(paths.snapshotsDir, { recursive: true });
64
- // 0600: the snapshot is a full copy of harness_state.json — same
65
- // permission discipline as the store itself (review audit 2026-08-28 S5).
66
- writeFileSync(join(paths.snapshotsDir, `${refinementId}.json`), readFileSync(statePath, "utf8"), { encoding: "utf8", mode: 0o600 });
67
- }
68
- /** Append an applied result to the store's JSONL history. */
69
- export function appendResult(paths, result) {
70
- mkdirSync(paths.stateDir, { recursive: true });
71
- writeFileSync(paths.resultsPath, `${JSON.stringify(result)}\n`, { encoding: "utf8", flag: "a" });
72
- }
73
- /**
74
- * Prune a store's snapshots to the newest `retain` files (by mtime, oldest
75
- * first). Best-effort: a missing directory or an unlink failure never breaks
76
- * the apply path — the next apply retries.
77
- */
78
- export function pruneSnapshots(snapshotsDir, retain) {
79
- const keep = Math.floor(retain);
80
- if (!(keep > 0) || !existsSync(snapshotsDir))
81
- return;
82
- let files;
83
- try {
84
- files = readdirSync(snapshotsDir).filter((f) => f.endsWith(".json"));
85
- }
86
- catch {
87
- return; // unreadable directory — leave it for the next attempt
88
- }
89
- if (files.length <= keep)
90
- return;
91
- const withTime = [];
92
- for (const file of files) {
93
- try {
94
- withTime.push({ file, mtime: statSync(join(snapshotsDir, file)).mtimeMs });
95
- }
96
- catch {
97
- // unstatable file — keep it rather than delete blindly
98
- }
99
- }
100
- withTime.sort((a, b) => a.mtime - b.mtime);
101
- for (const victim of withTime.slice(0, Math.max(0, withTime.length - keep))) {
102
- try {
103
- unlinkSync(join(snapshotsDir, victim.file));
104
- }
105
- catch {
106
- // one bad unlink must not stop the sweep or the apply path
107
- }
108
- }
109
- }
110
- /**
111
- * Truncate a JSONL file to its last `retain` non-empty lines. No-op when the
112
- * file is missing or already within budget — the common case costs one
113
- * read and no write.
114
- */
115
- export function pruneJsonlFile(path, retain) {
116
- const keep = Math.floor(retain);
117
- if (!(keep > 0) || !existsSync(path))
118
- return;
119
- let lines;
120
- try {
121
- lines = readFileSync(path, "utf8").split("\n").filter((l) => l.trim().length > 0);
122
- }
123
- catch {
124
- return; // unreadable file — leave it for the next attempt
125
- }
126
- if (lines.length <= keep)
127
- return;
128
- try {
129
- writeFileSync(path, `${lines.slice(-keep).join("\n")}\n`, "utf8");
130
- }
131
- catch {
132
- // truncation failure must not break the write path that called it
133
- }
134
- }
135
- /** Reviews audit-trail path shared by the gate, fate, and tool events. */
136
- export function reviewsPath(baseDir) {
137
- return join(baseDir, EVOLVE_DIR, "reviews.jsonl");
138
- }
139
- /** Read the applied results history; malformed lines are skipped, never fatal. */
140
- export function loadResults(paths) {
141
- if (!existsSync(paths.resultsPath)) {
142
- return [];
143
- }
144
- const results = [];
145
- for (const line of readFileSync(paths.resultsPath, "utf8").split("\n")) {
146
- const trimmed = line.trim();
147
- if (!trimmed)
148
- continue;
149
- try {
150
- const parsed = JSON.parse(trimmed);
151
- if (isResult(parsed)) {
152
- results.push(parsed);
153
- }
154
- }
155
- catch {
156
- // skip malformed line
157
- }
158
- }
159
- return results;
160
- }
161
- function isResult(data) {
162
- return typeof data === "object" && data !== null && "id" in data && "appliedEdits" in data;
163
- }
164
- //# sourceMappingURL=store.js.map
@@ -1,68 +0,0 @@
1
- import type { TokenUsage } from "@deepseek-ai/dsh-llm";
2
- import type { StreamTextObservation, StreamTextUsageObserver } from "./llm-text.js";
3
- export declare const TOKEN_USAGE_LEDGER_VERSION = 1;
4
- /** Plugin-owned direct call paths represented in the ledger. */
5
- export declare const TOKEN_USAGE_PHASES: readonly ["review", "memory", "planner", "wrapup", "fate"];
6
- export type TokenUsagePhase = (typeof TOKEN_USAGE_PHASES)[number];
7
- /** Whether the provider supplied a usage sample; missing is never treated as zero. */
8
- export type TokenUsageStatus = "reported" | "missing";
9
- /** How the exact full-call total was obtained. */
10
- export type TokenTotalSource = "provider" | "derived" | "unavailable";
11
- /** One retained direct-call observation. */
12
- export interface TokenUsageRecord {
13
- version: typeof TOKEN_USAGE_LEDGER_VERSION;
14
- timestamp: string;
15
- sessionId: string;
16
- phase: TokenUsagePhase;
17
- provider: string;
18
- model: string;
19
- outcome: StreamTextObservation["outcome"];
20
- usageStatus: TokenUsageStatus;
21
- /** Absent exactly when {@link usageStatus} is `missing`. */
22
- usage?: TokenUsage;
23
- totalSource: TokenTotalSource;
24
- }
25
- /** Caller-owned destination and failure hook; phase/provider/model are supplied by the call site. */
26
- export interface TokenUsageTarget {
27
- baseDir: string;
28
- sessionId: string;
29
- retain: number;
30
- onError?: (cause: unknown) => void;
31
- }
32
- /** Loaded retained window plus corrupt-line count for honest coverage reporting. */
33
- export interface LoadedTokenUsage {
34
- records: TokenUsageRecord[];
35
- corruptLines: number;
36
- }
37
- /** Aggregate provider-reported buckets over a retained ledger window. */
38
- export interface TokenUsageTotals {
39
- calls: number;
40
- reportedCalls: number;
41
- missingUsageCalls: number;
42
- uncachedInputTokens: number;
43
- cacheReadTokens: number;
44
- cacheWriteTokens: number;
45
- outputTokens: number;
46
- reasoningTokens: number;
47
- totalTokens: number;
48
- }
49
- /** Absolute path of the direct-call token ledger. */
50
- export declare function tokenUsagePath(baseDir: string): string;
51
- /** Exact full-call total, with the old-peer fallback only when `totalTokens` is absent. */
52
- export declare function exactTokenTotal(usage: TokenUsage): number;
53
- /** Build the observer attached to one `streamText` call. Errors are diagnostic-only. */
54
- export declare function createTokenUsageObserver(target: TokenUsageTarget, phase: TokenUsagePhase, provider: string, model: string): StreamTextUsageObserver;
55
- /**
56
- * Append one call and enforce the dedicated tail budget. Append + conditional
57
- * prune stay in one synchronous block, so concurrent async callers in one DSH
58
- * process cannot interleave the read/truncate/write. Multiple DSH processes
59
- * sharing one baseDir are outside the plugin's storage contract.
60
- */
61
- export declare function appendTokenUsage(target: TokenUsageTarget, record: TokenUsageRecord): void;
62
- /** Load valid records from the retained ledger; malformed lines never break `/evolve usage`. */
63
- export declare function loadTokenUsage(baseDir: string): LoadedTokenUsage;
64
- /** Sum exact provider-reported buckets without double-counting reasoning or cache fields. */
65
- export declare function aggregateTokenUsage(records: readonly TokenUsageRecord[]): TokenUsageTotals;
66
- /** Render the human-facing direct-call ledger section. */
67
- export declare function renderTokenUsageReport(ledger: LoadedTokenUsage, retain: number): string[];
68
- //# sourceMappingURL=token-usage.d.ts.map
@@ -1,211 +0,0 @@
1
- /**
2
- * Provider-reported token accounting for plugin-owned direct LLM calls.
3
- *
4
- * The review gate, planner, dedicated memory agent, wrap-up command, and
5
- * local-fate classifier all use the shared model-call boundary. These calls do
6
- * not belong to an Agent turn (they pass no sessionId), so the host session
7
- * token-meter cannot attribute them. This ledger records the exact
8
- * `TokenUsage` emitted by the adapter at the one shared stream boundary.
9
- *
10
- * Storage: `<baseDir>/evolve/token-usage.jsonl`. The file contains route and
11
- * count metadata only — never prompts, responses, or tool arguments — and is
12
- * trimmed to its dedicated retention budget after each synchronous append.
13
- */
14
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
- import { join } from "node:path";
16
- import { EVOLVE_DIR, pruneJsonlFile } from "./store.js";
17
- const TOKEN_USAGE_FILE = "token-usage.jsonl";
18
- export const TOKEN_USAGE_LEDGER_VERSION = 1;
19
- /** Plugin-owned direct call paths represented in the ledger. */
20
- export const TOKEN_USAGE_PHASES = ["review", "memory", "planner", "wrapup", "fate"];
21
- /** Absolute path of the direct-call token ledger. */
22
- export function tokenUsagePath(baseDir) {
23
- return join(baseDir, EVOLVE_DIR, TOKEN_USAGE_FILE);
24
- }
25
- /** Exact full-call total, with the old-peer fallback only when `totalTokens` is absent. */
26
- export function exactTokenTotal(usage) {
27
- if (usage.totalTokens !== undefined)
28
- return usage.totalTokens;
29
- return usage.inputTokens + usage.outputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
30
- }
31
- /** Build the observer attached to one `streamText` call. Errors are diagnostic-only. */
32
- export function createTokenUsageObserver(target, phase, provider, model) {
33
- return (observation) => {
34
- try {
35
- appendTokenUsage(target, {
36
- version: TOKEN_USAGE_LEDGER_VERSION,
37
- timestamp: new Date().toISOString(),
38
- sessionId: target.sessionId,
39
- phase,
40
- provider,
41
- model,
42
- outcome: observation.outcome,
43
- usageStatus: observation.usage ? "reported" : "missing",
44
- ...(observation.usage ? { usage: observation.usage } : {}),
45
- totalSource: observation.usage
46
- ? observation.usage.totalTokens !== undefined
47
- ? "provider"
48
- : "derived"
49
- : "unavailable",
50
- });
51
- }
52
- catch (cause) {
53
- target.onError?.(cause);
54
- }
55
- };
56
- }
57
- /**
58
- * Append one call and enforce the dedicated tail budget. Append + conditional
59
- * prune stay in one synchronous block, so concurrent async callers in one DSH
60
- * process cannot interleave the read/truncate/write. Multiple DSH processes
61
- * sharing one baseDir are outside the plugin's storage contract.
62
- */
63
- export function appendTokenUsage(target, record) {
64
- if (!isTokenUsageRecord(record)) {
65
- throw new Error("evolve: refusing to append an invalid token-usage record");
66
- }
67
- const dir = join(target.baseDir, EVOLVE_DIR);
68
- mkdirSync(dir, { recursive: true, mode: 0o700 });
69
- writeFileSync(tokenUsagePath(target.baseDir), `${JSON.stringify(record)}\n`, {
70
- encoding: "utf8",
71
- flag: "a",
72
- mode: 0o600,
73
- });
74
- pruneJsonlFile(tokenUsagePath(target.baseDir), target.retain);
75
- }
76
- /** Load valid records from the retained ledger; malformed lines never break `/evolve usage`. */
77
- export function loadTokenUsage(baseDir) {
78
- const path = tokenUsagePath(baseDir);
79
- if (!existsSync(path))
80
- return { records: [], corruptLines: 0 };
81
- let lines;
82
- try {
83
- lines = readFileSync(path, "utf8").split("\n");
84
- }
85
- catch {
86
- return { records: [], corruptLines: 0 };
87
- }
88
- const records = [];
89
- let corruptLines = 0;
90
- for (const line of lines) {
91
- if (line.trim().length === 0)
92
- continue;
93
- try {
94
- const parsed = JSON.parse(line);
95
- if (isTokenUsageRecord(parsed))
96
- records.push(parsed);
97
- else
98
- corruptLines += 1;
99
- }
100
- catch {
101
- corruptLines += 1;
102
- }
103
- }
104
- return { records, corruptLines };
105
- }
106
- /** Sum exact provider-reported buckets without double-counting reasoning or cache fields. */
107
- export function aggregateTokenUsage(records) {
108
- const totals = {
109
- calls: records.length,
110
- reportedCalls: 0,
111
- missingUsageCalls: 0,
112
- uncachedInputTokens: 0,
113
- cacheReadTokens: 0,
114
- cacheWriteTokens: 0,
115
- outputTokens: 0,
116
- reasoningTokens: 0,
117
- totalTokens: 0,
118
- };
119
- for (const record of records) {
120
- if (record.usageStatus === "missing" || !record.usage) {
121
- totals.missingUsageCalls += 1;
122
- continue;
123
- }
124
- totals.reportedCalls += 1;
125
- totals.uncachedInputTokens += record.usage.inputTokens;
126
- totals.cacheReadTokens += record.usage.cacheReadTokens ?? 0;
127
- totals.cacheWriteTokens += record.usage.cacheWriteTokens ?? 0;
128
- totals.outputTokens += record.usage.outputTokens;
129
- totals.reasoningTokens += record.usage.reasoningTokens ?? 0;
130
- totals.totalTokens += exactTokenTotal(record.usage);
131
- }
132
- return totals;
133
- }
134
- /** Render the human-facing direct-call ledger section. */
135
- export function renderTokenUsageReport(ledger, retain) {
136
- const { records, corruptLines } = ledger;
137
- if (records.length === 0) {
138
- const corrupt = corruptLines > 0 ? ` ${corruptLines} corrupt line(s) ignored.` : "";
139
- return [
140
- `direct LLM token usage: no valid calls in the retained tail.${corrupt}`,
141
- `window: last ${retain} call(s), not a lifetime total.`,
142
- "scope: review/memory/planner/wrapup/fate only; host benchmark subagents, their agent-loop calls, and per-entry injection cost are excluded.",
143
- ];
144
- }
145
- const totals = aggregateTokenUsage(records);
146
- const lines = [
147
- `direct LLM token usage: ${totals.calls} valid calls (${totals.reportedCalls} reported usage, ${totals.missingUsageCalls} missing)${corruptLines > 0 ? `; ${corruptLines} corrupt line(s) ignored` : ""}`,
148
- ` uncached input: ${totals.uncachedInputTokens}`,
149
- ` cache read: ${totals.cacheReadTokens}`,
150
- ` cache write: ${totals.cacheWriteTokens}`,
151
- ` output: ${totals.outputTokens}`,
152
- ` reasoning: ${totals.reasoningTokens}${records.some((record) => record.usageStatus === "reported" && record.usage?.reasoningTokens === undefined) ? " (partial provider reports)" : ""}`,
153
- ` exact total: ${totals.totalTokens} (reported calls only)`,
154
- `window: last ${retain} call(s), not a lifetime total.`,
155
- "by phase:",
156
- ];
157
- for (const phase of TOKEN_USAGE_PHASES) {
158
- const phaseRecords = records.filter((record) => record.phase === phase);
159
- if (phaseRecords.length === 0)
160
- continue;
161
- const phaseTotals = aggregateTokenUsage(phaseRecords);
162
- lines.push(` ${phase.padEnd(7)} ${phaseTotals.calls} calls · ${phaseTotals.totalTokens} tokens`);
163
- }
164
- const last = records.at(-1);
165
- if (last) {
166
- const lastTotal = last.usage ? exactTokenTotal(last.usage) : "usage unavailable";
167
- lines.push(`last: ${last.phase} ${last.provider}/${last.model} · ${last.outcome} · ${lastTotal} tokens · ${last.timestamp}`);
168
- }
169
- lines.push("scope: review/memory/planner/wrapup/fate only; host benchmark subagents, their agent-loop calls, and per-entry injection cost are excluded.");
170
- return lines;
171
- }
172
- function isTokenUsageRecord(value) {
173
- if (typeof value !== "object" || value === null || Array.isArray(value))
174
- return false;
175
- const record = value;
176
- if (record["version"] !== TOKEN_USAGE_LEDGER_VERSION ||
177
- typeof record["timestamp"] !== "string" ||
178
- typeof record["sessionId"] !== "string" ||
179
- typeof record["provider"] !== "string" ||
180
- typeof record["model"] !== "string" ||
181
- typeof record["outcome"] !== "string" ||
182
- !TOKEN_USAGE_PHASES.includes(record["phase"]) ||
183
- !["success", "max-tokens", "error", "aborted", "empty"].includes(record["outcome"]) ||
184
- !["reported", "missing"].includes(record["usageStatus"]) ||
185
- !["provider", "derived", "unavailable"].includes(record["totalSource"])) {
186
- return false;
187
- }
188
- if (record["usageStatus"] === "missing") {
189
- return record["usage"] === undefined && record["totalSource"] === "unavailable";
190
- }
191
- if (!isTokenUsage(record["usage"]))
192
- return false;
193
- const expectedSource = record["usage"]["totalTokens"] !== undefined ? "provider" : "derived";
194
- return record["totalSource"] === expectedSource;
195
- }
196
- function isCount(value) {
197
- return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
198
- }
199
- function isTokenUsage(value) {
200
- if (typeof value !== "object" || value === null || Array.isArray(value))
201
- return false;
202
- const usage = value;
203
- if (!isCount(usage["inputTokens"]) || !isCount(usage["outputTokens"]))
204
- return false;
205
- for (const key of ["totalTokens", "cacheReadTokens", "cacheWriteTokens", "reasoningTokens"]) {
206
- if (usage[key] !== undefined && !isCount(usage[key]))
207
- return false;
208
- }
209
- return true;
210
- }
211
- //# sourceMappingURL=token-usage.js.map
package/lib/tool.d.ts DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * Model-facing evolve_* tools. The model supplies content; every guarantee
3
- * (validation, snapshot, versioning, history, rollback) is code-enforced in
4
- * the engine. `global: true` is required explicitly for cross-session edits.
5
- */
6
- import type { Context } from "@deepseek-ai/cordis";
7
- import type { RecordLanguagePreference } from "./record-language.js";
8
- import type { HarnessScope } from "./types.js";
9
- import type { EvolutionEngine } from "./service.js";
10
- /**
11
- * Accept the string form (`scope: "project"`) with the legacy boolean tool
12
- * parameter (`global: true`) as fallback. `scope` wins when both are given.
13
- */
14
- export declare function scopeOf(value: unknown, fallback: HarnessScope): HarnessScope;
15
- /**
16
- * Collect the target ids for an evolve_delete call: legacy single `id`
17
- * plus the batch `ids` array, de-duplicated in first-seen order. Throws
18
- * loudly when neither carries an id — a silent no-op delete would look
19
- * like success while deleting nothing.
20
- *
21
- * @throws when both `id` and `ids` are absent or empty.
22
- */
23
- export declare function collectDeleteIds(id: unknown, ids: unknown): string[];
24
- export interface ToolGateOptions {
25
- requireGlobalApproval: boolean;
26
- /**
27
- * Plugin `recordLanguage` preference for the approval dialog copy;
28
- * `auto`/absent defers to the durable client preference and the session's
29
- * own user text (see `approval.ts`).
30
- */
31
- recordLanguage?: RecordLanguagePreference;
32
- }
33
- export declare function registerEvolveTools(ctx: Context, engine: EvolutionEngine, opts: ToolGateOptions): void;
34
- //# sourceMappingURL=tool.d.ts.map