dsh-mnemon-source-skills 0.5.2 → 0.5.4
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 +2 -0
- package/lib/client.js +397 -42
- package/lib/contracts.d.ts +29 -1
- package/lib/contracts.js +2 -0
- package/lib/index.js +205 -1
- package/lib/types/client/api.d.ts +26 -2
- package/lib/types/client/model-choice.d.ts +45 -0
- package/lib/types/client/skill-tests.d.ts +2 -1
- package/lib/types/contracts.d.ts +28 -0
- package/package.json +3 -3
- package/presentation/locales.json +22 -2
- package/presentation/page.module.css +8 -0
package/lib/contracts.d.ts
CHANGED
|
@@ -239,6 +239,30 @@ interface RunTarget {
|
|
|
239
239
|
draftId?: string;
|
|
240
240
|
versionId?: string;
|
|
241
241
|
}
|
|
242
|
+
/** One Provider and model, as a user picked it; never carries a reasoning effort. */
|
|
243
|
+
interface ModelRoute {
|
|
244
|
+
provider: string;
|
|
245
|
+
model: string;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The models the user picked for skill work, kept beside the ledger. Every
|
|
249
|
+
* entry is optional: absent means "follow the task Agent's own route".
|
|
250
|
+
* `suggestionModels` is keyed by suggestion id, `skillRunModels` by skill name.
|
|
251
|
+
*/
|
|
252
|
+
interface DraftPreferences {
|
|
253
|
+
version: 1;
|
|
254
|
+
draftModel?: ModelRoute;
|
|
255
|
+
runModel?: ModelRoute;
|
|
256
|
+
suggestionModels: Record<string, ModelRoute>;
|
|
257
|
+
skillRunModels: Record<string, ModelRoute>;
|
|
258
|
+
}
|
|
259
|
+
/** What the pickers read: the stored picks, named the way the pages use them. */
|
|
260
|
+
interface DraftPreferencesView {
|
|
261
|
+
model?: ModelRoute;
|
|
262
|
+
runModel?: ModelRoute;
|
|
263
|
+
suggestionModels: Record<string, ModelRoute>;
|
|
264
|
+
skillRunModels: Record<string, ModelRoute>;
|
|
265
|
+
}
|
|
242
266
|
interface CaseResult {
|
|
243
267
|
caseId: string;
|
|
244
268
|
title: string;
|
|
@@ -298,6 +322,10 @@ interface ReviewLogEntry {
|
|
|
298
322
|
}
|
|
299
323
|
/** How the Source admits a review's suggestions; the Host sends its settings with each review. */
|
|
300
324
|
interface SkillsPolicy {
|
|
325
|
+
/** Whether a review may propose new skills; omitted by older Hosts, default true. */
|
|
326
|
+
allowCreate?: boolean;
|
|
327
|
+
/** Whether a review may improve existing skills; omitted by older Hosts, default true. */
|
|
328
|
+
allowUpdate?: boolean;
|
|
301
329
|
minConfidence: number;
|
|
302
330
|
expiryDays: number;
|
|
303
331
|
maxPendingPerSession: number;
|
|
@@ -592,4 +620,4 @@ interface MarketInstall {
|
|
|
592
620
|
installedAt: string;
|
|
593
621
|
}
|
|
594
622
|
//#endregion
|
|
595
|
-
export { BacktestRun, CaseResult, CatalogView, DEFAULT_SKILLS_POLICY, type DetectedSource, DraftStatus, DraftView, EVOLUTION_SIGNALS, EvalCase, EvalCheck, EvidencePack, EvidenceReference, EvolutionSignal, LocalizedText, MarketAudit, MarketEntry, MarketInstall, MarketInstallState, MarketItem, MarketPreview, MarketRegistry, MarketRegistryFile, MarketSearchResult, MarketSource, type MarketSourceKind, MarketView, ReviewLogEntry, ReviewRecordInput, ReviewRecordResult, ReviewScore, RunStatus, RunTarget, RunTargetKind, SKILL_ROOT_KINDS, SKILL_ROOT_RANK, SUGGESTION_STATUSES, SkillCatalog, SkillDocument, SkillDraft, SkillInvocation, SkillProblem, SkillRoot, SkillRootKind, SkillSuggestion, SkillSummary, SkillVersion, SkillVersionReason, SkillsOverview, SkillsPolicy, SuggestionStatus, SuggestionView, Trajectory, TrajectoryAnnotation, TrajectoryStats, TrajectoryStep, TrajectoryStepKind, TrajectorySummary, TrashEntry, TrashRestoreInput, TrashRestorePreview };
|
|
623
|
+
export { BacktestRun, CaseResult, CatalogView, DEFAULT_SKILLS_POLICY, type DetectedSource, DraftPreferences, DraftPreferencesView, DraftStatus, DraftView, EVOLUTION_SIGNALS, EvalCase, EvalCheck, EvidencePack, EvidenceReference, EvolutionSignal, LocalizedText, MarketAudit, MarketEntry, MarketInstall, MarketInstallState, MarketItem, MarketPreview, MarketRegistry, MarketRegistryFile, MarketSearchResult, MarketSource, type MarketSourceKind, MarketView, ModelRoute, ReviewLogEntry, ReviewRecordInput, ReviewRecordResult, ReviewScore, RunStatus, RunTarget, RunTargetKind, SKILL_ROOT_KINDS, SKILL_ROOT_RANK, SUGGESTION_STATUSES, SkillCatalog, SkillDocument, SkillDraft, SkillInvocation, SkillProblem, SkillRoot, SkillRootKind, SkillSuggestion, SkillSummary, SkillVersion, SkillVersionReason, SkillsOverview, SkillsPolicy, SuggestionStatus, SuggestionView, Trajectory, TrajectoryAnnotation, TrajectoryStats, TrajectoryStep, TrajectoryStepKind, TrajectorySummary, TrashEntry, TrashRestoreInput, TrashRestorePreview };
|
package/lib/contracts.js
CHANGED
package/lib/index.js
CHANGED
|
@@ -489,6 +489,43 @@ var SkillsStore = class {
|
|
|
489
489
|
installs: array(value.installs)
|
|
490
490
|
};
|
|
491
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
* What the user picked for draft and backtest runs, kept out of `fingerprint()`
|
|
494
|
+
* on purpose: changing a picker must not invalidate an open page's revision.
|
|
495
|
+
*/
|
|
496
|
+
preferences() {
|
|
497
|
+
const stored = object$1(this.readJson("preferences.json")) ?? {};
|
|
498
|
+
const route = (value) => {
|
|
499
|
+
const picked = object$1(value);
|
|
500
|
+
const provider = typeof picked?.provider === "string" ? picked.provider : void 0;
|
|
501
|
+
const model = typeof picked?.model === "string" ? picked.model : void 0;
|
|
502
|
+
return provider !== void 0 && model !== void 0 ? {
|
|
503
|
+
provider,
|
|
504
|
+
model
|
|
505
|
+
} : void 0;
|
|
506
|
+
};
|
|
507
|
+
const routes = (value) => {
|
|
508
|
+
const result = {};
|
|
509
|
+
const map = object$1(value);
|
|
510
|
+
if (map !== void 0) for (const [key, item] of Object.entries(map)) {
|
|
511
|
+
const picked = route(item);
|
|
512
|
+
if (picked !== void 0) result[key] = picked;
|
|
513
|
+
}
|
|
514
|
+
return result;
|
|
515
|
+
};
|
|
516
|
+
const draft = route(stored.draftModel);
|
|
517
|
+
const run = route(stored.runModel);
|
|
518
|
+
return {
|
|
519
|
+
version: 1,
|
|
520
|
+
...draft === void 0 ? {} : { draftModel: draft },
|
|
521
|
+
...run === void 0 ? {} : { runModel: run },
|
|
522
|
+
suggestionModels: routes(stored.suggestionModels),
|
|
523
|
+
skillRunModels: routes(stored.skillRunModels)
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
savePreferences(preferences) {
|
|
527
|
+
this.writeJson("preferences.json", preferences);
|
|
528
|
+
}
|
|
492
529
|
saveLedger(ledger) {
|
|
493
530
|
ledger.generation += 1;
|
|
494
531
|
ledger.reviews = ledger.reviews.slice(0, 200);
|
|
@@ -732,13 +769,33 @@ function clip$1(value, maximum) {
|
|
|
732
769
|
const text = value.trim();
|
|
733
770
|
return text.length <= maximum ? text : text.slice(0, maximum - 1) + "…";
|
|
734
771
|
}
|
|
735
|
-
/**
|
|
772
|
+
/**
|
|
773
|
+
* `drafting` was set before this process started, so no live writer owns it any
|
|
774
|
+
* more. A draft write is cancelled only by the Host going away, and the catch
|
|
775
|
+
* that would put the suggestion back never runs then, so the row would sit in
|
|
776
|
+
* "drafting" forever and refuse both a retry and a delete.
|
|
777
|
+
*/
|
|
778
|
+
const PROCESS_STARTED_AT = Date.now() - Math.round(process.uptime() * 1e3);
|
|
779
|
+
const ORPHAN_DRAFT_ERROR = "the host restarted while this draft was being written";
|
|
780
|
+
function orphanedDraft(suggestion) {
|
|
781
|
+
const at = Date.parse(suggestion.updatedAt);
|
|
782
|
+
return !Number.isFinite(at) || at < PROCESS_STARTED_AT;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Move pending suggestions past their expiry to `expired`, and suggestions whose
|
|
786
|
+
* draft was abandoned by a dead process back to `pending`; true when anything changed.
|
|
787
|
+
*/
|
|
736
788
|
function expireSuggestions(ledger, now) {
|
|
737
789
|
let changed = false;
|
|
738
790
|
for (const suggestion of ledger.suggestions) if (suggestion.status === "pending" && Date.parse(suggestion.expiresAt) < now.getTime()) {
|
|
739
791
|
suggestion.status = "expired";
|
|
740
792
|
suggestion.updatedAt = now.toISOString();
|
|
741
793
|
changed = true;
|
|
794
|
+
} else if (suggestion.status === "drafting" && orphanedDraft(suggestion)) {
|
|
795
|
+
suggestion.status = "pending";
|
|
796
|
+
suggestion.updatedAt = now.toISOString();
|
|
797
|
+
suggestion.lastError = ORPHAN_DRAFT_ERROR;
|
|
798
|
+
changed = true;
|
|
742
799
|
}
|
|
743
800
|
return changed;
|
|
744
801
|
}
|
|
@@ -766,7 +823,14 @@ function policyFrom(value) {
|
|
|
766
823
|
const candidate = value?.[key];
|
|
767
824
|
return typeof candidate === "number" && Number.isFinite(candidate) && candidate >= minimum && candidate <= maximum ? candidate : DEFAULT_SKILLS_POLICY[key];
|
|
768
825
|
};
|
|
826
|
+
const allowed = (key) => {
|
|
827
|
+
const candidate = value?.[key];
|
|
828
|
+
if (candidate !== void 0 && typeof candidate !== "boolean") throw new Error(`skills policy ${key} must be a boolean`);
|
|
829
|
+
return candidate ?? true;
|
|
830
|
+
};
|
|
769
831
|
return {
|
|
832
|
+
allowCreate: allowed("allowCreate"),
|
|
833
|
+
allowUpdate: allowed("allowUpdate"),
|
|
770
834
|
minConfidence: pick("minConfidence", 0, 1),
|
|
771
835
|
expiryDays: Math.round(pick("expiryDays", 1, 90)),
|
|
772
836
|
maxPendingPerSession: Math.round(pick("maxPendingPerSession", 1, 20))
|
|
@@ -785,6 +849,7 @@ function admitReview(context, input) {
|
|
|
785
849
|
const stepIndexes = new Set(input.trajectory.steps.map((step) => step.index));
|
|
786
850
|
const steps = (values) => [...new Set(values.filter((value) => Number.isInteger(value) && stepIndexes.has(value)))].sort((a, b) => a - b);
|
|
787
851
|
const skipped = [];
|
|
852
|
+
const allowed = (action) => (action === "create" ? policy.allowCreate : policy.allowUpdate) !== false;
|
|
788
853
|
let inserted = 0;
|
|
789
854
|
let updated = 0;
|
|
790
855
|
let casesAdded = 0;
|
|
@@ -826,6 +891,14 @@ function admitReview(context, input) {
|
|
|
826
891
|
}
|
|
827
892
|
const existing = activeSkill(catalog, name);
|
|
828
893
|
const action = existing !== void 0 ? "update" : "create";
|
|
894
|
+
if (!allowed(action)) {
|
|
895
|
+
skipped.push({
|
|
896
|
+
skillName: name,
|
|
897
|
+
action,
|
|
898
|
+
reason: "suggestion_type_disabled"
|
|
899
|
+
});
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
829
902
|
if (raw.action === "update" && existing === void 0) {
|
|
830
903
|
skipped.push({
|
|
831
904
|
skillName: name,
|
|
@@ -940,6 +1013,13 @@ function admitReview(context, input) {
|
|
|
940
1013
|
});
|
|
941
1014
|
continue;
|
|
942
1015
|
}
|
|
1016
|
+
if (!allowed(activeSkill(catalog, name) === void 0 ? "create" : "update")) {
|
|
1017
|
+
skipped.push({
|
|
1018
|
+
skillName: name,
|
|
1019
|
+
reason: "suggestion_type_disabled"
|
|
1020
|
+
});
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
943
1023
|
if (similarCase(evals.cases, name, title, prompt) !== void 0) {
|
|
944
1024
|
skipped.push({
|
|
945
1025
|
skillName: name,
|
|
@@ -3387,6 +3467,39 @@ var SkillsService = class {
|
|
|
3387
3467
|
return { purged: entries.length };
|
|
3388
3468
|
});
|
|
3389
3469
|
}
|
|
3470
|
+
/** The model the user picked for drafts and backtests, if any. */
|
|
3471
|
+
draftPreferences() {
|
|
3472
|
+
const stored = this.store.preferences();
|
|
3473
|
+
return {
|
|
3474
|
+
...stored.draftModel === void 0 ? {} : { model: stored.draftModel },
|
|
3475
|
+
...stored.runModel === void 0 ? {} : { runModel: stored.runModel },
|
|
3476
|
+
suggestionModels: stored.suggestionModels,
|
|
3477
|
+
skillRunModels: stored.skillRunModels
|
|
3478
|
+
};
|
|
3479
|
+
}
|
|
3480
|
+
/**
|
|
3481
|
+
* Record one picker change. `suggestionId` targets a single suggestion,
|
|
3482
|
+
* `skillName` with `slot: 'run'` a single skill, and a null model clears the
|
|
3483
|
+
* entry so the page falls back to following the task Agent again.
|
|
3484
|
+
*/
|
|
3485
|
+
setDraftPreference(input) {
|
|
3486
|
+
return this.write(() => {
|
|
3487
|
+
const preferences = this.store.preferences();
|
|
3488
|
+
if (input.slot === "run") {
|
|
3489
|
+
if (input.skillName !== void 0) {
|
|
3490
|
+
if (input.model === null) delete preferences.skillRunModels[input.skillName];
|
|
3491
|
+
else preferences.skillRunModels[input.skillName] = input.model;
|
|
3492
|
+
} else if (input.model === null) delete preferences.runModel;
|
|
3493
|
+
else preferences.runModel = input.model;
|
|
3494
|
+
} else if (input.suggestionId !== void 0) {
|
|
3495
|
+
if (input.model === null) delete preferences.suggestionModels[input.suggestionId];
|
|
3496
|
+
else preferences.suggestionModels[input.suggestionId] = input.model;
|
|
3497
|
+
} else if (input.model === null) delete preferences.draftModel;
|
|
3498
|
+
else preferences.draftModel = input.model;
|
|
3499
|
+
this.store.savePreferences(preferences);
|
|
3500
|
+
return this.draftPreferences();
|
|
3501
|
+
});
|
|
3502
|
+
}
|
|
3390
3503
|
restoreVersion(workspace, input) {
|
|
3391
3504
|
return this.write(() => {
|
|
3392
3505
|
const skill = findSkill(this.catalog(workspace), input.key);
|
|
@@ -3426,6 +3539,25 @@ var SkillsService = class {
|
|
|
3426
3539
|
return suggestion;
|
|
3427
3540
|
});
|
|
3428
3541
|
}
|
|
3542
|
+
/** Remove a suggestion for good, together with any draft it produced and its model pick. */
|
|
3543
|
+
deleteSuggestion(id) {
|
|
3544
|
+
return this.write(() => {
|
|
3545
|
+
const ledger = this.store.ledger();
|
|
3546
|
+
expireSuggestions(ledger, this.clock());
|
|
3547
|
+
const index = ledger.suggestions.findIndex((candidate) => candidate.id === id);
|
|
3548
|
+
if (index < 0) throw new Error("suggestion not found: " + id);
|
|
3549
|
+
if (ledger.suggestions[index].status === "drafting") throw new Error("a draft is being written for this suggestion; wait for it to finish");
|
|
3550
|
+
ledger.suggestions.splice(index, 1);
|
|
3551
|
+
ledger.drafts = ledger.drafts.filter((draft) => draft.suggestionId !== id);
|
|
3552
|
+
const preferences = this.store.preferences();
|
|
3553
|
+
if (preferences.suggestionModels[id] !== void 0) {
|
|
3554
|
+
delete preferences.suggestionModels[id];
|
|
3555
|
+
this.store.savePreferences(preferences);
|
|
3556
|
+
}
|
|
3557
|
+
this.store.saveLedger(ledger);
|
|
3558
|
+
return { deleted: id };
|
|
3559
|
+
});
|
|
3560
|
+
}
|
|
3429
3561
|
/** The Host marks a suggestion `drafting` before a task Agent writes, and back to `pending` if it fails. */
|
|
3430
3562
|
suggestionStatus(input) {
|
|
3431
3563
|
return this.withSuggestion(input.id, (suggestion) => {
|
|
@@ -3675,6 +3807,40 @@ var SkillsService = class {
|
|
|
3675
3807
|
return draft;
|
|
3676
3808
|
});
|
|
3677
3809
|
}
|
|
3810
|
+
/** Remove a draft for good; its suggestion goes back to `pending` so it can be drafted again. */
|
|
3811
|
+
deleteDraft(id) {
|
|
3812
|
+
return this.write(() => {
|
|
3813
|
+
const ledger = this.store.ledger();
|
|
3814
|
+
const index = ledger.drafts.findIndex((candidate) => candidate.id === id);
|
|
3815
|
+
if (index < 0) throw new Error("draft not found: " + id);
|
|
3816
|
+
const draft = ledger.drafts[index];
|
|
3817
|
+
ledger.drafts.splice(index, 1);
|
|
3818
|
+
if (draft.suggestionId !== void 0) {
|
|
3819
|
+
const suggestion = ledger.suggestions.find((candidate) => candidate.id === draft.suggestionId);
|
|
3820
|
+
if (suggestion?.draftId === draft.id) {
|
|
3821
|
+
delete suggestion.draftId;
|
|
3822
|
+
if (suggestion.status === "review") suggestion.status = "pending";
|
|
3823
|
+
suggestion.updatedAt = this.clock().toISOString();
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
this.store.saveLedger(ledger);
|
|
3827
|
+
return { deleted: id };
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3830
|
+
/**
|
|
3831
|
+
* Put suggestions whose draft write died with the previous Host back to
|
|
3832
|
+
* `pending`, so the page stops showing them as "drafting" forever.
|
|
3833
|
+
*/
|
|
3834
|
+
reclaimOrphanDrafts() {
|
|
3835
|
+
return this.write(() => {
|
|
3836
|
+
const ledger = this.store.ledger();
|
|
3837
|
+
const before = ledger.suggestions.filter((suggestion) => suggestion.status === "drafting").length;
|
|
3838
|
+
if (!expireSuggestions(ledger, this.clock())) return { reclaimed: 0 };
|
|
3839
|
+
this.store.saveLedger(ledger);
|
|
3840
|
+
const after = ledger.suggestions.filter((suggestion) => suggestion.status === "drafting").length;
|
|
3841
|
+
return { reclaimed: Math.max(0, before - after) };
|
|
3842
|
+
});
|
|
3843
|
+
}
|
|
3678
3844
|
/** The content a run tests, resolved from its target. */
|
|
3679
3845
|
targetContent(workspace, skillName, target) {
|
|
3680
3846
|
if (target.kind === "none") return { target: { kind: "none" } };
|
|
@@ -4268,6 +4434,16 @@ function rootKind(value) {
|
|
|
4268
4434
|
if (typeof value !== "string" || !SKILL_ROOT_KINDS.includes(value)) throw new Error("root must be one of " + SKILL_ROOT_KINDS.join(", "));
|
|
4269
4435
|
return value;
|
|
4270
4436
|
}
|
|
4437
|
+
/** A picked model, or null to clear the pick and follow the task Agent again. */
|
|
4438
|
+
function draftModelInput(value) {
|
|
4439
|
+
if (value === void 0 || value === null) return null;
|
|
4440
|
+
if (typeof value !== "object" || Array.isArray(value)) throw new Error("model must be an object or null");
|
|
4441
|
+
const picked = value;
|
|
4442
|
+
return {
|
|
4443
|
+
provider: memoryInputText(picked.provider, "model.provider", 100),
|
|
4444
|
+
model: memoryInputText(picked.model, "model.model", 200)
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4271
4447
|
function caseInput(value, skillName) {
|
|
4272
4448
|
const input = memoryInputRecord(value, "case");
|
|
4273
4449
|
const checks = input.checks;
|
|
@@ -4362,7 +4538,14 @@ function step(value) {
|
|
|
4362
4538
|
function policyInput(value) {
|
|
4363
4539
|
const input = memoryInputRecord(value, "policy");
|
|
4364
4540
|
const number = (key) => typeof input[key] === "number" && Number.isFinite(input[key]) ? { [key]: input[key] } : {};
|
|
4541
|
+
const allowed = (key) => {
|
|
4542
|
+
if (input[key] === void 0) return {};
|
|
4543
|
+
if (typeof input[key] !== "boolean") throw new Error(`skills policy ${key} must be a boolean`);
|
|
4544
|
+
return { [key]: input[key] };
|
|
4545
|
+
};
|
|
4365
4546
|
return {
|
|
4547
|
+
...allowed("allowCreate"),
|
|
4548
|
+
...allowed("allowUpdate"),
|
|
4366
4549
|
...number("minConfidence"),
|
|
4367
4550
|
...number("expiryDays"),
|
|
4368
4551
|
...number("maxPendingPerSession")
|
|
@@ -4485,6 +4668,7 @@ function createSkillsMemorySource(config = {}) {
|
|
|
4485
4668
|
...effective.dshHome === void 0 ? {} : { dshHome: effective.dshHome },
|
|
4486
4669
|
...effective.agentsHome === void 0 ? {} : { agentsHome: effective.agentsHome }
|
|
4487
4670
|
});
|
|
4671
|
+
service.reclaimOrphanDrafts().catch(() => void 0);
|
|
4488
4672
|
const facts = (scope, management) => management ? {
|
|
4489
4673
|
sourceInstanceKey: context.sourceInstanceKey,
|
|
4490
4674
|
sourceTypeId: SKILLS_SOURCE_TYPE_ID,
|
|
@@ -4542,6 +4726,7 @@ function createSkillsMemorySource(config = {}) {
|
|
|
4542
4726
|
...optionalText(fields.skill, "skill", 64) === void 0 ? {} : { skill: optionalText(fields.skill, "skill", 64) }
|
|
4543
4727
|
});
|
|
4544
4728
|
case "draft": return service.draft(root, memoryInputText(fields.id, "id", 100));
|
|
4729
|
+
case "draft-preferences": return service.draftPreferences();
|
|
4545
4730
|
case "reviews": return service.reviews({
|
|
4546
4731
|
...typeof fields.sessionId === "string" ? { sessionId: fields.sessionId } : {},
|
|
4547
4732
|
...typeof fields.limit === "number" ? { limit: fields.limit } : {}
|
|
@@ -4629,6 +4814,9 @@ function createSkillsMemorySource(config = {}) {
|
|
|
4629
4814
|
case "suggestion-dismiss":
|
|
4630
4815
|
value = await service.dismissSuggestion(memoryInputText(fields.id, "id", 100));
|
|
4631
4816
|
break;
|
|
4817
|
+
case "suggestion-delete":
|
|
4818
|
+
value = await service.deleteSuggestion(memoryInputText(fields.id, "id", 100));
|
|
4819
|
+
break;
|
|
4632
4820
|
case "suggestion-status": {
|
|
4633
4821
|
const status = fields.status;
|
|
4634
4822
|
if (status !== "drafting" && status !== "pending") throw new Error("status must be drafting or pending");
|
|
@@ -4685,6 +4873,22 @@ function createSkillsMemorySource(config = {}) {
|
|
|
4685
4873
|
case "draft-discard":
|
|
4686
4874
|
value = await service.discardDraft(memoryInputText(fields.id, "id", 100));
|
|
4687
4875
|
break;
|
|
4876
|
+
case "draft-delete":
|
|
4877
|
+
value = await service.deleteDraft(memoryInputText(fields.id, "id", 100));
|
|
4878
|
+
break;
|
|
4879
|
+
case "draft-preference": {
|
|
4880
|
+
const slot = optionalText(fields.slot, "slot", 10);
|
|
4881
|
+
if (slot !== void 0 && slot !== "draft" && slot !== "run") throw new Error("slot must be draft or run");
|
|
4882
|
+
const suggestionId = optionalText(fields.suggestionId, "suggestionId", 100);
|
|
4883
|
+
if (suggestionId !== void 0 && slot === "run") throw new Error("a suggestion pick cannot be a backtest pick");
|
|
4884
|
+
value = await service.setDraftPreference({
|
|
4885
|
+
...slot === void 0 ? {} : { slot },
|
|
4886
|
+
...suggestionId === void 0 ? {} : { suggestionId },
|
|
4887
|
+
...optionalText(fields.skillName, "skillName", 64) === void 0 ? {} : { skillName: optionalText(fields.skillName, "skillName", 64) },
|
|
4888
|
+
model: draftModelInput(fields.model)
|
|
4889
|
+
});
|
|
4890
|
+
break;
|
|
4891
|
+
}
|
|
4688
4892
|
case "run-start":
|
|
4689
4893
|
value = await service.startRun({
|
|
4690
4894
|
skillName: memoryInputText(fields.skillName, "skillName", 64),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { MnemonSourceManagementClient } from 'dsh-mnemon/client';
|
|
2
|
-
import type { BacktestRun, CatalogView, DraftView, EvalCase, MarketPreview, MarketRegistry, MarketSearchResult, MarketView, EvidencePack, ReviewLogEntry, ReviewRecordResult, RunTarget, SkillDocument, SkillDraft, SkillRootKind, SkillSuggestion, SkillSummary, SkillVersion, SkillsOverview, SuggestionStatus, SuggestionView, Trajectory, TrajectorySummary, TrashEntry, TrashRestoreInput, TrashRestorePreview } from '../contracts.ts';
|
|
1
|
+
import type { MnemonSourceManagementClient, TaskAgentModelCatalog } from 'dsh-mnemon/client';
|
|
2
|
+
import type { BacktestRun, CatalogView, DraftPreferencesView, DraftView, EvalCase, MarketPreview, MarketRegistry, MarketSearchResult, MarketView, EvidencePack, ModelRoute, ReviewLogEntry, ReviewRecordResult, RunTarget, SkillDocument, SkillDraft, SkillRootKind, SkillSuggestion, SkillSummary, SkillVersion, SkillsOverview, SuggestionStatus, SuggestionView, Trajectory, TrajectorySummary, TrashEntry, TrashRestoreInput, TrashRestorePreview } from '../contracts.ts';
|
|
3
3
|
export interface CaseDraft {
|
|
4
4
|
id?: string;
|
|
5
5
|
skillName: string;
|
|
@@ -75,6 +75,14 @@ export interface SkillsPageClient {
|
|
|
75
75
|
}>;
|
|
76
76
|
restoreVersion(key: string, versionId: string, expectedHash: string): Promise<SkillSummary>;
|
|
77
77
|
dismissSuggestion(id: string): Promise<SkillSuggestion>;
|
|
78
|
+
/** Delete a waiting suggestion and any draft written for it. */
|
|
79
|
+
deleteSuggestion(id: string): Promise<{
|
|
80
|
+
deleted: string;
|
|
81
|
+
}>;
|
|
82
|
+
/** Delete a draft; its suggestion goes back to waiting. */
|
|
83
|
+
deleteDraft(id: string): Promise<{
|
|
84
|
+
deleted: string;
|
|
85
|
+
}>;
|
|
78
86
|
pinTrajectory(id: string, pinned: boolean): Promise<TrajectorySummary>;
|
|
79
87
|
deleteTrajectory(id: string): Promise<{
|
|
80
88
|
deleted: string;
|
|
@@ -92,6 +100,18 @@ export interface SkillsPageClient {
|
|
|
92
100
|
discardDraft(id: string): Promise<SkillDraft>;
|
|
93
101
|
/** Attach a baseline run to the draft it is compared with. */
|
|
94
102
|
linkRun(draftId: string, runId: string): Promise<void>;
|
|
103
|
+
/** The model each piece of model work follows: the two global picks and their per-item overrides. */
|
|
104
|
+
draftPreferences(): Promise<DraftPreferencesView>;
|
|
105
|
+
/** Pick the model for drafts, for backtests, or for one suggestion or skill; `null` follows the task Agent again. */
|
|
106
|
+
saveDraftModel(model: ModelRoute | null): Promise<DraftPreferencesView>;
|
|
107
|
+
saveSuggestionModel(id: string, model: ModelRoute | null): Promise<DraftPreferencesView>;
|
|
108
|
+
saveRunModel(model: ModelRoute | null): Promise<DraftPreferencesView>;
|
|
109
|
+
saveSkillRunModel(skillName: string, model: ModelRoute | null): Promise<DraftPreferencesView>;
|
|
110
|
+
/**
|
|
111
|
+
* The routes and model directory a page may offer. Rejects when the Host
|
|
112
|
+
* offers none, which is also when the page hides its pickers.
|
|
113
|
+
*/
|
|
114
|
+
models(): Promise<TaskAgentModelCatalog>;
|
|
95
115
|
market(): Promise<MarketView>;
|
|
96
116
|
marketPreview(registryId: string, name: string): Promise<MarketPreview>;
|
|
97
117
|
/** Search the search sources; the page filters the listed ones itself. */
|
|
@@ -118,6 +138,8 @@ export interface SkillsPageClient {
|
|
|
118
138
|
skill?: string;
|
|
119
139
|
guidance?: string;
|
|
120
140
|
targetRoot?: SkillRootKind;
|
|
141
|
+
provider?: string;
|
|
142
|
+
model?: string;
|
|
121
143
|
}): Promise<{
|
|
122
144
|
started: true;
|
|
123
145
|
}>;
|
|
@@ -125,6 +147,8 @@ export interface SkillsPageClient {
|
|
|
125
147
|
skillName: string;
|
|
126
148
|
target: RunTarget;
|
|
127
149
|
caseIds?: string[];
|
|
150
|
+
provider?: string;
|
|
151
|
+
model?: string;
|
|
128
152
|
}): Promise<{
|
|
129
153
|
runId: string;
|
|
130
154
|
}>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FieldOption, TaskAgentModelCatalog } from 'dsh-mnemon/client';
|
|
2
|
+
import type { DraftPreferencesView, ModelRoute } from '../contracts.ts';
|
|
3
|
+
import { type SkillsTranslate } from './presentation.ts';
|
|
4
|
+
import type { SkillsPageContext } from './ui.tsx';
|
|
5
|
+
/**
|
|
6
|
+
* The model pickers the Evolution and Evaluation pages share: which model
|
|
7
|
+
* drafts and backtests run on, globally and per item, offered from the same
|
|
8
|
+
* directory the task Agent resolves its own route against.
|
|
9
|
+
*
|
|
10
|
+
* A pick is stored as `provider|model`, so one menu value names one route, and
|
|
11
|
+
* an absent pick is `auto`: the page follows the task Agent, as it did before
|
|
12
|
+
* the user picked anything.
|
|
13
|
+
*/
|
|
14
|
+
/** The menu value of a pick; `auto` asks for no pick at all. */
|
|
15
|
+
export declare function modelOptionValue(model: ModelRoute | null | undefined): string;
|
|
16
|
+
/** The pick a menu value names; `auto` and a malformed value both pick nothing. */
|
|
17
|
+
export declare function modelOptionPick(value: string): ModelRoute | null;
|
|
18
|
+
/**
|
|
19
|
+
* Follow-default first, then every Provider's models. The first entry exists
|
|
20
|
+
* before the directory arrives, so the toolbar holds its place instead of
|
|
21
|
+
* appearing a round trip later than the rest of the page.
|
|
22
|
+
*/
|
|
23
|
+
export declare function modelOptionsFrom(models: TaskAgentModelCatalog | null, t: SkillsTranslate, autoKey: 'tests.modelAuto' | 'evolution.modelAuto'): FieldOption<string>[];
|
|
24
|
+
/** The same list for one card: follow-default, then the directory without its own default entry. */
|
|
25
|
+
export declare function modelCardOptionsFrom(modelOptions: readonly FieldOption<string>[], t: SkillsTranslate, autoKey: 'tests.modelAuto' | 'evolution.modelAuto'): FieldOption<string>[];
|
|
26
|
+
/** What the pickers read and write, and the directory behind them. */
|
|
27
|
+
export interface ModelChoice {
|
|
28
|
+
/** `null` until the directory arrives; the pickers are disabled meanwhile. */
|
|
29
|
+
models: TaskAgentModelCatalog | null;
|
|
30
|
+
preferences: DraftPreferencesView;
|
|
31
|
+
/** Every routable model, follow-default first. */
|
|
32
|
+
options: FieldOption<string>[];
|
|
33
|
+
/** The same for one card's own pick. */
|
|
34
|
+
cardOptions: FieldOption<string>[];
|
|
35
|
+
saveDraftModel(model: ModelRoute | null): void;
|
|
36
|
+
saveSuggestionModel(id: string, model: ModelRoute | null): void;
|
|
37
|
+
saveRunModel(model: ModelRoute | null): void;
|
|
38
|
+
saveSkillRunModel(skillName: string, model: ModelRoute | null): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the directory and the stored picks alongside the page's own data, and
|
|
42
|
+
* write one pick at a time. A write shows locally first so the menu answers at
|
|
43
|
+
* once, then takes the Host's answer, which is also what a second window sees.
|
|
44
|
+
*/
|
|
45
|
+
export declare function useModelChoice(context: SkillsPageContext, autoKey: 'tests.modelAuto' | 'evolution.modelAuto'): ModelChoice;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type JSX } from 'react';
|
|
2
|
-
import type { BacktestRun } from '../contracts.ts';
|
|
2
|
+
import type { BacktestRun, ModelRoute } from '../contracts.ts';
|
|
3
3
|
import type { SkillsPageContext } from './ui.tsx';
|
|
4
4
|
/**
|
|
5
5
|
* A skill's test cases and its backtests: the cases with a switch each, one
|
|
@@ -9,6 +9,7 @@ import type { SkillsPageContext } from './ui.tsx';
|
|
|
9
9
|
export declare function SkillTests(props: {
|
|
10
10
|
context: SkillsPageContext;
|
|
11
11
|
skillName: string;
|
|
12
|
+
runModel?: ModelRoute | null;
|
|
12
13
|
}): JSX.Element;
|
|
13
14
|
/** One run's cases: the verdict, each check with the judge's note, and what the simulated run did and answered. */
|
|
14
15
|
export declare function RunDetail(props: {
|
package/lib/types/contracts.d.ts
CHANGED
|
@@ -221,6 +221,30 @@ export interface RunTarget {
|
|
|
221
221
|
draftId?: string;
|
|
222
222
|
versionId?: string;
|
|
223
223
|
}
|
|
224
|
+
/** One Provider and model, as a user picked it; never carries a reasoning effort. */
|
|
225
|
+
export interface ModelRoute {
|
|
226
|
+
provider: string;
|
|
227
|
+
model: string;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The models the user picked for skill work, kept beside the ledger. Every
|
|
231
|
+
* entry is optional: absent means "follow the task Agent's own route".
|
|
232
|
+
* `suggestionModels` is keyed by suggestion id, `skillRunModels` by skill name.
|
|
233
|
+
*/
|
|
234
|
+
export interface DraftPreferences {
|
|
235
|
+
version: 1;
|
|
236
|
+
draftModel?: ModelRoute;
|
|
237
|
+
runModel?: ModelRoute;
|
|
238
|
+
suggestionModels: Record<string, ModelRoute>;
|
|
239
|
+
skillRunModels: Record<string, ModelRoute>;
|
|
240
|
+
}
|
|
241
|
+
/** What the pickers read: the stored picks, named the way the pages use them. */
|
|
242
|
+
export interface DraftPreferencesView {
|
|
243
|
+
model?: ModelRoute;
|
|
244
|
+
runModel?: ModelRoute;
|
|
245
|
+
suggestionModels: Record<string, ModelRoute>;
|
|
246
|
+
skillRunModels: Record<string, ModelRoute>;
|
|
247
|
+
}
|
|
224
248
|
export interface CaseResult {
|
|
225
249
|
caseId: string;
|
|
226
250
|
title: string;
|
|
@@ -280,6 +304,10 @@ export interface ReviewLogEntry {
|
|
|
280
304
|
}
|
|
281
305
|
/** How the Source admits a review's suggestions; the Host sends its settings with each review. */
|
|
282
306
|
export interface SkillsPolicy {
|
|
307
|
+
/** Whether a review may propose new skills; omitted by older Hosts, default true. */
|
|
308
|
+
allowCreate?: boolean;
|
|
309
|
+
/** Whether a review may improve existing skills; omitted by older Hosts, default true. */
|
|
310
|
+
allowUpdate?: boolean;
|
|
283
311
|
minConfidence: number;
|
|
284
312
|
expiryDays: number;
|
|
285
313
|
maxPendingPerSession: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-mnemon-source-skills",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Skills Source for Composable View Memory: DSH skill management, evolution suggestions, evidence trajectories and eval sets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
56
|
-
"dsh-mnemon": "^0.5.
|
|
56
|
+
"dsh-mnemon": "^0.5.37",
|
|
57
57
|
"react": "^18.2.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@types/node": "^22.0.0",
|
|
63
63
|
"@types/react": "^18.3.0",
|
|
64
64
|
"@types/react-dom": "^18.3.0",
|
|
65
|
-
"dsh-mnemon": "^0.5.
|
|
65
|
+
"dsh-mnemon": "^0.5.37",
|
|
66
66
|
"jsdom": "^26.0.0",
|
|
67
67
|
"react": "^18.2.0",
|
|
68
68
|
"react-dom": "^18.2.0",
|
|
@@ -335,7 +335,17 @@
|
|
|
335
335
|
"trash.original": "原位置",
|
|
336
336
|
"trash.chooseHint": "可选择恢复位置或改名。同名替换需要明确确认,现有技能会保留在回收站。",
|
|
337
337
|
"trash.replace": "替换同名技能,并将现有版本保留在回收站",
|
|
338
|
-
"trash.confirmName": "输入 {name} 确认彻底删除"
|
|
338
|
+
"trash.confirmName": "输入 {name} 确认彻底删除",
|
|
339
|
+
"evolution.model": "全局模型",
|
|
340
|
+
"evolution.modelCard": "所选草案模型",
|
|
341
|
+
"evolution.modelAuto": "跟随默认",
|
|
342
|
+
"evolution.modelAutoHint": "使用当前任务 Agent 的模型",
|
|
343
|
+
"evolution.deleteTitle": "删除这条建议?",
|
|
344
|
+
"evolution.deleteBody": "它生成的草案和证据引用会一起删掉,无法恢复。",
|
|
345
|
+
"evolution.deleted": "已删除",
|
|
346
|
+
"tests.model": "全局模型",
|
|
347
|
+
"tests.modelCard": "所选回测模型",
|
|
348
|
+
"tests.modelAuto": "跟随默认"
|
|
339
349
|
},
|
|
340
350
|
"en": {
|
|
341
351
|
"page.title": "Skills",
|
|
@@ -673,6 +683,16 @@
|
|
|
673
683
|
"trash.original": "Original location",
|
|
674
684
|
"trash.chooseHint": "Choose a location or a different name. Replacing a skill requires confirmation and keeps its current version in the Trash.",
|
|
675
685
|
"trash.replace": "Replace the existing skill and keep its current version in the Trash",
|
|
676
|
-
"trash.confirmName": "Type {name} to confirm permanent deletion"
|
|
686
|
+
"trash.confirmName": "Type {name} to confirm permanent deletion",
|
|
687
|
+
"evolution.model": "Global model",
|
|
688
|
+
"evolution.modelCard": "Selected model",
|
|
689
|
+
"evolution.modelAuto": "Follow default",
|
|
690
|
+
"evolution.modelAutoHint": "Use the model of the current task Agent",
|
|
691
|
+
"evolution.deleteTitle": "Delete this suggestion?",
|
|
692
|
+
"evolution.deleteBody": "Its draft and the evidence references it cites go with it, and this cannot be undone.",
|
|
693
|
+
"evolution.deleted": "Deleted",
|
|
694
|
+
"tests.model": "Global model",
|
|
695
|
+
"tests.modelCard": "Selected model",
|
|
696
|
+
"tests.modelAuto": "Follow default"
|
|
677
697
|
}
|
|
678
698
|
}
|
|
@@ -180,6 +180,12 @@
|
|
|
180
180
|
.skList { display: flex; flex-direction: column; overflow: hidden; padding-bottom: 6px; }
|
|
181
181
|
.skPane { padding: clamp(16px, 2vw, 24px); overflow: hidden; }
|
|
182
182
|
|
|
183
|
+
/* Keep the evolution inbox beside its detail while browsing long lists. */
|
|
184
|
+
.skEvolutionSplit { height: max(320px, calc(100dvh - 320px)); min-height: 0; }
|
|
185
|
+
.skEvolutionSplit > .skList,
|
|
186
|
+
.skEvolutionSplit > .skPane { min-height: 0; overflow-y: auto; overscroll-behavior-y: contain; }
|
|
187
|
+
.skEvolutionSplit > .skList > * { flex-shrink: 0; }
|
|
188
|
+
|
|
183
189
|
.skListBar { display: flex; align-items: center; gap: 8px; padding: 10px; border-bottom: .5px solid var(--mn-line); }
|
|
184
190
|
.skListBar > :first-child { min-width: 0; flex: 1; }
|
|
185
191
|
.skListNote { margin: 0; padding: 18px 16px; color: var(--mn-faint); font-size: 12px; line-height: 18px; text-align: center; }
|
|
@@ -574,6 +580,8 @@
|
|
|
574
580
|
@media (max-width: 860px) {
|
|
575
581
|
.skSplit { grid-template-columns: minmax(0, 1fr); }
|
|
576
582
|
.skList { max-height: 360px; overflow-y: auto; }
|
|
583
|
+
.skEvolutionSplit { height: auto; }
|
|
584
|
+
.skEvolutionSplit > .skPane { overflow: visible; overscroll-behavior-y: auto; }
|
|
577
585
|
}
|
|
578
586
|
|
|
579
587
|
@media (max-width: 560px) {
|