dsh-mnemon 0.5.42 → 0.5.43
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/lib/bundle.js +1 -1
- package/lib/client.js +139 -78
- package/lib/extension-sdk.js +39 -1
- package/lib/index.js +550 -96
- package/lib/{starter-resolution-B5mdKlvf.js → starter-resolution-CTY14Exj.js} +7 -7
- package/lib/starter.js +1 -1
- package/lib/types/src/client/locales.d.ts +10 -0
- package/lib/types/src/host/protocol.d.ts +5 -0
- package/lib/types/src/sdk/index.d.ts +1 -0
- package/lib/types/src/sdk/projection-reservation.d.ts +3 -0
- package/package.json +9 -8
package/lib/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import z from "@deepseek-ai/schemastery";
|
|
|
7
7
|
import { basename, delimiter, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
|
|
8
8
|
import { resolveEmbedding, resolvePersistenceStrategy, resolveRecallQuality } from "dsh-mnemon-source-memory-spaces";
|
|
9
9
|
import { CATEGORIES, DEFAULT_EMBEDDING_ENDPOINT, DEFAULT_EMBEDDING_MODEL, DEFAULT_EMBEDDING_PROTOCOL, EDGE_TYPES, MEMORY_PLACEMENT_CAPABILITIES, MEMORY_PROVIDER_REGISTRY_FILE, MEMORY_SPACES_DIRECTORY, MEMORY_SPACE_DATABASE_FILE, MEMORY_SPACE_ID_PATTERN, MEMORY_SPACE_NAME_MAX_LENGTH, MEMORY_SPACE_REGISTRY_FILE, MNEMON_ACTIVE_STORE_FILE, MNEMON_EMBEDDING_PROTOCOLS, SOURCES, parseMemorySpaceRegistry, summarizeMemoryProviderRegistry, summarizeMemorySpaceRegistry } from "dsh-mnemon-source-memory-spaces/contracts";
|
|
10
|
-
import { RUNTIME_ARCHIVE_DIRECTORY, RUNTIME_DIRECTORY, RUNTIME_ENTRY_DELIMITER, RUNTIME_ENTRY_MAX_BYTES, RUNTIME_LOCK_FILE, RUNTIME_LOCK_STALE_MS, RUNTIME_MEMORIES_FILE, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_LIMIT_MAX_BYTES, RUNTIME_MEMORY_VERSION, RUNTIME_PROJECTION_FILES, parseRuntimeBranches, runtimeBytes, runtimeProjection, summarizeRuntimeMemories } from "dsh-mnemon-source-runtime/contracts";
|
|
10
|
+
import { RUNTIME_ARCHIVE_DIRECTORY, RUNTIME_DIRECTORY, RUNTIME_ENTRY_DELIMITER, RUNTIME_ENTRY_MAX_BYTES, RUNTIME_LOCK_FILE, RUNTIME_LOCK_STALE_MS, RUNTIME_MEMORIES_FILE, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_LIMIT_MAX_BYTES, RUNTIME_MEMORY_VERSION, RUNTIME_PROJECTION_FILES, assertRuntimePinnedBudget, parseRuntimeBranches, runtimeBytes, runtimeProjection, summarizeRuntimeMemories } from "dsh-mnemon-source-runtime/contracts";
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import fs, { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
13
13
|
import { homedir, hostname } from "node:os";
|
|
@@ -23,6 +23,8 @@ import { Agent, request as request$1 } from "node:https";
|
|
|
23
23
|
import { connect } from "node:tls";
|
|
24
24
|
import { readFile, stat } from "node:fs/promises";
|
|
25
25
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
26
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
27
|
+
import * as SkillsContracts from "dsh-mnemon-source-skills/contracts";
|
|
26
28
|
import { z as z$1 } from "zod";
|
|
27
29
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
28
30
|
import { isVolatile } from "@deepseek-ai/cosmokit";
|
|
@@ -389,6 +391,7 @@ const DEFAULT_SKILLS_CONFIG = Object.freeze({
|
|
|
389
391
|
allowUpdate: true,
|
|
390
392
|
targetScope: "user",
|
|
391
393
|
autoBacktest: true,
|
|
394
|
+
autoAdopt: false,
|
|
392
395
|
draftMaxTokens: 32768,
|
|
393
396
|
draftRetryMaxTokens: 65536,
|
|
394
397
|
hints: true,
|
|
@@ -483,6 +486,7 @@ const SkillsConfig = z.object({
|
|
|
483
486
|
"project"
|
|
484
487
|
]).default(DEFAULT_SKILLS_CONFIG.targetScope),
|
|
485
488
|
autoBacktest: z.boolean().default(DEFAULT_SKILLS_CONFIG.autoBacktest),
|
|
489
|
+
autoAdopt: z.boolean().default(DEFAULT_SKILLS_CONFIG.autoAdopt),
|
|
486
490
|
draftMaxTokens: z.number().step(1).min(128).max(131072).default(DEFAULT_SKILLS_CONFIG.draftMaxTokens),
|
|
487
491
|
draftRetryMaxTokens: z.number().step(1).min(128).max(131072).default(DEFAULT_SKILLS_CONFIG.draftRetryMaxTokens),
|
|
488
492
|
hints: z.boolean().default(DEFAULT_SKILLS_CONFIG.hints),
|
|
@@ -539,6 +543,7 @@ const RecallQualitySchema = z.object({
|
|
|
539
543
|
maxUnknownResults: z.number().step(1).min(0).max(50).default(DEFAULT_RECALL_MAX_UNKNOWN_RESULTS)
|
|
540
544
|
});
|
|
541
545
|
const RuntimeMemorySchema = z.object({
|
|
546
|
+
inheritToSubagents: z.boolean().default(false),
|
|
542
547
|
memoryLimitBytes: z.number().step(1).min(1).max(MAX_RUNTIME_MEMORY_LIMIT_BYTES).default(DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES),
|
|
543
548
|
userLimitBytes: z.number().step(1).min(1).max(MAX_RUNTIME_MEMORY_LIMIT_BYTES).default(DEFAULT_RUNTIME_USER_LIMIT_BYTES),
|
|
544
549
|
maintenanceMaxTokens: z.number().step(1).min(1).max(MAX_RUNTIME_MAINTENANCE_MAX_TOKENS).default(DEFAULT_RUNTIME_MAINTENANCE_MAX_TOKENS)
|
|
@@ -595,6 +600,7 @@ const Config = z.object({
|
|
|
595
600
|
timeoutMs: z.number().step(1).min(100).max(12e4).default(DEFAULT_TIMEOUT_MS),
|
|
596
601
|
defaultRecallLimit: z.number().step(1).min(1).max(50).default(10),
|
|
597
602
|
runtimeMemory: RuntimeMemorySchema.default({
|
|
603
|
+
inheritToSubagents: false,
|
|
598
604
|
memoryLimitBytes: DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES,
|
|
599
605
|
userLimitBytes: DEFAULT_RUNTIME_USER_LIMIT_BYTES,
|
|
600
606
|
maintenanceMaxTokens: DEFAULT_RUNTIME_MAINTENANCE_MAX_TOKENS
|
|
@@ -719,6 +725,8 @@ function resolveTaskAgentModel(value) {
|
|
|
719
725
|
};
|
|
720
726
|
}
|
|
721
727
|
function resolveRuntimeMemory(value) {
|
|
728
|
+
const inheritToSubagents = value?.inheritToSubagents ?? false;
|
|
729
|
+
if (typeof inheritToSubagents !== "boolean") throw new Error("dsh-mnemon: Runtime inheritToSubagents must be a boolean");
|
|
722
730
|
const memoryLimitBytes = value?.memoryLimitBytes ?? DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES;
|
|
723
731
|
const userLimitBytes = value?.userLimitBytes ?? DEFAULT_RUNTIME_USER_LIMIT_BYTES;
|
|
724
732
|
const maintenanceMaxTokens = value?.maintenanceMaxTokens ?? 8192;
|
|
@@ -726,6 +734,7 @@ function resolveRuntimeMemory(value) {
|
|
|
726
734
|
if (!Number.isInteger(userLimitBytes) || userLimitBytes < 1 || userLimitBytes > MAX_RUNTIME_MEMORY_LIMIT_BYTES) throw new Error(`dsh-mnemon: Runtime USER.md limit must be an integer within 1..${MAX_RUNTIME_MEMORY_LIMIT_BYTES} bytes`);
|
|
727
735
|
if (!Number.isInteger(maintenanceMaxTokens) || maintenanceMaxTokens < 1 || maintenanceMaxTokens > 1e6) throw new Error(`dsh-mnemon: Runtime maintenance maxTokens must be an integer within 1..${MAX_RUNTIME_MAINTENANCE_MAX_TOKENS}`);
|
|
728
736
|
return {
|
|
737
|
+
inheritToSubagents,
|
|
729
738
|
memoryLimitBytes,
|
|
730
739
|
userLimitBytes,
|
|
731
740
|
maintenanceMaxTokens
|
|
@@ -2250,6 +2259,7 @@ function parseRuntime(value, limits = RUNTIME_MEMORY_LIMITS) {
|
|
|
2250
2259
|
"low"
|
|
2251
2260
|
].includes(String(entry.importance))) throw new Error("runtime memories.json contains an invalid entry");
|
|
2252
2261
|
if (typeof entry.created_at !== "string" || typeof entry.updated_at !== "string") throw new Error("runtime memories.json contains invalid timestamps");
|
|
2262
|
+
if (entry.pinned !== void 0 && typeof entry.pinned !== "boolean") throw new Error("runtime memories.json contains invalid pin state");
|
|
2253
2263
|
const content = entry.content.trim().replace(/\s+/gu, " ");
|
|
2254
2264
|
if (content === "" || content.includes("§") || Buffer.byteLength(content, "utf8") > RUNTIME_ENTRY_MAX_BYTES) throw new Error("runtime memories.json contains invalid content");
|
|
2255
2265
|
const branches = entry.target === "memory" ? parseRuntimeBranches(entry.branches) : void 0;
|
|
@@ -2260,10 +2270,12 @@ function parseRuntime(value, limits = RUNTIME_MEMORY_LIMITS) {
|
|
|
2260
2270
|
importance: entry.importance,
|
|
2261
2271
|
created_at: entry.created_at,
|
|
2262
2272
|
updated_at: entry.updated_at,
|
|
2273
|
+
...entry.pinned === void 0 ? {} : { pinned: entry.pinned },
|
|
2263
2274
|
...branches === void 0 ? {} : { branches },
|
|
2264
2275
|
...origin === void 0 ? {} : { origin }
|
|
2265
2276
|
};
|
|
2266
2277
|
});
|
|
2278
|
+
assertRuntimePinnedBudget(entries);
|
|
2267
2279
|
for (const target of ["user", "memory"]) {
|
|
2268
2280
|
const used = runtimeBytes(entries, target);
|
|
2269
2281
|
if (used > limits[target]) throw new Error(`runtime ${target} memory holds ${used} bytes and exceeds its ${limits[target]} byte limit`);
|
|
@@ -2476,6 +2488,7 @@ function archiveRuntimeOverflow(stagedRuntime, entries) {
|
|
|
2476
2488
|
}
|
|
2477
2489
|
/** Retain the same hot set on both machines, without asking a model to rewrite evidence. */
|
|
2478
2490
|
function fitMergedRuntime(entries, limits) {
|
|
2491
|
+
if (runtimeBytes(entries.filter((entry) => entry.pinned === true), "memory") > limits.memory) throw new Error("Pinned runtime entries exceed the merge capacity. Unpin entries or increase the capacity; no data was changed.");
|
|
2479
2492
|
if (runtimeBytes(entries, "memory") <= limits.memory) return {
|
|
2480
2493
|
entries,
|
|
2481
2494
|
archived: []
|
|
@@ -2486,7 +2499,7 @@ function fitMergedRuntime(entries, limits) {
|
|
|
2486
2499
|
low: 0
|
|
2487
2500
|
};
|
|
2488
2501
|
const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
2489
|
-
const ranked = entries.filter((entry) => entry.target === "memory").sort((left, right) => importance[right.importance] - importance[left.importance] || compare(right.updated_at, left.updated_at) || compare(entryKey(left), entryKey(right)));
|
|
2502
|
+
const ranked = entries.filter((entry) => entry.target === "memory").sort((left, right) => Number(right.pinned === true) - Number(left.pinned === true) || importance[right.importance] - importance[left.importance] || compare(right.updated_at, left.updated_at) || compare(entryKey(left), entryKey(right)));
|
|
2490
2503
|
const kept = [];
|
|
2491
2504
|
const archived = [];
|
|
2492
2505
|
for (const entry of ranked) if (runtimeBytes([...kept, entry], "memory") <= limits.memory) kept.push(entry);
|
|
@@ -2835,7 +2848,14 @@ function mergeRuntime(root, pack, runtimeLimits, options = {}) {
|
|
|
2835
2848
|
}
|
|
2836
2849
|
for (const tombstone of hidden) if (!revived.includes(tombstone)) revived.push(tombstone);
|
|
2837
2850
|
}
|
|
2838
|
-
if (keys.has(key))
|
|
2851
|
+
if (keys.has(key)) {
|
|
2852
|
+
const existing = entries.find((candidate) => entryKey(candidate) === key);
|
|
2853
|
+
if (entry.pinned !== void 0 && (existing.pinned === void 0 || entry.updated_at > existing.updated_at)) {
|
|
2854
|
+
existing.pinned = entry.pinned;
|
|
2855
|
+
if (entry.updated_at > existing.updated_at) existing.updated_at = entry.updated_at;
|
|
2856
|
+
}
|
|
2857
|
+
continue;
|
|
2858
|
+
}
|
|
2839
2859
|
keys.add(key);
|
|
2840
2860
|
entries.push(entry);
|
|
2841
2861
|
incomingKeys.add(key);
|
|
@@ -9672,7 +9692,11 @@ function runtimeRouteChunks(entries) {
|
|
|
9672
9692
|
function compactedBudget(plan) {
|
|
9673
9693
|
const pendingBytes = plan.pending === void 0 ? 0 : Buffer.byteLength(plan.pending.content, "utf8");
|
|
9674
9694
|
const separatorBytes = plan.pending === void 0 || plan.entries.length === 0 ? 0 : Buffer.byteLength(RUNTIME_ENTRY_DELIMITER, "utf8");
|
|
9675
|
-
|
|
9695
|
+
const pinned = plan.entries.filter((entry) => entry.pinned === true);
|
|
9696
|
+
const pinnedBytes = Buffer.byteLength(pinned.map((entry) => entry.content).join(RUNTIME_ENTRY_DELIMITER), "utf8");
|
|
9697
|
+
const available = plan.limit - pendingBytes - separatorBytes;
|
|
9698
|
+
if ((pinned.length > 0 || plan.pending?.pinned === true) && pinnedBytes > available) throw new Error("Pinned runtime entries leave no capacity for this write. Unpin entries or increase the capacity; no archive writes were attempted.");
|
|
9699
|
+
return Math.max(pinnedBytes, Math.max(0, Math.floor(plan.limit * .7) - pendingBytes - separatorBytes));
|
|
9676
9700
|
}
|
|
9677
9701
|
function runtimeMigrationSources(revision, entries) {
|
|
9678
9702
|
return entries.map((entry, offset) => ({
|
|
@@ -9721,10 +9745,11 @@ async function archiveRuntimeLocally(counters, context, request, plan, reason, s
|
|
|
9721
9745
|
if (plan.localArchive !== true) throw new Error(`runtime memory archival found no Memory Space that can take the archive (${reason}), and this Runtime Source cannot archive locally; pending write was not committed and existing runtime entries are unchanged; update dsh-mnemon-source-runtime, activate a supported Memory Space, or increase runtimeMemory.memoryLimitBytes`);
|
|
9722
9746
|
context.assertWritable?.();
|
|
9723
9747
|
const runId = `host-${randomUUID()}`;
|
|
9724
|
-
const compacted = plan.entries.map(({ content, importance, branches }) => ({
|
|
9748
|
+
const compacted = plan.entries.map(({ content, importance, branches, pinned }) => ({
|
|
9725
9749
|
content,
|
|
9726
9750
|
importance,
|
|
9727
|
-
...branches === void 0 ? {} : { branches }
|
|
9751
|
+
...branches === void 0 ? {} : { branches },
|
|
9752
|
+
...pinned === void 0 ? {} : { pinned }
|
|
9728
9753
|
}));
|
|
9729
9754
|
const mutation = await context.runtime.mutateResult("compact-and-mutate", {
|
|
9730
9755
|
revision: plan.revision,
|
|
@@ -9767,7 +9792,9 @@ async function runtimeLocked(counters, context, request, signal) {
|
|
|
9767
9792
|
if (context.expectedRevision !== void 0 && context.expectedRevision !== plan.revision) throw new Error("Runtime source revision conflict before capacity maintenance");
|
|
9768
9793
|
if (!plan.requiresMaintenance) return context.commit();
|
|
9769
9794
|
if (plan.entries.length === 0) throw new Error("runtime memory capacity was exceeded without entries available for maintenance");
|
|
9795
|
+
compactedBudget(plan);
|
|
9770
9796
|
if (request.target === "user") return compactUserAndCommit(context, request, plan, signal);
|
|
9797
|
+
const archiveEntries = plan.entries.filter((entry) => entry.pinned !== true);
|
|
9771
9798
|
const archive = await context.memorySpaces();
|
|
9772
9799
|
if (archive === void 0) return archiveRuntimeLocally(counters, context, request, plan, "no Memory Space takes automatic writes here", signal);
|
|
9773
9800
|
const memoryService = archive.source;
|
|
@@ -9802,11 +9829,11 @@ async function runtimeLocked(counters, context, request, signal) {
|
|
|
9802
9829
|
let provider = "host";
|
|
9803
9830
|
let runId = `host-${randomUUID()}`;
|
|
9804
9831
|
let summary = "Routed every entry to the only eligible Memory Space without model work.";
|
|
9805
|
-
if (eligibleBodies.length === 1) for (const index of
|
|
9832
|
+
if (eligibleBodies.length === 1) for (const index of archiveEntries.keys()) routed.set(index + 1, eligibleBodies[0].id);
|
|
9806
9833
|
else {
|
|
9807
9834
|
const summaries = [];
|
|
9808
9835
|
const fallbackBody = eligibleBodies.find((body) => body.mnemonDefault) ?? eligibleBodies[0];
|
|
9809
|
-
const chunks = runtimeRouteChunks(
|
|
9836
|
+
const chunks = runtimeRouteChunks(archiveEntries);
|
|
9810
9837
|
for (const [chunkIndex, chunk] of chunks.entries()) {
|
|
9811
9838
|
const prompt = `Route this bounded MEMORY.md archive batch now. The host retains and writes the exact source content; these excerpts exist only for destination selection.
|
|
9812
9839
|
|
|
@@ -9851,11 +9878,12 @@ ${chunk.context}
|
|
|
9851
9878
|
}
|
|
9852
9879
|
summary = summaries.join(" ");
|
|
9853
9880
|
}
|
|
9854
|
-
if (routed.size !==
|
|
9855
|
-
const compactedEntries = plan.entries.map(({ content, importance, branches }) => ({
|
|
9881
|
+
if (routed.size !== archiveEntries.length) throw new Error("runtime memory migration omitted committed archive sources");
|
|
9882
|
+
const compactedEntries = plan.entries.map(({ content, importance, branches, pinned }) => ({
|
|
9856
9883
|
content,
|
|
9857
9884
|
importance,
|
|
9858
|
-
...branches === void 0 ? {} : { branches }
|
|
9885
|
+
...branches === void 0 ? {} : { branches },
|
|
9886
|
+
...pinned === void 0 ? {} : { pinned }
|
|
9859
9887
|
}));
|
|
9860
9888
|
if ((await runtimeMemory.read("maintenance-plan", {
|
|
9861
9889
|
...request,
|
|
@@ -9865,9 +9893,9 @@ ${chunk.context}
|
|
|
9865
9893
|
for (const memoryBodyId of new Set(routed.values())) if (!destinations.items.some((body) => body.id === memoryBodyId && eligible(body))) throw new Error(`runtime archive Memory Space ${memoryBodyId} is no longer eligible; no archive writes were attempted`);
|
|
9866
9894
|
signal.throwIfAborted();
|
|
9867
9895
|
context.assertWritable?.();
|
|
9868
|
-
const sources = runtimeMigrationSources(plan.revision,
|
|
9896
|
+
const sources = runtimeMigrationSources(plan.revision, archiveEntries);
|
|
9869
9897
|
const requests = sources.map((source) => {
|
|
9870
|
-
const entry =
|
|
9898
|
+
const entry = archiveEntries[source.index - 1];
|
|
9871
9899
|
return {
|
|
9872
9900
|
content: entry.content,
|
|
9873
9901
|
category: "context",
|
|
@@ -9910,7 +9938,7 @@ ${chunk.context}
|
|
|
9910
9938
|
archiveResults.set(source.index, result);
|
|
9911
9939
|
}
|
|
9912
9940
|
if (written.length !== batch.length) throw new Error("runtime archive batch did not return one receipt per source entry");
|
|
9913
|
-
for (const source of batch) verified.set(source.index, await archiveRuntimeEntry(memoryService, memoryBodyId,
|
|
9941
|
+
for (const source of batch) verified.set(source.index, await archiveRuntimeEntry(memoryService, memoryBodyId, archiveEntries[source.index - 1], archiveResults.get(source.index), signal));
|
|
9914
9942
|
}
|
|
9915
9943
|
const lineage = [];
|
|
9916
9944
|
const memoryBodyIds = /* @__PURE__ */ new Set();
|
|
@@ -9977,11 +10005,13 @@ ${chunk.context}
|
|
|
9977
10005
|
async function compactUserAndCommit(context, request, plan, signal) {
|
|
9978
10006
|
const runtimeMemory = context.runtime;
|
|
9979
10007
|
const budget = compactedBudget(plan);
|
|
10008
|
+
const pinned = plan.entries.filter((entry) => entry.pinned === true);
|
|
10009
|
+
const mutable = plan.entries.filter((entry) => entry.pinned !== true);
|
|
9980
10010
|
const prompt = `Run local USER.md compaction now.
|
|
9981
10011
|
Pending mutation (uncommitted; do not include in compaction):
|
|
9982
10012
|
${pendingMutationContext(plan)}
|
|
9983
10013
|
|
|
9984
|
-
${runtimeSnapshotContext("user",
|
|
10014
|
+
${runtimeSnapshotContext("user", mutable)}`;
|
|
9985
10015
|
const { provider, runId, result } = await context.model("compaction", "Consolidate local user profile", prompt, USER_COMPACTION_SCHEMA, USER_COMPACTION_PERSONA);
|
|
9986
10016
|
const value = object$6(result.structured);
|
|
9987
10017
|
if (value.action !== "compacted") throw new Error(typeof value.summary === "string" && value.summary !== "" ? value.summary : "USER.md compaction failed");
|
|
@@ -10010,17 +10040,21 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
|
|
|
10010
10040
|
if (entry.sourceIndexes.length === 0) throw new Error("USER.md compaction returned an entry without a source");
|
|
10011
10041
|
let requiredRank = 0;
|
|
10012
10042
|
for (const index of entry.sourceIndexes) {
|
|
10013
|
-
if (index < 1 || index >
|
|
10043
|
+
if (index < 1 || index > mutable.length || seen.has(index)) throw new Error("USER.md compaction source coverage is invalid");
|
|
10014
10044
|
seen.add(index);
|
|
10015
|
-
requiredRank = Math.max(requiredRank, importanceRank[
|
|
10045
|
+
requiredRank = Math.max(requiredRank, importanceRank[mutable[index - 1].importance]);
|
|
10016
10046
|
}
|
|
10017
10047
|
if (importanceRank[entry.importance] < requiredRank) throw new Error("USER.md compaction lowered source importance");
|
|
10018
10048
|
}
|
|
10019
|
-
if (seen.size !==
|
|
10020
|
-
const candidates =
|
|
10049
|
+
if (seen.size !== mutable.length) throw new Error("USER.md compaction omitted committed entries");
|
|
10050
|
+
const candidates = [...pinned.map((entry) => ({
|
|
10051
|
+
content: entry.content,
|
|
10052
|
+
importance: entry.importance,
|
|
10053
|
+
pinned: true
|
|
10054
|
+
})), ...compactedEntries.map(({ content, importance }) => ({
|
|
10021
10055
|
content,
|
|
10022
10056
|
importance
|
|
10023
|
-
}));
|
|
10057
|
+
}))];
|
|
10024
10058
|
const candidateBytes = Buffer.byteLength(candidates.map((entry) => entry.content.trim().replace(/\s+/gu, " ")).join(RUNTIME_ENTRY_DELIMITER), "utf8");
|
|
10025
10059
|
if (candidateBytes > budget) throw new Error(`USER.md compaction did not fit the host budget (${candidateBytes} > ${budget} bytes)`);
|
|
10026
10060
|
signal.throwIfAborted();
|
|
@@ -10357,9 +10391,16 @@ const ACTION_TOOLS = namedBindings("action");
|
|
|
10357
10391
|
const NAMED_SOURCE_TYPES = [.../* @__PURE__ */ new Set([...Object.keys(ROUTE_TOOLS), ...Object.keys(ACTION_TOOLS)])];
|
|
10358
10392
|
/** Use a short availability list only where the Host has the matching named binding.
|
|
10359
10393
|
* Unbound external operations retain exact ids and schemas in the generic envelope. */
|
|
10360
|
-
function modelMemoryWake(graph, turn) {
|
|
10394
|
+
function modelMemoryWake(graph, turn, includeRuntime = true) {
|
|
10361
10395
|
const generation = graph.memoryComposition.generation(turn.view.runtimeGeneration);
|
|
10362
10396
|
if (generation === void 0) throw new Error("Cannot present an unpinned Memory generation");
|
|
10397
|
+
if (!includeRuntime) {
|
|
10398
|
+
const runtimeSources = new Set(generation.sourceInstances().filter((source) => source.sourceTypeId === "runtime").map((source) => source.sourceInstanceKey));
|
|
10399
|
+
return createMemoryWake({
|
|
10400
|
+
...turn.view,
|
|
10401
|
+
projection: turn.view.projection.filter((fragment) => !runtimeSources.has(fragment.sourceInstanceKey))
|
|
10402
|
+
}, memoryWakeBindings(generation, turn.view));
|
|
10403
|
+
}
|
|
10363
10404
|
return graph.composableTurns.memoryWake(turn.view.id, memoryWakeBindings(generation, turn.view));
|
|
10364
10405
|
}
|
|
10365
10406
|
function presentMemoryWake(generation, view) {
|
|
@@ -10485,17 +10526,23 @@ var AgentMemoryTurn = class {
|
|
|
10485
10526
|
lastInspection;
|
|
10486
10527
|
lastWorkspace;
|
|
10487
10528
|
releaseDelegation;
|
|
10529
|
+
includeRuntime;
|
|
10488
10530
|
constructor(agent, runtime, delegation, independent = false) {
|
|
10489
10531
|
this.agent = agent;
|
|
10490
10532
|
this.runtime = runtime;
|
|
10491
10533
|
this.delegation = delegation;
|
|
10492
10534
|
this.independent = independent;
|
|
10535
|
+
this.includeRuntime = delegation === void 0 || runtime.config.runtimeMemory.inheritToSubagents;
|
|
10493
10536
|
if (delegation === void 0) return;
|
|
10494
10537
|
this.releaseDelegation = runtime.executions.retain(agent, delegation);
|
|
10495
10538
|
}
|
|
10496
10539
|
get current() {
|
|
10497
10540
|
return this.pinned;
|
|
10498
10541
|
}
|
|
10542
|
+
modelWake() {
|
|
10543
|
+
const pinned = this.pinned;
|
|
10544
|
+
return pinned === void 0 ? void 0 : modelMemoryWake(pinned.graph, pinned.context, this.includeRuntime);
|
|
10545
|
+
}
|
|
10499
10546
|
/** Why the current turn has no View, when memory could not be composed for it. */
|
|
10500
10547
|
get unavailableReason() {
|
|
10501
10548
|
return this.unavailable?.reason;
|
|
@@ -10586,7 +10633,7 @@ var AgentMemoryTurn = class {
|
|
|
10586
10633
|
try {
|
|
10587
10634
|
if (this.closed || this.generation !== generation) throw new Error("memory turn ended during View preparation");
|
|
10588
10635
|
signal?.throwIfAborted();
|
|
10589
|
-
const inspection = inspectMemoryView(graph.memoryComposition.generation(context.view.runtimeGeneration), context.view, "active", turn, modelMemoryWake(graph, context).text);
|
|
10636
|
+
const inspection = inspectMemoryView(graph.memoryComposition.generation(context.view.runtimeGeneration), context.view, "active", turn, modelMemoryWake(graph, context, this.includeRuntime).text);
|
|
10590
10637
|
this.pinned = {
|
|
10591
10638
|
turn,
|
|
10592
10639
|
graph,
|
|
@@ -13871,6 +13918,16 @@ var MnemonLifecycle = class {
|
|
|
13871
13918
|
...typeof effort === "string" && effort !== "" ? { reasoningEffort: effort } : {}
|
|
13872
13919
|
};
|
|
13873
13920
|
}
|
|
13921
|
+
/** Freeze the effective task route for a paired skill evaluation. */
|
|
13922
|
+
skillTaskRoute(workspaceRoot) {
|
|
13923
|
+
const options = this.taskAgentModelOptions("", workspaceRoot);
|
|
13924
|
+
if (!options?.provider || !options.model) return void 0;
|
|
13925
|
+
return {
|
|
13926
|
+
provider: options.provider,
|
|
13927
|
+
model: options.model,
|
|
13928
|
+
...options.reasoningEffort === void 0 ? {} : { reasoningEffort: options.reasoningEffort }
|
|
13929
|
+
};
|
|
13930
|
+
}
|
|
13874
13931
|
taskAgentModelOptions(fallbackSessionId, workspaceRoot) {
|
|
13875
13932
|
return this.taskAgentModelRoute(fallbackSessionId, workspaceRoot)?.options;
|
|
13876
13933
|
}
|
|
@@ -13958,12 +14015,11 @@ var MnemonLifecycle = class {
|
|
|
13958
14015
|
return decision;
|
|
13959
14016
|
}
|
|
13960
14017
|
if (openAgentTurn(agent) !== payload.turn) return decision;
|
|
13961
|
-
const recovering = memory.current?.turn !== payload.turn;
|
|
13962
14018
|
await memory.begin(payload.turn, payload.signal);
|
|
13963
14019
|
const pinned = memory.current;
|
|
13964
|
-
if (
|
|
13965
|
-
const wake =
|
|
13966
|
-
if (wake.text.trim() !== "") return {
|
|
14020
|
+
if (payload.step === 1 && this.config.lifecycleEnabled && pinned !== void 0) {
|
|
14021
|
+
const wake = memory.modelWake();
|
|
14022
|
+
if (wake !== void 0 && wake.text.trim() !== "") return {
|
|
13967
14023
|
kind: "enter",
|
|
13968
14024
|
messages: [...decision.messages, createPluginMessage(wake.text, "recall")]
|
|
13969
14025
|
};
|
|
@@ -13983,6 +14039,219 @@ var MnemonLifecycle = class {
|
|
|
13983
14039
|
}
|
|
13984
14040
|
};
|
|
13985
14041
|
//#endregion
|
|
14042
|
+
//#region src/host/skill-auto-adoption.ts
|
|
14043
|
+
var AutomaticSkillStop = class extends Error {
|
|
14044
|
+
reason;
|
|
14045
|
+
constructor(reason, detail) {
|
|
14046
|
+
super(detail ?? reason);
|
|
14047
|
+
this.reason = reason;
|
|
14048
|
+
}
|
|
14049
|
+
};
|
|
14050
|
+
function hashSkillCases(cases) {
|
|
14051
|
+
return createHash("sha256").update(SkillsContracts.skillCaseSignature(cases)).digest("hex");
|
|
14052
|
+
}
|
|
14053
|
+
function hashSkillDraft(draft) {
|
|
14054
|
+
return createHash("sha256").update(draft.content).digest("hex");
|
|
14055
|
+
}
|
|
14056
|
+
/** Only fresh idle-review suggestions enter this bounded, in-process queue. */
|
|
14057
|
+
var SkillAutoAdoption = class {
|
|
14058
|
+
host;
|
|
14059
|
+
jobs = /* @__PURE__ */ new Map();
|
|
14060
|
+
tail = Promise.resolve();
|
|
14061
|
+
disposed = false;
|
|
14062
|
+
constructor(host) {
|
|
14063
|
+
this.host = host;
|
|
14064
|
+
}
|
|
14065
|
+
enqueue(source, workspace, ids, available) {
|
|
14066
|
+
if (this.disposed || !this.host.settings().autoAdopt) return;
|
|
14067
|
+
for (const id of ids) {
|
|
14068
|
+
if (this.jobs.has(id)) continue;
|
|
14069
|
+
if (this.jobs.size >= 16) {
|
|
14070
|
+
this.record({
|
|
14071
|
+
source,
|
|
14072
|
+
id
|
|
14073
|
+
}, {
|
|
14074
|
+
stage: "review",
|
|
14075
|
+
attempt: 0,
|
|
14076
|
+
reason: "queue-full"
|
|
14077
|
+
}).catch((error) => this.host.warn("Could not record automatic adoption queue limit", error));
|
|
14078
|
+
continue;
|
|
14079
|
+
}
|
|
14080
|
+
const job = {
|
|
14081
|
+
source,
|
|
14082
|
+
workspace,
|
|
14083
|
+
id,
|
|
14084
|
+
available,
|
|
14085
|
+
controller: new AbortController()
|
|
14086
|
+
};
|
|
14087
|
+
this.jobs.set(id, job);
|
|
14088
|
+
this.tail = this.tail.then(() => this.run(job)).catch((error) => this.host.warn("Automatic skill adoption failed", error)).finally(() => {
|
|
14089
|
+
this.jobs.delete(id);
|
|
14090
|
+
});
|
|
14091
|
+
}
|
|
14092
|
+
}
|
|
14093
|
+
allowed(job) {
|
|
14094
|
+
const settings = this.host.settings();
|
|
14095
|
+
return !this.disposed && settings.evolution && settings.autoAdopt && settings.autoBacktest && job.available() && (job.action === void 0 || (job.action === "create" ? settings.allowCreate : settings.allowUpdate));
|
|
14096
|
+
}
|
|
14097
|
+
settingsChanged() {
|
|
14098
|
+
for (const job of this.jobs.values()) if (!this.allowed(job)) job.controller.abort(new AutomaticSkillStop("disabled"));
|
|
14099
|
+
}
|
|
14100
|
+
dispose() {
|
|
14101
|
+
this.disposed = true;
|
|
14102
|
+
for (const job of this.jobs.values()) job.controller.abort(new AutomaticSkillStop("interrupted"));
|
|
14103
|
+
}
|
|
14104
|
+
checkpoint(job) {
|
|
14105
|
+
if (!this.allowed(job)) job.controller.abort(new AutomaticSkillStop(this.disposed ? "interrupted" : "disabled"));
|
|
14106
|
+
job.controller.signal.throwIfAborted();
|
|
14107
|
+
}
|
|
14108
|
+
record(job, event) {
|
|
14109
|
+
return job.source.mutate("auto-adoption-record", {
|
|
14110
|
+
id: job.id,
|
|
14111
|
+
...event
|
|
14112
|
+
});
|
|
14113
|
+
}
|
|
14114
|
+
async unchanged(job, draft, casesHash) {
|
|
14115
|
+
this.checkpoint(job);
|
|
14116
|
+
const current = await job.source.read("draft", { id: draft.id }, job.controller.signal);
|
|
14117
|
+
const suggestion = (await job.source.read("suggestions", null, job.controller.signal)).find((entry) => entry.id === job.id);
|
|
14118
|
+
const cases = (await job.source.read("cases", { skill: draft.skillName }, job.controller.signal)).filter((entry) => entry.enabled);
|
|
14119
|
+
if (current.conflict !== void 0 || current.draft.status !== "ready" || suggestion?.status !== "review" || suggestion.draftId !== draft.id || hashSkillDraft(current.draft) !== hashSkillDraft(draft) || hashSkillCases(cases) !== casesHash) throw new AutomaticSkillStop("changed");
|
|
14120
|
+
}
|
|
14121
|
+
async runs(job, ids) {
|
|
14122
|
+
const deadline = AbortSignal.timeout(276e4);
|
|
14123
|
+
const signal = AbortSignal.any([job.controller.signal, deadline]);
|
|
14124
|
+
for (;;) {
|
|
14125
|
+
this.checkpoint(job);
|
|
14126
|
+
signal.throwIfAborted();
|
|
14127
|
+
const runs = await Promise.all(ids.map((id) => job.source.read("run", { id }, signal)));
|
|
14128
|
+
if (runs.some((run) => run.status !== "running" && run.status !== "completed") || runs.every((run) => run.status !== "running")) return runs;
|
|
14129
|
+
await setTimeout$1(250, void 0, { signal });
|
|
14130
|
+
}
|
|
14131
|
+
}
|
|
14132
|
+
async run(job) {
|
|
14133
|
+
const runIds = /* @__PURE__ */ new Set();
|
|
14134
|
+
let attempt = 0;
|
|
14135
|
+
let scores = {};
|
|
14136
|
+
try {
|
|
14137
|
+
this.checkpoint(job);
|
|
14138
|
+
const suggestion = (await job.source.read("suggestions")).find((entry) => entry.id === job.id);
|
|
14139
|
+
if (suggestion?.status !== "pending" || suggestion.draftId !== void 0) throw new AutomaticSkillStop("changed");
|
|
14140
|
+
job.action = suggestion.action;
|
|
14141
|
+
this.checkpoint(job);
|
|
14142
|
+
await this.record(job, {
|
|
14143
|
+
stage: "queued",
|
|
14144
|
+
attempt
|
|
14145
|
+
});
|
|
14146
|
+
const cases = (await job.source.read("cases", { skill: suggestion.skillName })).filter((entry) => entry.enabled);
|
|
14147
|
+
if (cases.length === 0) throw new AutomaticSkillStop("no-cases");
|
|
14148
|
+
if (cases.some((entry) => entry.requiresVision === true || entry.checks.length === 0)) throw new AutomaticSkillStop("unmeasured");
|
|
14149
|
+
if (cases.length > this.host.settings().casesPerRun) throw new AutomaticSkillStop("case-limit");
|
|
14150
|
+
const casesHash = hashSkillCases(cases);
|
|
14151
|
+
const preferences = await job.source.read("draft-preferences");
|
|
14152
|
+
const defaultRoute = this.host.route(job.workspace);
|
|
14153
|
+
const runRoute = preferences.skillRunModels[suggestion.skillName] ?? preferences.runModel ?? defaultRoute;
|
|
14154
|
+
const draftRoute = preferences.suggestionModels[job.id] ?? preferences.model ?? defaultRoute;
|
|
14155
|
+
if (runRoute === void 0 || draftRoute === void 0) throw new AutomaticSkillStop("model-unavailable");
|
|
14156
|
+
let previous;
|
|
14157
|
+
let guidance = "";
|
|
14158
|
+
for (attempt = 1; attempt <= 3; attempt++) {
|
|
14159
|
+
this.checkpoint(job);
|
|
14160
|
+
await this.record(job, {
|
|
14161
|
+
stage: "drafting",
|
|
14162
|
+
attempt
|
|
14163
|
+
});
|
|
14164
|
+
const draft = await this.host.draft(job.source, job.workspace, {
|
|
14165
|
+
suggestionId: job.id,
|
|
14166
|
+
action: suggestion.action,
|
|
14167
|
+
...previous === void 0 ? {} : { previous },
|
|
14168
|
+
guidance,
|
|
14169
|
+
route: draftRoute
|
|
14170
|
+
}, job.controller.signal);
|
|
14171
|
+
await this.unchanged(job, draft, casesHash);
|
|
14172
|
+
await this.record(job, {
|
|
14173
|
+
stage: "backtesting",
|
|
14174
|
+
attempt
|
|
14175
|
+
});
|
|
14176
|
+
const baseline = await this.host.backtest(job.source, job.workspace, {
|
|
14177
|
+
skillName: draft.skillName,
|
|
14178
|
+
target: {
|
|
14179
|
+
kind: draft.action === "update" ? "current" : "none",
|
|
14180
|
+
...draft.action === "update" ? { draftId: draft.id } : {}
|
|
14181
|
+
},
|
|
14182
|
+
route: runRoute,
|
|
14183
|
+
expectedCasesHash: casesHash,
|
|
14184
|
+
signal: job.controller.signal
|
|
14185
|
+
});
|
|
14186
|
+
runIds.add(baseline.runId);
|
|
14187
|
+
this.checkpoint(job);
|
|
14188
|
+
const candidate = await this.host.backtest(job.source, job.workspace, {
|
|
14189
|
+
skillName: draft.skillName,
|
|
14190
|
+
target: {
|
|
14191
|
+
kind: "draft",
|
|
14192
|
+
draftId: draft.id
|
|
14193
|
+
},
|
|
14194
|
+
route: runRoute,
|
|
14195
|
+
expectedCasesHash: casesHash,
|
|
14196
|
+
signal: job.controller.signal
|
|
14197
|
+
});
|
|
14198
|
+
runIds.add(candidate.runId);
|
|
14199
|
+
await job.source.mutate("run-link", {
|
|
14200
|
+
draftId: draft.id,
|
|
14201
|
+
runId: baseline.runId
|
|
14202
|
+
}, job.controller.signal);
|
|
14203
|
+
const [before, after] = await this.runs(job, [baseline.runId, candidate.runId]);
|
|
14204
|
+
await this.unchanged(job, draft, casesHash);
|
|
14205
|
+
const comparison = SkillsContracts.compareAutomaticSkillRuns(cases, before, after, casesHash);
|
|
14206
|
+
scores = {
|
|
14207
|
+
...comparison.baselineScore === void 0 ? {} : { baselineScore: comparison.baselineScore },
|
|
14208
|
+
...comparison.draftScore === void 0 ? {} : { draftScore: comparison.draftScore }
|
|
14209
|
+
};
|
|
14210
|
+
if (comparison.outcome === "unmeasured") throw new AutomaticSkillStop("unmeasured");
|
|
14211
|
+
if (comparison.outcome === "not-worse") {
|
|
14212
|
+
this.checkpoint(job);
|
|
14213
|
+
await job.source.mutate("draft-adopt", {
|
|
14214
|
+
id: draft.id,
|
|
14215
|
+
automatic: {
|
|
14216
|
+
draftHash: hashSkillDraft(draft),
|
|
14217
|
+
baselineRunId: baseline.runId,
|
|
14218
|
+
draftRunId: candidate.runId,
|
|
14219
|
+
casesHash,
|
|
14220
|
+
attempt
|
|
14221
|
+
}
|
|
14222
|
+
}, job.controller.signal);
|
|
14223
|
+
return;
|
|
14224
|
+
}
|
|
14225
|
+
if (attempt === 3) throw new AutomaticSkillStop("regressed");
|
|
14226
|
+
await this.record(job, {
|
|
14227
|
+
stage: "rewriting",
|
|
14228
|
+
attempt,
|
|
14229
|
+
reason: "regressed",
|
|
14230
|
+
...scores
|
|
14231
|
+
});
|
|
14232
|
+
guidance = `The previous draft regressed in paired backtests. Baseline score: ${comparison.baselineScore}; previous draft score: ${comparison.draftScore}. Improve each regressed case without losing any previously passing check.\n` + JSON.stringify(cases.map((entry) => ({
|
|
14233
|
+
title: entry.title,
|
|
14234
|
+
baseline: before.results.find((result) => result.caseId === entry.id),
|
|
14235
|
+
draft: after.results.find((result) => result.caseId === entry.id)
|
|
14236
|
+
}))).slice(0, 8e3);
|
|
14237
|
+
previous = draft;
|
|
14238
|
+
}
|
|
14239
|
+
} catch (error) {
|
|
14240
|
+
const failure = job.controller.signal.aborted ? job.controller.signal.reason : error;
|
|
14241
|
+
const reason = failure instanceof AutomaticSkillStop ? failure.reason : "failed";
|
|
14242
|
+
await this.record(job, {
|
|
14243
|
+
stage: reason === "disabled" || reason === "interrupted" ? "paused" : "review",
|
|
14244
|
+
attempt: Math.min(attempt, 3),
|
|
14245
|
+
reason,
|
|
14246
|
+
...scores,
|
|
14247
|
+
...failure instanceof AutomaticSkillStop ? {} : { detail: failure instanceof Error ? failure.message : String(failure) }
|
|
14248
|
+
}).catch((recordError) => this.host.warn("Could not record automatic adoption outcome", recordError));
|
|
14249
|
+
} finally {
|
|
14250
|
+
for (const id of runIds) await job.source.mutate("run-cancel", { id }).catch((error) => this.host.warn("Could not stop automatic skill backtest", error));
|
|
14251
|
+
}
|
|
14252
|
+
}
|
|
14253
|
+
};
|
|
14254
|
+
//#endregion
|
|
13986
14255
|
//#region src/host/skill-review.ts
|
|
13987
14256
|
const SIGNALS = [
|
|
13988
14257
|
"correction",
|
|
@@ -14815,7 +15084,7 @@ function retryingSource(source, attempts = 4) {
|
|
|
14815
15084
|
for (let attempt = 1;; attempt++) try {
|
|
14816
15085
|
return await source.mutate(operation, input, signal);
|
|
14817
15086
|
} catch (error) {
|
|
14818
|
-
if (attempt >= attempts || !/revision conflict|changed since it was read; reload/u.test(error instanceof Error ? error.message : String(error))) throw error;
|
|
15087
|
+
if (attempt >= attempts || !/revision conflict|Skills Source changed since it was read; reload/u.test(error instanceof Error ? error.message : String(error))) throw error;
|
|
14819
15088
|
}
|
|
14820
15089
|
}
|
|
14821
15090
|
};
|
|
@@ -14823,6 +15092,27 @@ function retryingSource(source, attempts = 4) {
|
|
|
14823
15092
|
function message(error) {
|
|
14824
15093
|
return redact(error instanceof Error ? error.message : String(error));
|
|
14825
15094
|
}
|
|
15095
|
+
/**
|
|
15096
|
+
* A draft task that stops reporting is abandoned here. The subagent behind it
|
|
15097
|
+
* can die without settling, and the suggestion would otherwise stay `drafting`
|
|
15098
|
+
* - blocking dismiss, delete and a retry - until the Host restarts.
|
|
15099
|
+
*/
|
|
15100
|
+
const DRAFT_DEADLINE_MS = 9e5;
|
|
15101
|
+
/** Distinguishes a deadline abort from the plain abort a Host shutdown sends. */
|
|
15102
|
+
var DraftDeadlineError = class extends Error {};
|
|
15103
|
+
/** The deadline error a task ran into, so the Source records why the draft ended. */
|
|
15104
|
+
function deadlineReason(signal) {
|
|
15105
|
+
return signal.reason instanceof DraftDeadlineError ? signal.reason : void 0;
|
|
15106
|
+
}
|
|
15107
|
+
/** Cancellation settles the owner even when an underlying task ignores its signal. */
|
|
15108
|
+
function abortable(signal, work) {
|
|
15109
|
+
signal.throwIfAborted();
|
|
15110
|
+
return new Promise((resolve, reject) => {
|
|
15111
|
+
const abort = () => reject(signal.reason);
|
|
15112
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
15113
|
+
Promise.resolve().then(work).then(resolve, reject).finally(() => signal.removeEventListener("abort", abort));
|
|
15114
|
+
});
|
|
15115
|
+
}
|
|
14826
15116
|
/** Resolve only roots supplied by this Source's scoped catalog, never the task's cwd. */
|
|
14827
15117
|
function newSkillRoot(settings, source, catalog, explicit) {
|
|
14828
15118
|
let root = explicit;
|
|
@@ -14863,8 +15153,22 @@ var SkillEvolution = class {
|
|
|
14863
15153
|
sessions = /* @__PURE__ */ new Map();
|
|
14864
15154
|
jobs = /* @__PURE__ */ new Set();
|
|
14865
15155
|
disposed = false;
|
|
15156
|
+
activeBacktests = 0;
|
|
15157
|
+
backtestJobs = 0;
|
|
15158
|
+
backtestWaiters = /* @__PURE__ */ new Set();
|
|
15159
|
+
automatic;
|
|
14866
15160
|
constructor(host) {
|
|
14867
15161
|
this.host = host;
|
|
15162
|
+
this.automatic = new SkillAutoAdoption({
|
|
15163
|
+
settings: () => host.settings(),
|
|
15164
|
+
route: (workspace) => host.taskRoute?.(workspace),
|
|
15165
|
+
draft: (source, workspace, input, signal) => this.automaticDraft(source, workspace, input, signal),
|
|
15166
|
+
backtest: (source, workspace, input) => this.backtest(source, workspace, input),
|
|
15167
|
+
warn: (text, error) => this.warn(text, error)
|
|
15168
|
+
});
|
|
15169
|
+
}
|
|
15170
|
+
settingsChanged() {
|
|
15171
|
+
this.automatic.settingsChanged();
|
|
14868
15172
|
}
|
|
14869
15173
|
source(agent) {
|
|
14870
15174
|
const source = this.host.source(agent);
|
|
@@ -15098,6 +15402,7 @@ var SkillEvolution = class {
|
|
|
15098
15402
|
});
|
|
15099
15403
|
state.failures = 0;
|
|
15100
15404
|
state.lastReviewAt = this.now().getTime();
|
|
15405
|
+
if (trigger === "automatic" && result.createdSuggestionIds !== void 0) this.automatic.enqueue(source, workspace, result.createdSuggestionIds, () => this.host.source(agent) !== void 0);
|
|
15101
15406
|
return result;
|
|
15102
15407
|
} catch (error) {
|
|
15103
15408
|
state.failures += 1;
|
|
@@ -15128,10 +15433,17 @@ var SkillEvolution = class {
|
|
|
15128
15433
|
delete state.reviewing;
|
|
15129
15434
|
}
|
|
15130
15435
|
}
|
|
15131
|
-
background(name, work) {
|
|
15436
|
+
background(name, work, deadlineMs) {
|
|
15132
15437
|
const controller = new AbortController();
|
|
15133
15438
|
this.jobs.add(controller);
|
|
15134
|
-
|
|
15439
|
+
const timer = deadlineMs === void 0 ? void 0 : setTimeout(() => {
|
|
15440
|
+
controller.abort(new DraftDeadlineError(`${name} did not finish within ${Math.round(deadlineMs / 6e4)} minutes`));
|
|
15441
|
+
}, deadlineMs);
|
|
15442
|
+
timer?.unref?.();
|
|
15443
|
+
return work(controller.signal).catch((error) => this.warn(`${name} failed`, deadlineReason(controller.signal) ?? error)).finally(() => {
|
|
15444
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
15445
|
+
this.jobs.delete(controller);
|
|
15446
|
+
});
|
|
15135
15447
|
}
|
|
15136
15448
|
/**
|
|
15137
15449
|
* Write a draft for a suggestion (or for a skill directly) in the
|
|
@@ -15157,26 +15469,83 @@ var SkillEvolution = class {
|
|
|
15157
15469
|
status: "drafting"
|
|
15158
15470
|
});
|
|
15159
15471
|
this.background("skill draft", async (signal) => {
|
|
15472
|
+
let recorded = false;
|
|
15160
15473
|
try {
|
|
15161
|
-
await this.writeDraft(source, workspace, input, {
|
|
15474
|
+
await abortable(signal, () => this.writeDraft(source, workspace, input, {
|
|
15162
15475
|
pack,
|
|
15163
15476
|
targetRoot,
|
|
15164
|
-
guidance
|
|
15165
|
-
|
|
15477
|
+
guidance,
|
|
15478
|
+
onRecorded: () => {
|
|
15479
|
+
recorded = true;
|
|
15480
|
+
}
|
|
15481
|
+
}, signal));
|
|
15166
15482
|
} catch (error) {
|
|
15167
|
-
if (input.suggestionId !== void 0)
|
|
15168
|
-
id
|
|
15169
|
-
|
|
15170
|
-
|
|
15171
|
-
|
|
15483
|
+
if (!recorded && input.suggestionId !== void 0) {
|
|
15484
|
+
if ((await source.read("suggestions")).find((item) => item.id === input.suggestionId)?.status === "drafting") await source.mutate("suggestion-status", {
|
|
15485
|
+
id: input.suggestionId,
|
|
15486
|
+
status: "pending",
|
|
15487
|
+
error: message(deadlineReason(signal) ?? error)
|
|
15488
|
+
}).catch(() => void 0);
|
|
15489
|
+
}
|
|
15172
15490
|
throw error;
|
|
15173
15491
|
}
|
|
15174
|
-
});
|
|
15492
|
+
}, DRAFT_DEADLINE_MS);
|
|
15175
15493
|
return {
|
|
15176
15494
|
started: true,
|
|
15177
15495
|
...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId }
|
|
15178
15496
|
};
|
|
15179
15497
|
}
|
|
15498
|
+
async automaticDraft(source, workspace, input, parentSignal) {
|
|
15499
|
+
const controller = new AbortController();
|
|
15500
|
+
const signal = AbortSignal.any([parentSignal, controller.signal]);
|
|
15501
|
+
const timer = setTimeout(() => controller.abort(new DraftDeadlineError("Automatic skill draft did not finish within 15 minutes")), DRAFT_DEADLINE_MS);
|
|
15502
|
+
timer.unref?.();
|
|
15503
|
+
const expectedDraft = input.previous === void 0 ? null : {
|
|
15504
|
+
id: input.previous.id,
|
|
15505
|
+
hash: hashSkillDraft(input.previous)
|
|
15506
|
+
};
|
|
15507
|
+
let recorded = false;
|
|
15508
|
+
let claimed = false;
|
|
15509
|
+
try {
|
|
15510
|
+
return await abortable(signal, async () => {
|
|
15511
|
+
const pack = await source.read("evidence-pack", { suggestionId: input.suggestionId }, signal);
|
|
15512
|
+
signal.throwIfAborted();
|
|
15513
|
+
if (pack.action !== input.action || pack.action === "update" && pack.suggestion?.skillFileHash !== pack.skill?.hash) throw new AutomaticSkillStop("changed");
|
|
15514
|
+
const targetRoot = pack.action === "update" ? pack.skill.root : newSkillRoot(this.host.settings(), source, await source.read("catalog", null, signal), input.previous?.targetRoot ?? pack.suggestion?.targetRoot);
|
|
15515
|
+
const guidance = [...input.previous?.guidance ?? [], ...input.guidance === "" ? [] : [input.guidance]].slice(-8);
|
|
15516
|
+
await source.mutate("suggestion-status", {
|
|
15517
|
+
id: input.suggestionId,
|
|
15518
|
+
status: "drafting",
|
|
15519
|
+
expectedDraft
|
|
15520
|
+
}, signal);
|
|
15521
|
+
claimed = true;
|
|
15522
|
+
return this.writeDraft(source, workspace, {
|
|
15523
|
+
suggestionId: input.suggestionId,
|
|
15524
|
+
...input.previous === void 0 ? {} : { draftId: input.previous.id },
|
|
15525
|
+
route: input.route
|
|
15526
|
+
}, {
|
|
15527
|
+
pack,
|
|
15528
|
+
targetRoot,
|
|
15529
|
+
guidance,
|
|
15530
|
+
expectedDraft,
|
|
15531
|
+
deferBacktests: true,
|
|
15532
|
+
onRecorded: () => {
|
|
15533
|
+
recorded = true;
|
|
15534
|
+
}
|
|
15535
|
+
}, signal);
|
|
15536
|
+
});
|
|
15537
|
+
} catch (error) {
|
|
15538
|
+
if (claimed && !recorded) await source.mutate("suggestion-status", {
|
|
15539
|
+
id: input.suggestionId,
|
|
15540
|
+
status: "pending",
|
|
15541
|
+
expectedDraft,
|
|
15542
|
+
...parentSignal.aborted ? {} : { error: message(signal.aborted ? signal.reason : error) }
|
|
15543
|
+
}).catch(() => void 0);
|
|
15544
|
+
throw signal.aborted ? signal.reason : error;
|
|
15545
|
+
} finally {
|
|
15546
|
+
clearTimeout(timer);
|
|
15547
|
+
}
|
|
15548
|
+
}
|
|
15180
15549
|
async writeDraft(source, workspace, input, prepared, signal) {
|
|
15181
15550
|
const settings = this.host.settings();
|
|
15182
15551
|
const { pack, targetRoot, guidance } = prepared;
|
|
@@ -15213,15 +15582,19 @@ var SkillEvolution = class {
|
|
|
15213
15582
|
notes: parsed.notes,
|
|
15214
15583
|
changes: parsed.changes,
|
|
15215
15584
|
guidance,
|
|
15585
|
+
expectedBaseHash: pack.skill?.hash ?? null,
|
|
15586
|
+
...prepared.expectedDraft === void 0 ? {} : { expectedDraft: prepared.expectedDraft },
|
|
15216
15587
|
...pack.action === "create" ? { targetRoot } : {}
|
|
15217
15588
|
}, signal);
|
|
15589
|
+
prepared.onRecorded?.();
|
|
15218
15590
|
} catch (error) {
|
|
15219
15591
|
signal.throwIfAborted();
|
|
15220
|
-
if (attempt > 0) throw error;
|
|
15592
|
+
if (attempt > 0 || /changed while this draft/.test(message(error))) throw error;
|
|
15221
15593
|
prompt += `\n\nYour previous SKILL.md was rejected: ${message(error)}. Return a corrected complete file.`;
|
|
15222
15594
|
}
|
|
15223
15595
|
}
|
|
15224
|
-
|
|
15596
|
+
signal.throwIfAborted();
|
|
15597
|
+
if (!prepared.deferBacktests && (settings.autoBacktest || input.backtest) && pack.cases.length > 0) {
|
|
15225
15598
|
await this.backtest(source, workspace, {
|
|
15226
15599
|
skillName: pack.skillName,
|
|
15227
15600
|
target: {
|
|
@@ -15252,67 +15625,138 @@ var SkillEvolution = class {
|
|
|
15252
15625
|
return draft;
|
|
15253
15626
|
}
|
|
15254
15627
|
/** Start a backtest of one version of a skill against its enabled cases; it runs in the background. */
|
|
15628
|
+
backtestSlot(signal) {
|
|
15629
|
+
signal.throwIfAborted();
|
|
15630
|
+
return new Promise((resolve, reject) => {
|
|
15631
|
+
const abort = () => {
|
|
15632
|
+
this.backtestWaiters.delete(acquire);
|
|
15633
|
+
reject(signal.reason);
|
|
15634
|
+
};
|
|
15635
|
+
const acquire = () => {
|
|
15636
|
+
if (signal.aborted) {
|
|
15637
|
+
abort();
|
|
15638
|
+
return;
|
|
15639
|
+
}
|
|
15640
|
+
if (this.activeBacktests >= 2) return;
|
|
15641
|
+
this.backtestWaiters.delete(acquire);
|
|
15642
|
+
signal.removeEventListener("abort", abort);
|
|
15643
|
+
this.activeBacktests++;
|
|
15644
|
+
resolve(() => {
|
|
15645
|
+
this.activeBacktests--;
|
|
15646
|
+
for (const waiter of [...this.backtestWaiters]) waiter();
|
|
15647
|
+
});
|
|
15648
|
+
};
|
|
15649
|
+
this.backtestWaiters.add(acquire);
|
|
15650
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
15651
|
+
acquire();
|
|
15652
|
+
});
|
|
15653
|
+
}
|
|
15255
15654
|
async backtest(port, workspace, input) {
|
|
15655
|
+
if (this.disposed) throw new Error("Skill evolution has stopped");
|
|
15656
|
+
input.signal?.throwIfAborted();
|
|
15657
|
+
if (this.backtestJobs >= 16) throw new Error("The skill backtest queue is full. Cancel a run or retry after it finishes.");
|
|
15658
|
+
this.backtestJobs++;
|
|
15256
15659
|
const source = retryingSource(port);
|
|
15257
|
-
|
|
15258
|
-
|
|
15259
|
-
|
|
15260
|
-
|
|
15261
|
-
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
|
|
15269
|
-
|
|
15270
|
-
|
|
15271
|
-
|
|
15272
|
-
|
|
15273
|
-
|
|
15274
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
15284
|
-
|
|
15285
|
-
|
|
15286
|
-
|
|
15660
|
+
try {
|
|
15661
|
+
const settings = this.host.settings();
|
|
15662
|
+
const cases = (await source.read("cases", { skill: input.skillName }, input.signal)).filter((entry) => entry.enabled && (input.caseIds === void 0 || input.caseIds.includes(entry.id))).slice(0, settings.casesPerRun);
|
|
15663
|
+
if (cases.length === 0) throw new Error(`${input.skillName} has no enabled eval case to run`);
|
|
15664
|
+
if (input.expectedCasesHash !== void 0 && hashSkillCases(cases) !== input.expectedCasesHash) throw new Error("Eval cases changed before this backtest started");
|
|
15665
|
+
if (cases.some((entry) => entry.requiresVision !== void 0 && typeof entry.requiresVision !== "boolean")) throw new Error("requiresVision must be a boolean");
|
|
15666
|
+
const resolved = await source.read("target-content", {
|
|
15667
|
+
skillName: input.skillName,
|
|
15668
|
+
target: input.target
|
|
15669
|
+
}, input.signal);
|
|
15670
|
+
const run = await source.mutate("run-start", {
|
|
15671
|
+
skillName: input.skillName,
|
|
15672
|
+
target: resolved.target,
|
|
15673
|
+
caseIds: cases.map((entry) => entry.id),
|
|
15674
|
+
...input.expectedCasesHash === void 0 ? {} : { expectedCasesHash: input.expectedCasesHash },
|
|
15675
|
+
...input.route === void 0 ? {} : { model: input.route }
|
|
15676
|
+
}, input.signal);
|
|
15677
|
+
this.background("skill backtest", async (deadline) => {
|
|
15678
|
+
const controller = new AbortController();
|
|
15679
|
+
const abort = () => controller.abort(deadline.aborted ? deadline.reason : input.signal?.reason);
|
|
15680
|
+
deadline.addEventListener("abort", abort, { once: true });
|
|
15681
|
+
input.signal?.addEventListener("abort", abort, { once: true });
|
|
15682
|
+
if (deadline.aborted || input.signal?.aborted) abort();
|
|
15683
|
+
const signal = controller.signal;
|
|
15684
|
+
let checking = false;
|
|
15685
|
+
const watcher = setInterval(() => {
|
|
15686
|
+
if (checking || signal.aborted) return;
|
|
15687
|
+
checking = true;
|
|
15688
|
+
source.read("run", { id: run.id }).then((current) => {
|
|
15689
|
+
if (current.status !== "running") controller.abort(new Error(current.error ?? "Backtest interrupted"));
|
|
15690
|
+
}).catch((error) => controller.abort(error)).finally(() => {
|
|
15691
|
+
checking = false;
|
|
15287
15692
|
});
|
|
15288
|
-
}
|
|
15289
|
-
|
|
15290
|
-
|
|
15291
|
-
|
|
15292
|
-
|
|
15693
|
+
}, 1e3);
|
|
15694
|
+
watcher.unref?.();
|
|
15695
|
+
try {
|
|
15696
|
+
await abortable(signal, async () => {
|
|
15697
|
+
const release = await this.backtestSlot(signal);
|
|
15698
|
+
try {
|
|
15699
|
+
for (const entry of cases.filter((entry) => entry.requiresVision === true)) {
|
|
15700
|
+
signal.throwIfAborted();
|
|
15701
|
+
await source.mutate("run-progress", {
|
|
15702
|
+
id: run.id,
|
|
15703
|
+
result: {
|
|
15704
|
+
caseId: entry.id,
|
|
15705
|
+
title: entry.title,
|
|
15706
|
+
verdict: "error",
|
|
15707
|
+
score: 0,
|
|
15708
|
+
unmeasuredReason: "requires-vision",
|
|
15709
|
+
checks: [],
|
|
15710
|
+
steps: [],
|
|
15711
|
+
answer: "",
|
|
15712
|
+
error: "Not measured: this case requires image input, which text-only backtests cannot receive."
|
|
15713
|
+
}
|
|
15714
|
+
}, signal);
|
|
15715
|
+
}
|
|
15716
|
+
const queue = cases.filter((entry) => entry.requiresVision !== true);
|
|
15717
|
+
if (queue.length > 0) await abortable(signal, () => this.host.runTask(workspace, signal, async (task) => {
|
|
15718
|
+
const worker = async () => {
|
|
15719
|
+
for (let entry = queue.shift(); entry !== void 0; entry = queue.shift()) {
|
|
15720
|
+
signal.throwIfAborted();
|
|
15721
|
+
const result = await this.runCase(task, entry, input.skillName, resolved.content, signal);
|
|
15722
|
+
signal.throwIfAborted();
|
|
15723
|
+
await source.mutate("run-progress", {
|
|
15724
|
+
id: run.id,
|
|
15725
|
+
result
|
|
15726
|
+
}, signal);
|
|
15727
|
+
}
|
|
15728
|
+
};
|
|
15729
|
+
await Promise.all(Array.from({ length: Math.min(BACKTEST_CONCURRENCY, queue.length) }, worker));
|
|
15730
|
+
}, input.route));
|
|
15293
15731
|
signal.throwIfAborted();
|
|
15294
|
-
await source.mutate("run-
|
|
15732
|
+
await source.mutate("run-finish", {
|
|
15295
15733
|
id: run.id,
|
|
15296
|
-
|
|
15297
|
-
});
|
|
15734
|
+
status: "completed"
|
|
15735
|
+
}, signal);
|
|
15736
|
+
} finally {
|
|
15737
|
+
release();
|
|
15298
15738
|
}
|
|
15299
|
-
};
|
|
15300
|
-
|
|
15301
|
-
|
|
15302
|
-
|
|
15303
|
-
|
|
15304
|
-
|
|
15305
|
-
|
|
15306
|
-
|
|
15307
|
-
|
|
15308
|
-
|
|
15309
|
-
|
|
15310
|
-
|
|
15311
|
-
}
|
|
15312
|
-
|
|
15313
|
-
|
|
15314
|
-
|
|
15315
|
-
|
|
15739
|
+
});
|
|
15740
|
+
} catch (error) {
|
|
15741
|
+
await source.mutate("run-finish", {
|
|
15742
|
+
id: run.id,
|
|
15743
|
+
status: signal.aborted ? "interrupted" : "failed",
|
|
15744
|
+
error: message(signal.aborted ? signal.reason : error)
|
|
15745
|
+
}).catch(() => void 0);
|
|
15746
|
+
throw error;
|
|
15747
|
+
} finally {
|
|
15748
|
+
clearInterval(watcher);
|
|
15749
|
+
deadline.removeEventListener("abort", abort);
|
|
15750
|
+
input.signal?.removeEventListener("abort", abort);
|
|
15751
|
+
}
|
|
15752
|
+
}, 27e5).finally(() => {
|
|
15753
|
+
this.backtestJobs--;
|
|
15754
|
+
});
|
|
15755
|
+
return { runId: run.id };
|
|
15756
|
+
} catch (error) {
|
|
15757
|
+
this.backtestJobs--;
|
|
15758
|
+
throw error;
|
|
15759
|
+
}
|
|
15316
15760
|
}
|
|
15317
15761
|
/**
|
|
15318
15762
|
* Execute and grade one case. A run that fails (an output limit, a missing
|
|
@@ -15326,9 +15770,15 @@ var SkillEvolution = class {
|
|
|
15326
15770
|
name: skillName,
|
|
15327
15771
|
...content === void 0 ? {} : { content }
|
|
15328
15772
|
}), SKILL_EXECUTOR_SCHEMA, SKILL_EXECUTOR_PERSONA, signal)).structured);
|
|
15329
|
-
|
|
15773
|
+
signal.throwIfAborted();
|
|
15774
|
+
const judged = await this.host.skillTask(task, `Mnemon skill judge: ${entry.title}`.slice(0, 120), skillJudgePrompt(entry, executed.steps, executed.answer), SKILL_JUDGE_SCHEMA, SKILL_JUDGE_PERSONA, signal);
|
|
15775
|
+
const grade = gradeCase(entry, judged.structured);
|
|
15776
|
+
const raw = judged.structured;
|
|
15777
|
+
const checks = Array.isArray(raw?.checks) ? raw.checks : [];
|
|
15778
|
+
const fullyGraded = checks.length === entry.checks.length && entry.checks.every((_check, index) => checks.filter((check) => check?.index === index + 1 && typeof check.pass === "boolean").length === 1);
|
|
15330
15779
|
return {
|
|
15331
15780
|
caseId: entry.id,
|
|
15781
|
+
fullyGraded,
|
|
15332
15782
|
title: entry.title,
|
|
15333
15783
|
verdict: grade.verdict,
|
|
15334
15784
|
score: grade.score,
|
|
@@ -15438,6 +15888,7 @@ var SkillEvolution = class {
|
|
|
15438
15888
|
};
|
|
15439
15889
|
}
|
|
15440
15890
|
dispose() {
|
|
15891
|
+
this.automatic.dispose();
|
|
15441
15892
|
this.disposed = true;
|
|
15442
15893
|
for (const state of this.sessions.values()) if (state.timer !== void 0) clearTimeout(state.timer);
|
|
15443
15894
|
this.sessions.clear();
|
|
@@ -15783,6 +16234,7 @@ async function skillAssistance(runtime, lifecycle, operation, input, signal) {
|
|
|
15783
16234
|
if (guidance !== void 0 && guidance.length > 4e3) throw new Error("guidance is too long (max 4000 characters)");
|
|
15784
16235
|
const draftRoute = modelRoute(input);
|
|
15785
16236
|
return skills.draft(source, workspaceRoot, {
|
|
16237
|
+
...input.backtest === true ? { backtest: true } : {},
|
|
15786
16238
|
...suggestionId === void 0 ? {} : { suggestionId },
|
|
15787
16239
|
...skill === void 0 ? {} : { skill },
|
|
15788
16240
|
...draftId === void 0 ? {} : { draftId },
|
|
@@ -19486,9 +19938,11 @@ function apply(rawContext, rawConfig = {}) {
|
|
|
19486
19938
|
const skills = new SkillEvolution({
|
|
19487
19939
|
settings: () => skillSettings.get(),
|
|
19488
19940
|
source: (agent) => skillsSourceFor(runtime, agent),
|
|
19941
|
+
taskRoute: (workspace) => lifecycle.skillTaskRoute(workspace),
|
|
19489
19942
|
runTask: (workspaceRoot, signal, operation, route) => lifecycle.runSkillTask(workspaceRoot, signal, operation, route),
|
|
19490
19943
|
skillTask: (parent, label, prompt, schema, persona, signal, _route, maxTokens) => coordinator.skillTask(parent, label, prompt, schema, persona, signal, taskRoute(parent), maxTokens)
|
|
19491
19944
|
});
|
|
19945
|
+
ctx.effect(() => hostSettings.onUpdated(() => skills.settingsChanged()), "dsh-mnemon: automatic skill adoption settings");
|
|
19492
19946
|
lifecycle.useSkillEvolution(skills);
|
|
19493
19947
|
ctx.effect(() => {
|
|
19494
19948
|
const stop = autoBackup.start();
|