@lmzhen/dsh-evolution-core 0.26.15 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -1
- package/lib/composition/index.js +3 -0
- package/lib/composition-D8Y3-ROd.js +604 -0
- package/lib/ensure-MEYMj9jn.js +222 -0
- package/lib/errors-Br2-uUSK.js +57 -0
- package/lib/http-routes-BP-S3KV-.js +149 -0
- package/lib/http-routes.js +2 -0
- package/lib/index.js +777 -840
- package/lib/restrictor.js +37 -0
- package/lib/subagent/index.js +186 -0
- package/lib/types/citations.js +192 -0
- package/lib/types/{preset-composition.d.ts → composition/composer.d.ts} +2 -2
- package/lib/types/composition/composer.js +681 -0
- package/lib/types/composition/ensure.d.ts +10 -0
- package/lib/types/composition/ensure.js +95 -0
- package/lib/types/composition/index.d.ts +5 -0
- package/lib/types/composition/index.js +7 -0
- package/lib/types/composition/preset-ids.d.ts +17 -0
- package/lib/types/composition/preset-ids.js +21 -0
- package/lib/types/composition/restricted-preset.d.ts +36 -0
- package/lib/types/composition/restricted-preset.js +42 -0
- package/lib/types/constants.js +223 -0
- package/lib/types/cost.js +67 -0
- package/lib/types/curator.d.ts +2 -0
- package/lib/types/curator.js +346 -0
- package/lib/types/diagnostics.d.ts +26 -0
- package/lib/types/diagnostics.js +23 -0
- package/lib/types/document-body.js +91 -0
- package/lib/types/drift-signals.js +319 -0
- package/lib/types/env.js +25 -0
- package/lib/types/errors.d.ts +4 -1
- package/lib/types/errors.js +59 -0
- package/lib/types/events.js +16 -0
- package/lib/types/evidence.js +65 -0
- package/lib/types/evolution-events.js +479 -0
- package/lib/types/fold-untrusted.js +44 -0
- package/lib/types/frontmatter.js +623 -0
- package/lib/types/fuzzy-match.js +135 -0
- package/lib/types/gates.js +51 -0
- package/lib/types/guards.js +27 -0
- package/lib/types/health-baseline.js +63 -0
- package/lib/types/http-routes.d.ts +67 -35
- package/lib/types/http-routes.js +135 -0
- package/lib/types/index.d.ts +13 -2
- package/lib/types/index.js +101 -0
- package/lib/types/instance-scope.js +69 -0
- package/lib/types/io.js +675 -0
- package/lib/types/learn-prompt.js +42 -0
- package/lib/types/limits.js +63 -0
- package/lib/types/live.js +107 -0
- package/lib/types/long-runs.js +96 -0
- package/lib/types/memory-store.d.ts +0 -16
- package/lib/types/memory-store.js +606 -0
- package/lib/types/mutations.js +95 -0
- package/lib/types/namespace-migration.js +156 -0
- package/lib/types/numeric.js +47 -0
- package/lib/types/opt-in.js +101 -0
- package/lib/types/package-identity.d.ts +14 -0
- package/lib/types/package-identity.js +47 -0
- package/lib/types/params.js +328 -0
- package/lib/types/payload.js +36 -0
- package/lib/types/platform-line.generated.d.ts +10 -0
- package/lib/types/platform-line.generated.js +12 -0
- package/lib/types/platform-services.d.ts +2 -2
- package/lib/types/platform-services.js +52 -0
- package/lib/types/probe.d.ts +9 -3
- package/lib/types/probe.js +122 -0
- package/lib/types/prompts.js +409 -0
- package/lib/types/quality.js +308 -0
- package/lib/types/reachability.d.ts +46 -0
- package/lib/types/reachability.js +122 -0
- package/lib/types/redact.js +257 -0
- package/lib/types/reference-rewrite.js +144 -0
- package/lib/types/reports.js +110 -0
- package/lib/types/restrictor.d.ts +11 -0
- package/lib/types/restrictor.js +48 -0
- package/lib/types/review-channel.js +59 -0
- package/lib/types/run-registry.js +345 -0
- package/lib/types/scope.js +7 -0
- package/lib/types/serial.js +17 -0
- package/lib/types/session-fold.d.ts +15 -0
- package/lib/types/session-fold.js +28 -0
- package/lib/types/session-projection.js +295 -0
- package/lib/types/signals.js +209 -0
- package/lib/types/skill-discovery.js +66 -0
- package/lib/types/skill-health.js +134 -0
- package/lib/types/skill-history.js +745 -0
- package/lib/types/skill-lint.js +112 -0
- package/lib/types/skill-reads.js +67 -0
- package/lib/types/skill-row-cells.js +123 -0
- package/lib/types/skill-store.d.ts +34 -3
- package/lib/types/skill-store.js +3528 -0
- package/lib/types/state-store.js +60 -0
- package/lib/types/store-ledger.d.ts +44 -0
- package/lib/types/store-ledger.js +117 -0
- package/lib/types/subagent/channel.d.ts +4 -0
- package/lib/types/subagent/channel.js +131 -0
- package/lib/types/subagent/index.d.ts +4 -0
- package/lib/types/subagent/index.js +5 -0
- package/lib/types/subagent/readiness.d.ts +6 -0
- package/lib/types/subagent/readiness.js +35 -0
- package/lib/types/subagent/types.d.ts +58 -0
- package/lib/types/subagent/types.js +2 -0
- package/lib/types/threats.js +338 -0
- package/lib/types/tool-dispatch.d.ts +5 -0
- package/lib/types/tool-dispatch.js +535 -0
- package/lib/types/tree-write.d.ts +53 -0
- package/lib/types/tree-write.js +40 -0
- package/lib/types/unit-members.js +121 -0
- package/lib/types/usage.js +474 -0
- package/lib/types/write-authority.d.ts +87 -0
- package/lib/types/write-authority.js +23 -0
- package/lib/types/write-gates.d.ts +111 -0
- package/lib/types/write-gates.js +344 -0
- package/lib/types/write-inventory.js +158 -0
- package/package.json +39 -11
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
2
|
+
import { type RestrictedPresetRequest, type RestrictedPresetState } from './restricted-preset.ts';
|
|
3
|
+
export interface EnsureRestrictedPresetArgs extends RestrictedPresetRequest {
|
|
4
|
+
}
|
|
5
|
+
export declare function ensureRestrictedPreset(ctx: Context, req: EnsureRestrictedPresetArgs): Promise<RestrictedPresetState>;
|
|
6
|
+
/** Read-only view of the current registration (doctor runtime row). */
|
|
7
|
+
export declare function restrictedPresetState(ctx: Context, kind: RestrictedPresetRequest['kind']): RestrictedPresetState | undefined;
|
|
8
|
+
/** Release the registration for one kind (HMR/tests); the fiber effect also calls this. */
|
|
9
|
+
export declare function disposeRestrictedPreset(ctx: Context, kind: RestrictedPresetRequest['kind']): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=ensure.d.ts.map
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { normalizeAllow } from "./preset-ids.js";
|
|
2
|
+
import { composeRestrictedPresetDefinition, } from "./restricted-preset.js";
|
|
3
|
+
const SLOTS = new WeakMap();
|
|
4
|
+
function slotFor(ctx, kind) {
|
|
5
|
+
let byKind = SLOTS.get(ctx);
|
|
6
|
+
if (byKind === undefined) {
|
|
7
|
+
byKind = new Map();
|
|
8
|
+
SLOTS.set(ctx, byKind);
|
|
9
|
+
}
|
|
10
|
+
let slot = byKind.get(kind);
|
|
11
|
+
if (slot === undefined) {
|
|
12
|
+
slot = {};
|
|
13
|
+
byKind.set(kind, slot);
|
|
14
|
+
}
|
|
15
|
+
return slot;
|
|
16
|
+
}
|
|
17
|
+
function registryOf(ctx) {
|
|
18
|
+
const candidate = ctx.get('agentPresets');
|
|
19
|
+
if (candidate === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
if (typeof candidate.readDocument !== 'function' || typeof candidate.register !== 'function')
|
|
22
|
+
return undefined;
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
function installReleaseEffect(ctx, slot) {
|
|
26
|
+
if (slot.effectInstalled === true)
|
|
27
|
+
return;
|
|
28
|
+
const effect = ctx.effect;
|
|
29
|
+
if (effect === undefined)
|
|
30
|
+
return;
|
|
31
|
+
slot.effectInstalled = true;
|
|
32
|
+
effect.call(ctx, () => () => { void releaseRestrictedPreset(ctx, undefined, slot); }, 'evolution-restricted-preset');
|
|
33
|
+
}
|
|
34
|
+
async function releaseRestrictedPreset(ctx, kind, explicitSlot) {
|
|
35
|
+
const slot = explicitSlot ?? (kind === undefined ? undefined : slotFor(ctx, kind));
|
|
36
|
+
if (slot === undefined)
|
|
37
|
+
return;
|
|
38
|
+
const release = slot.release;
|
|
39
|
+
slot.release = undefined;
|
|
40
|
+
slot.readyKey = undefined;
|
|
41
|
+
slot.state = undefined;
|
|
42
|
+
if (release !== undefined)
|
|
43
|
+
await release();
|
|
44
|
+
}
|
|
45
|
+
export async function ensureRestrictedPreset(ctx, req) {
|
|
46
|
+
const allow = normalizeAllow(req.allow);
|
|
47
|
+
const key = req.parentPresetId + '\n' + allow.join('\n');
|
|
48
|
+
const slot = slotFor(ctx, req.kind);
|
|
49
|
+
if (slot.readyKey === key && slot.state !== undefined && slot.state.kind === 'ready')
|
|
50
|
+
return slot.state;
|
|
51
|
+
if (slot.inflight !== undefined)
|
|
52
|
+
return slot.inflight;
|
|
53
|
+
slot.inflight = (async () => {
|
|
54
|
+
await releaseRestrictedPreset(ctx, req.kind, slot);
|
|
55
|
+
if (allow.length === 0) {
|
|
56
|
+
slot.state = { kind: 'failed', code: 'E-324', reason: 'allow set is empty' };
|
|
57
|
+
return slot.state;
|
|
58
|
+
}
|
|
59
|
+
const registry = registryOf(ctx);
|
|
60
|
+
if (registry === undefined) {
|
|
61
|
+
slot.state = { kind: 'failed', code: 'E-324', reason: 'agentPresets service unavailable or incomplete' };
|
|
62
|
+
return slot.state;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const document = await registry.readDocument(req.parentPresetId);
|
|
66
|
+
const definition = composeRestrictedPresetDefinition(document, { kind: req.kind, parentPresetId: req.parentPresetId, allow });
|
|
67
|
+
const release = await registry.register(definition);
|
|
68
|
+
slot.release = release;
|
|
69
|
+
slot.readyKey = key;
|
|
70
|
+
slot.state = { kind: 'ready', presetId: definition.id, parentPresetId: req.parentPresetId, allow };
|
|
71
|
+
installReleaseEffect(ctx, slot);
|
|
72
|
+
return slot.state;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
slot.readyKey = undefined;
|
|
76
|
+
slot.state = { kind: 'failed', code: 'E-324', reason: error instanceof Error ? error.message : String(error) };
|
|
77
|
+
return slot.state;
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
80
|
+
try {
|
|
81
|
+
return await slot.inflight;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
slot.inflight = undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/** Read-only view of the current registration (doctor runtime row). */
|
|
88
|
+
export function restrictedPresetState(ctx, kind) {
|
|
89
|
+
return SLOTS.get(ctx)?.get(kind)?.state;
|
|
90
|
+
}
|
|
91
|
+
/** Release the registration for one kind (HMR/tests); the fiber effect also calls this. */
|
|
92
|
+
export async function disposeRestrictedPreset(ctx, kind) {
|
|
93
|
+
await releaseRestrictedPreset(ctx, kind);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=ensure.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// Composition subpath: the ONE composer implementation + preset identities + the restricted
|
|
2
|
+
// preset lifecycle (see restricted-preset.ts / ensure.ts).
|
|
3
|
+
export * from "./composer.js";
|
|
4
|
+
export * from "./preset-ids.js";
|
|
5
|
+
export * from "./restricted-preset.js";
|
|
6
|
+
export * from "./ensure.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type RestrictedPresetKind = 'reviewer' | 'maintainer';
|
|
2
|
+
export declare const EVOLUTION_PRESET_IDS: readonly ["evolution", "evolution-ptc", "evolution-cordis", "evolution-minimal"];
|
|
3
|
+
export declare const REVIEWER_PRESET_ID: "evolution-reviewer-readonly";
|
|
4
|
+
export declare const MAINTAINER_PRESET_ID: "evolution-maintainer-readonly";
|
|
5
|
+
export declare const RESTRICTED_TOOLS_ROW_ID: "evolution-restricted-tools";
|
|
6
|
+
export declare const RESTRICTOR_MODULE: string;
|
|
7
|
+
export declare const RESTRICTED_PRESET_DELIVERY: "runtime" | "installer";
|
|
8
|
+
export interface RestrictedPresetDisplay {
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly description: string;
|
|
11
|
+
readonly order: number;
|
|
12
|
+
}
|
|
13
|
+
export declare const RESTRICTED_PRESET_DISPLAY: Readonly<Record<RestrictedPresetKind, RestrictedPresetDisplay>>;
|
|
14
|
+
export declare function restrictedPresetId(kind: RestrictedPresetKind): string;
|
|
15
|
+
/** Trim, drop blanks, de-duplicate and sort: ONE spelling for an allow/deny set. */
|
|
16
|
+
export declare function normalizeAllow(names: readonly string[]): readonly string[];
|
|
17
|
+
//# sourceMappingURL=preset-ids.d.ts.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The frozen preset identities and restricted-preset display/transport constants.
|
|
2
|
+
import { scopedPackageName } from "../package-identity.js";
|
|
3
|
+
// Derived from bases.json (single source: packages/evolution-agent/bases.json).
|
|
4
|
+
export const EVOLUTION_PRESET_IDS = ['evolution', 'evolution-ptc', 'evolution-cordis', 'evolution-minimal'];
|
|
5
|
+
export const REVIEWER_PRESET_ID = 'evolution-reviewer-readonly';
|
|
6
|
+
export const MAINTAINER_PRESET_ID = 'evolution-maintainer-readonly';
|
|
7
|
+
export const RESTRICTED_TOOLS_ROW_ID = 'evolution-restricted-tools';
|
|
8
|
+
export const RESTRICTOR_MODULE = scopedPackageName('dsh-evolution-core/restrictor', import.meta.url);
|
|
9
|
+
export const RESTRICTED_PRESET_DELIVERY = 'runtime';
|
|
10
|
+
export const RESTRICTED_PRESET_DISPLAY = {
|
|
11
|
+
reviewer: { name: 'evolution reviewer (read-only)', description: 'Internal restricted preset used by the family review channel.', order: 900 },
|
|
12
|
+
maintainer: { name: 'evolution maintainer (read-only)', description: 'Internal restricted preset used by the family maintenance channel.', order: 901 },
|
|
13
|
+
};
|
|
14
|
+
export function restrictedPresetId(kind) {
|
|
15
|
+
return kind === 'reviewer' ? REVIEWER_PRESET_ID : MAINTAINER_PRESET_ID;
|
|
16
|
+
}
|
|
17
|
+
/** Trim, drop blanks, de-duplicate and sort: ONE spelling for an allow/deny set. */
|
|
18
|
+
export function normalizeAllow(names) {
|
|
19
|
+
return [...new Set(names.map(name => name.trim()).filter(name => name !== ''))].sort();
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=preset-ids.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as yaml from 'js-yaml';
|
|
2
|
+
export declare const entryListSchema: yaml.Schema;
|
|
3
|
+
import { type RestrictedPresetKind } from './preset-ids.ts';
|
|
4
|
+
export interface PresetDocumentLike {
|
|
5
|
+
readonly agentPreset: string;
|
|
6
|
+
readonly content: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PresetRegistryLike {
|
|
9
|
+
readDocument(agentPreset: string): Promise<PresetDocumentLike>;
|
|
10
|
+
register(definition: RestrictedPresetDefinition): Promise<() => Promise<void>>;
|
|
11
|
+
}
|
|
12
|
+
export interface RestrictedPresetDefinition {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly description: string;
|
|
16
|
+
readonly order: number;
|
|
17
|
+
readonly plugins: readonly unknown[];
|
|
18
|
+
}
|
|
19
|
+
export interface RestrictedPresetRequest {
|
|
20
|
+
readonly kind: RestrictedPresetKind;
|
|
21
|
+
readonly parentPresetId: string;
|
|
22
|
+
readonly allow: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
export type RestrictedPresetState = {
|
|
25
|
+
readonly kind: 'ready';
|
|
26
|
+
readonly presetId: string;
|
|
27
|
+
readonly parentPresetId: string;
|
|
28
|
+
readonly allow: readonly string[];
|
|
29
|
+
} | {
|
|
30
|
+
readonly kind: 'failed';
|
|
31
|
+
readonly code: 'E-324';
|
|
32
|
+
readonly reason: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function parsePresetRows(content: string): readonly unknown[];
|
|
35
|
+
export declare function composeRestrictedPresetDefinition(source: PresetDocumentLike, req: RestrictedPresetRequest): RestrictedPresetDefinition;
|
|
36
|
+
//# sourceMappingURL=restricted-preset.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Build one restricted preset DEFINITION by cloning the live parent preset rows and appending the
|
|
2
|
+
// single restrictor row. The parent rows are read through agentPresets.readDocument (the Loader's
|
|
3
|
+
// own YAML dialect), so the child keeps every base row the parent had.
|
|
4
|
+
import * as yaml from 'js-yaml';
|
|
5
|
+
import { errorText } from "../errors.js";
|
|
6
|
+
// The Loader's entry-list YAML dialect: `JSON_SCHEMA` plus one `!!js` scalar tag whose node keeps
|
|
7
|
+
// the source text for activation-time evaluation. It is DEFINED here rather than imported from the
|
|
8
|
+
// platform's include package: the family library has to load in a tree where only the family libs
|
|
9
|
+
// are built (the CI overlay runs the family tests against platform sources without building the
|
|
10
|
+
// platform), and the two definitions must round-trip the same documents. The shape (JSON_SCHEMA
|
|
11
|
+
// extended with the js tag, node `{ __jsExpr }`) is pinned by the parent document the registry
|
|
12
|
+
// hands back and by its golden tests.
|
|
13
|
+
const JsExpr = new yaml.Type('tag:yaml.org,2002:js', {
|
|
14
|
+
kind: 'scalar',
|
|
15
|
+
resolve: (data) => typeof data === 'string',
|
|
16
|
+
construct: (data) => ({ __jsExpr: data }),
|
|
17
|
+
represent: data => data.__jsExpr,
|
|
18
|
+
});
|
|
19
|
+
export const entryListSchema = yaml.JSON_SCHEMA.extend(JsExpr);
|
|
20
|
+
import { RESTRICTED_PRESET_DISPLAY, RESTRICTED_TOOLS_ROW_ID, RESTRICTOR_MODULE, restrictedPresetId, normalizeAllow, } from "./preset-ids.js";
|
|
21
|
+
export function parsePresetRows(content) {
|
|
22
|
+
const parsed = yaml.load(content, { schema: entryListSchema });
|
|
23
|
+
if (!Array.isArray(parsed))
|
|
24
|
+
throw new Error(errorText('e-324-restricted-preset-unavailable', { a1: 'parent preset composition is not a top-level list' }));
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
export function composeRestrictedPresetDefinition(source, req) {
|
|
28
|
+
const allow = normalizeAllow(req.allow);
|
|
29
|
+
if (allow.length === 0)
|
|
30
|
+
throw new Error(errorText('e-324-restricted-preset-unavailable', { a1: 'restricted preset needs a non-empty allow set' }));
|
|
31
|
+
const rows = parsePresetRows(source.content);
|
|
32
|
+
const display = RESTRICTED_PRESET_DISPLAY[req.kind];
|
|
33
|
+
const restrictorRow = { id: RESTRICTED_TOOLS_ROW_ID, name: RESTRICTOR_MODULE, config: { allow, presetId: restrictedPresetId(req.kind) } };
|
|
34
|
+
return {
|
|
35
|
+
id: restrictedPresetId(req.kind),
|
|
36
|
+
name: display.name,
|
|
37
|
+
description: display.description,
|
|
38
|
+
order: display.order,
|
|
39
|
+
plugins: [...rows, restrictorRow],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=restricted-preset.js.map
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants for the dsh-evolution plugin family.
|
|
3
|
+
*
|
|
4
|
+
* Two classes of value live here, deliberately separated by section so future
|
|
5
|
+
* edits do not blur the semantic boundary:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Fixed protocol/format/security invariants** — changing these breaks an
|
|
8
|
+
* on-disk format, a naming/format contract, a path-security boundary, or a
|
|
9
|
+
* cross-component invariant. They are NOT exposed as deployment config.
|
|
10
|
+
*
|
|
11
|
+
* 2. **Cross-package shared tunable defaults** — the same semantic default is
|
|
12
|
+
* read (with a config override path) by more than one package (e.g.
|
|
13
|
+
* `evolution-policy` and `evolution-curator` both default `staleAfterDays`
|
|
14
|
+
* to 30). Centralizing them here means one authoritative default: a config
|
|
15
|
+
* override still applies per package, but the fallback is single-sourced.
|
|
16
|
+
*
|
|
17
|
+
* Package-private tunables (used by exactly one package) stay in that package,
|
|
18
|
+
* not here — see evolution-replay's `DEFAULT_WEIGHTS` and evolution-feedback's
|
|
19
|
+
* threshold, which are intentionally left where they are used.
|
|
20
|
+
* @module @lmzhen/dsh-evolution-core
|
|
21
|
+
*/
|
|
22
|
+
// ── Fixed protocol / format / security invariants ────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Required argument names per `skill_manage` action — the SINGLE SOURCE read
|
|
25
|
+
* by the tool's argument gate (tool-skill-manage write-gates.ts) and the plan
|
|
26
|
+
* validator (evolution-plan-validator), so the two can never drift.
|
|
27
|
+
* OPT-05 (2026-09): the plan validator used to accept a `write_file`/
|
|
28
|
+
* `remove_file` op without `file_path` while the executor required it — the
|
|
29
|
+
* staged write then failed at EVERY approve until rejected.
|
|
30
|
+
* Rows here are the op-level requirements only: `delete` additionally
|
|
31
|
+
* requires `absorbed_into` at the PLAN layer (review passes may only delete
|
|
32
|
+
* into an umbrella) and `pin`/`unpin` are tool-only actions — each consumer
|
|
33
|
+
* adds its own extras on top of this table. An empty-string argument is NOT
|
|
34
|
+
* caught here (the tool's gate deliberately lets it reach the library for a
|
|
35
|
+
* more specific remedy message); the validator adds its own `.trim()`
|
|
36
|
+
* emptiness checks for payload fields.
|
|
37
|
+
*/
|
|
38
|
+
export const SKILL_ACTION_REQUIRED_FIELDS = {
|
|
39
|
+
create: ['name', 'content'],
|
|
40
|
+
edit: ['name', 'content'],
|
|
41
|
+
update: ['name', 'content'],
|
|
42
|
+
patch: ['name', 'old_string', 'new_string'],
|
|
43
|
+
delete: ['name'],
|
|
44
|
+
write_file: ['name', 'file_path', 'file_content'],
|
|
45
|
+
remove_file: ['name', 'file_path'],
|
|
46
|
+
restructure: ['name'],
|
|
47
|
+
pin: ['name'],
|
|
48
|
+
unpin: ['name'],
|
|
49
|
+
};
|
|
50
|
+
/** Skill frontmatter `name` validated for the file name (lowercase + hyphen).
|
|
51
|
+
* 计划 B-4 (v18): tightened to the UPSTREAM `SKILL_NAME` shape
|
|
52
|
+
* (`/^[a-z0-9]+(?:-[a-z0-9]+)*$/`, packages/skill/skill/src/index.ts:21). The
|
|
53
|
+
* old form admitted trailing/consecutive hyphens, which upstream
|
|
54
|
+
* `validateCandidate` throws on — and that throw aborts the WHOLE `ctx.skills`
|
|
55
|
+
* collection. The catalog provider still filters such legacy tree entries so
|
|
56
|
+
* an existing tree cannot break a session. */
|
|
57
|
+
export const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
58
|
+
/** Allowed skill support-file subdirectories (path-traversal boundary). */
|
|
59
|
+
export const SUPPORT_DIRS = ['references', 'templates', 'scripts', 'assets'];
|
|
60
|
+
/** Delimiter between durable memory entries (on-disk storage format). */
|
|
61
|
+
export const ENTRY_DELIMITER = '\n§\n';
|
|
62
|
+
/** Built-in skill names the curator must never lifecycle-manage. */
|
|
63
|
+
export const PROTECTED_BUILTIN_SKILLS = new Set(['plan']);
|
|
64
|
+
// ── Skill-limit bounds ───────────────────────────────────────────────────────
|
|
65
|
+
// These are the values that make up `DEFAULT_SKILL_LIMITS` (the shared default
|
|
66
|
+
// the tool config lowers/raises from). Kept under their historic names so the
|
|
67
|
+
// public export surface is unchanged.
|
|
68
|
+
export const MAX_SKILL_NAME_LENGTH = 64;
|
|
69
|
+
export const MAX_DESCRIPTION_LENGTH = 1024;
|
|
70
|
+
export const MAX_SKILL_CONTENT_CHARS = 100_000;
|
|
71
|
+
/** How many content versions to retain per skill (skill-history.ts). 20 mirrors the count
|
|
72
|
+
* KiroCrew settled on for the same job (its `MAX_SKILL_VERSIONS`), and is an E2 deployment
|
|
73
|
+
* parameter — the value is a retention policy, not a protocol constant. */
|
|
74
|
+
export const DEFAULT_SKILL_VERSION_KEEP = 20;
|
|
75
|
+
/** The authoring DISCIPLINE band, taken from the upstream standard (archive §5):
|
|
76
|
+
* peer skills sit at 8-14k characters and a body pushing past 20k belongs in
|
|
77
|
+
* `references/*.md`. Deliberately separate from `MAX_SKILL_CONTENT_CHARS`: the
|
|
78
|
+
* hard ceiling is a deployment-tunable limit, this band is the authoring
|
|
79
|
+
* standard — deriving the band from the ceiling is exactly what let a 40k
|
|
80
|
+
* ceiling hide a 99k body without a single signal saying "split me" (V3). */
|
|
81
|
+
export const AUTHORING_SPLIT_LINE_CHARS = 20_000;
|
|
82
|
+
/** Upstream's conversion basis for CHARACTER LIMITS: 2.75 chars/token, labelled
|
|
83
|
+
* model-independent in the config template (\`cli-config.yaml.example:538\`), and the
|
|
84
|
+
* basis behind every quoted token figure there — memory 2200 chars ≈ 800 tokens,
|
|
85
|
+
* user 1375 ≈ 500, SKILL.md 100_000 ≈ 36k (\`tools/skill_manager_tool.py:455\`).
|
|
86
|
+
* Limits are deliberately conservative, so this is the basis a BORROWED LIMIT must
|
|
87
|
+
* be converted with. */
|
|
88
|
+
export const UPSTREAM_LIMIT_CHARS_PER_TOKEN = 2.75;
|
|
89
|
+
/** The platform's own estimate basis: \`CHARS_PER_TOKEN = 4\` in
|
|
90
|
+
* \`@deepseek-ai/dsh-token-meter/estimate.ts\` (its comment reads "used until exact
|
|
91
|
+
* tokenization is needed"), matching upstream's ESTIMATE-side heuristic — "~4
|
|
92
|
+
* chars/token is the usual English heuristic" (\`agent/prompt_builder.py:1179\`).
|
|
93
|
+
* Estimates use this; limits use the constant above. */
|
|
94
|
+
export const PLATFORM_ESTIMATE_CHARS_PER_TOKEN = 4;
|
|
95
|
+
export const MAX_SKILL_FILE_BYTES = 1_048_576;
|
|
96
|
+
// ── Cross-package shared tunable defaults ────────────────────────────────────
|
|
97
|
+
// Fallback defaults when no config override is set. Single-sourced here;
|
|
98
|
+
// packages reference them via `z.default(DEFAULT_*)` or `config.x ?? DEFAULT_*`.
|
|
99
|
+
export const DEFAULT_REVIEW_MEMORY_INTERVAL = 10;
|
|
100
|
+
export const DEFAULT_REVIEW_SKILL_INTERVAL = 10;
|
|
101
|
+
/** Skill-review completion channel trigger mode: 'cadence' | 'completion' | 'both'.
|
|
102
|
+
* 0.3.39 (V6-53 follow-up): default is 'cadence' — since 0.3.38 the cadence
|
|
103
|
+
* channel's review is executed at conversation END (deferred), so 'both' would
|
|
104
|
+
* inject a second (task-complete) prompt at the same boundary; the cadence
|
|
105
|
+
* deferral alone IS the end-of-conversation summary. */
|
|
106
|
+
export const DEFAULT_SKILL_REVIEW_TRIGGER = 'cadence';
|
|
107
|
+
/** Cumulative session tool calls before a session counts as "proven long" for the completion channel. */
|
|
108
|
+
export const DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS = 20;
|
|
109
|
+
export const DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS = 3;
|
|
110
|
+
export const DEFAULT_SUBSTANTIVE_MIN_USER_CHARS = 200;
|
|
111
|
+
export const DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS = 500;
|
|
112
|
+
export const DEFAULT_MAX_OPS_PER_PLAN = 32;
|
|
113
|
+
export const DEFAULT_CURATOR_INTERVAL_HOURS = 168;
|
|
114
|
+
export const DEFAULT_MIN_IDLE_HOURS = 2;
|
|
115
|
+
export const DEFAULT_STALE_AFTER_DAYS = 30;
|
|
116
|
+
export const DEFAULT_ARCHIVE_AFTER_DAYS = 90;
|
|
117
|
+
/**
|
|
118
|
+
* Support-file count above which `references-sprawl` warns. **NOT CALIBRATED YET** — 60 is a
|
|
119
|
+
* placeholder: the platform corpus is empty (no platform skills available here), so the admission
|
|
120
|
+
* band cannot be checked. Until `gen-health-baseline` reports both corpora, the rule is
|
|
121
|
+
* REPORT-ONLY: its count must not be shown as a number on any surface (plan G4 ③).
|
|
122
|
+
*/
|
|
123
|
+
export const SUPPORT_SPRAWL_FILES = 60;
|
|
124
|
+
export const DEFAULT_MEMORY_CHAR_LIMIT = 2200;
|
|
125
|
+
export const DEFAULT_USER_CHAR_LIMIT = 1375;
|
|
126
|
+
/** Consolidation-failure backoff cap, shared by MemoryStore and memory-files' Config default. */
|
|
127
|
+
export const DEFAULT_CONSOLIDATION_FAILURES = 3;
|
|
128
|
+
/** F-20 (v18): the authored-body budget and the hard ceiling are the same
|
|
129
|
+
* number today. Derive it so a future divergence is one edit, not two names
|
|
130
|
+
* that silently disagree. */
|
|
131
|
+
export const DEFAULT_SKILL_CONTENT_CHARS = MAX_SKILL_CONTENT_CHARS;
|
|
132
|
+
/** P3-19 (v14): defaults that were written twice (schema `.default()` AND the
|
|
133
|
+
* clamp fallback literal) now have one home per value. */
|
|
134
|
+
export const DEFAULT_REVIEW_TIMEOUT_MS = 120_000;
|
|
135
|
+
export const DEFAULT_REVIEW_CONTEXT_MESSAGES = 60;
|
|
136
|
+
export const DEFAULT_REVIEW_MESSAGE_CHARS = 2_000;
|
|
137
|
+
export const DEFAULT_CURATOR_BOOT_GRACE_SECONDS = 10;
|
|
138
|
+
export const DEFAULT_CURATOR_REVIEW_MAX_TOKENS = 2_048;
|
|
139
|
+
/** Threat-scan WINDOW SIZE (not a total cap — E-12: the whole text is scanned in
|
|
140
|
+
* overlapping windows, so content beyond this stays in scope). The coverage
|
|
141
|
+
* floor is `PATTERN_OVERLAP + 1` (V6-05). Single home for the core scanners'
|
|
142
|
+
* default parameter and clamp fallback, plus evolution-threat's Config default
|
|
143
|
+
* — the two packages previously wrote 65_536 independently (G0/S0.1). */
|
|
144
|
+
export const DEFAULT_THREAT_MAX_SCAN_CHARS = 65_536;
|
|
145
|
+
/** 0.3.17 (S3.10, T-1): control-plane fields a model-facing write call may
|
|
146
|
+
* never carry — single source for plan-validator, evolution-policy and the
|
|
147
|
+
* threat scanner (they used to each hardcode the list).
|
|
148
|
+
* P2-13 (v37): staged_from_sha256 joins the list — it is the replay's own
|
|
149
|
+
* staleness anchor, and the tool-arguments root is an OPEN object, so a model
|
|
150
|
+
* could otherwise choose the anchor that decides the write's outcome. */
|
|
151
|
+
export const FORBIDDEN_CONTROL_KEYS = ['policy', 'threshold', 'prompt_hash', 'model_route', 'evolution_config', 'staged_from_sha256'];
|
|
152
|
+
/** 0.3.17 (S3.10): the model-facing write tools the policy guard and threat
|
|
153
|
+
* scanner cover. */
|
|
154
|
+
export const EVOLUTION_WRITE_TOOLS = ['memory', 'skill_manage'];
|
|
155
|
+
/** Hermes authoring quality bar for descriptions (the 60-char Rule). The
|
|
156
|
+
* platform's own index limit stays in validateFrontmatter; this bar is the
|
|
157
|
+
* target the authoring standard names, enforced as ADVISORY feedback.
|
|
158
|
+
* 0.3.16 (T-4): moved here from skill-store.ts so drift-signals (pure, no IO)
|
|
159
|
+
* can reference it without importing the skill-store module. */
|
|
160
|
+
export const AUTHORING_DESCRIPTION_BAR = 60;
|
|
161
|
+
/** The PLATFORM's default cap on the description a skill catalog shows —
|
|
162
|
+
* `catalogDescriptionMaxLength` on the platform's `tool-skill` row (default 500).
|
|
163
|
+
*
|
|
164
|
+
* Recorded here because it is the third number in the description-length relationship, and the one
|
|
165
|
+
* that is easiest to misread as a family rule: the family's own host/all rows SET that field to
|
|
166
|
+
* `AUTHORING_DESCRIPTION_BAR` (60), so what a family deployment truncates at is the ROW's value —
|
|
167
|
+
* this default only governs compositions that do not override it. It is NOT a refusal threshold:
|
|
168
|
+
* the only refusal is `MAX_DESCRIPTION_LENGTH` / the row's `maxDescriptionLength`, and the catalog
|
|
169
|
+
* cut is a view, not a validation. See tool-skill-manage/README.md for the three-number table. */
|
|
170
|
+
export const PLATFORM_CATALOG_DESCRIPTION_DEFAULT = 500;
|
|
171
|
+
/** The split hint both size refusals share (0.5.0 V1). The same sentence used to
|
|
172
|
+
* be copied into validateFrontmatter AND the patch path, and neither copy named
|
|
173
|
+
* where the content should go — the upstream cap message names the destination
|
|
174
|
+
* directories, and that is the part a model actually acts on. */
|
|
175
|
+
export const CONTENT_SPLIT_HINT = '建议拆成更小的 SKILL.md,把支撑文件放进 references/ 或 templates/。';
|
|
176
|
+
/** V27 G2.4: the largest millisecond delay a timer accepts. `AbortSignal.timeout`
|
|
177
|
+
* (and `setTimeout`) coerce anything larger to 1ms after a Node warning, so a
|
|
178
|
+
* timeout configured above this ceiling silently collapses to "immediately
|
|
179
|
+
* aborted". The curator's review timeout and the review timeout each carried
|
|
180
|
+
* their own copy of the literal; the bound is one protocol constant.
|
|
181
|
+
* (v19 P2-10 corrected the value from 2^32-1 to Node's real 2^31-1 ceiling.) */
|
|
182
|
+
export const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
183
|
+
/**
|
|
184
|
+
* Model-facing budget of one `maintenance_probe` call (ms) — a HANG GUARD, not a
|
|
185
|
+
* performance knob.
|
|
186
|
+
*
|
|
187
|
+
* Measured (2026-10-10): one probe costs 188/220/225 ms on the deployed library
|
|
188
|
+
* (28 skills / 418 KB) and scales linearly at ~5.5 ms per skill (100 skills
|
|
189
|
+
* 0.4-0.6 s, 400 skills 1.9-2.5 s), so this default is ~50x the worst measured
|
|
190
|
+
* case and ~600x the deployed one — it will not fire in normal use, and the
|
|
191
|
+
* ~305 s desktop interaction wall is ~5e4 skills away.
|
|
192
|
+
*
|
|
193
|
+
* It exists for the state the probe cannot report itself: a provider that never
|
|
194
|
+
* answers. The platform's tool-timeout guard reads this budget off the tool
|
|
195
|
+
* definition and turns the hang into a diagnosable `tool call timed out after
|
|
196
|
+
* <ms>ms` error result, instead of a request that ends with nothing to read.
|
|
197
|
+
* Reopen the default when the library approaches ~1e4 skills, when the
|
|
198
|
+
* `evolutionIo` provider stops being local, or when a model leg enters a tool's
|
|
199
|
+
* execute (the same conditions the probe's own note lists).
|
|
200
|
+
*/
|
|
201
|
+
export const DEFAULT_PROBE_TIMEOUT_MS = 120_000;
|
|
202
|
+
/** V27 G2.4: the model each review/curation leg defaults to. The policy schema,
|
|
203
|
+
* the policy resolver and the curator's LLM nomination pass each carried their
|
|
204
|
+
* own copy of these strings — a deployment that changed the policy default used
|
|
205
|
+
* to leave the curator passing a different model than the reviews. */
|
|
206
|
+
export const DEFAULT_MEMORY_REVIEW_MODEL = 'deepseek-v4-flash';
|
|
207
|
+
export const DEFAULT_SKILL_REVIEW_MODEL = 'deepseek-v4-pro';
|
|
208
|
+
export const DEFAULT_CURATOR_MODEL = 'deepseek-v4-pro';
|
|
209
|
+
// ── Model-visible prompt-section orders ──────────────────────────────────────
|
|
210
|
+
// The platform's `PromptSection.order` scale is a platform-internal constant
|
|
211
|
+
// table (`SECTION_ORDERS`, core/system-prompt), not a published contract: the
|
|
212
|
+
// 0.1.5 line re-scaled every first-party section from -1000 to 10200
|
|
213
|
+
// (`DEPLOYMENT_PERSONA_SUFFIX` is the highest), so a third-party literal that
|
|
214
|
+
// used to sort last could silently sort into the middle of the tool guidance.
|
|
215
|
+
// Both family sections are operational guidance for the model, so they belong
|
|
216
|
+
// AFTER every first-party section on every line. These values sit above 0.1.5's
|
|
217
|
+
// maximum; scripts/verify-platform-contract.mjs --upstream re-reads the
|
|
218
|
+
// platform's scale and fails when it grows past them.
|
|
219
|
+
/** Order of the `evolution:memory-guidance` section (before the skills one). */
|
|
220
|
+
export const MEMORY_GUIDANCE_SECTION_ORDER = 11000;
|
|
221
|
+
/** Order of the `evolution-skills-guidance` section (last of the two). */
|
|
222
|
+
export const SKILLS_GUIDANCE_SECTION_ORDER = 11100;
|
|
223
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-cost accounting for skill bodies (design §5.2).
|
|
3
|
+
*
|
|
4
|
+
* A raw character count is a poor budget: the same 100k characters cost very
|
|
5
|
+
* different amounts of context in Chinese-heavy prose and in English. The
|
|
6
|
+
* weighted unit makes the two comparable, and the token range exists only to
|
|
7
|
+
* make the load cost visible next to the character count — it is an ESTIMATE,
|
|
8
|
+
* never a measurement.
|
|
9
|
+
*
|
|
10
|
+
* Accounting basis: the ON-DISK form (`skillMdOnDisk()`, the same view the
|
|
11
|
+
* write limit uses), so a body that is exactly at the limit cannot read as
|
|
12
|
+
* over-limit here and pass there (the v37 P2-1 deadlock class).
|
|
13
|
+
*/
|
|
14
|
+
import { UPSTREAM_LIMIT_CHARS_PER_TOKEN } from "./constants.js";
|
|
15
|
+
import { skillMdOnDisk } from "./frontmatter.js";
|
|
16
|
+
/** Token weight of one CJK / kana / full-width code unit on the LIMIT basis —
|
|
17
|
+
* upstream's model-independent conversion is 2.75 chars/token for prose, and CJK
|
|
18
|
+
* prose is ~1 token per character (the conservative end of the 0.6–1.0 range the
|
|
19
|
+
* estimate side reports). Thresholds are compared on THIS basis so a borrowed
|
|
20
|
+
* character line keeps its meaning; the estimate range below reports the spread. */
|
|
21
|
+
export const TOKEN_CJK_WEIGHT = 1;
|
|
22
|
+
/** Token weight of one non-CJK code unit on the LIMIT basis (1 / 2.75 ≈ 0.364). */
|
|
23
|
+
export const TOKEN_ASCII_WEIGHT = 1 / UPSTREAM_LIMIT_CHARS_PER_TOKEN;
|
|
24
|
+
/** Token-per-unit bounds of the estimate range (CJK: 0.6–1.0, non-CJK: 1/4–1/3).
|
|
25
|
+
* The range is the 4-chars/token estimate family, so on ASCII-heavy bodies the
|
|
26
|
+
* LIMIT-basis `tokens` point can sit above `tokensHigh` (1/2.75 > 1/3): the two
|
|
27
|
+
* answer different questions and are never nested by construction. */
|
|
28
|
+
export const COST_CJK_TOKEN_LOW = 0.6;
|
|
29
|
+
export const COST_CJK_TOKEN_HIGH = 1;
|
|
30
|
+
export const COST_ASCII_TOKEN_LOW = 0.25;
|
|
31
|
+
export const COST_ASCII_TOKEN_HIGH = 1 / 3;
|
|
32
|
+
/** The token line a borrowed CHARACTER threshold draws for ONE body: 20k characters
|
|
33
|
+
* of THIS composition, expressed in the same tokens `bodyCost` counts. Comparing
|
|
34
|
+
* `tokens` against this line is deliberately equivalent to "chars >= line" — that
|
|
35
|
+
* equivalence is what keeps a borrowed line's textual effect identical — while the
|
|
36
|
+
* judgment itself, and every number reported, stays on the token scale.
|
|
37
|
+
* @param lineChars - the borrowed character line (whole-body characters).
|
|
38
|
+
* @param cost - the body's own cost breakdown.
|
|
39
|
+
* @returns the equivalent token line (0 when the body is empty). */
|
|
40
|
+
export function tokenLineFor(lineChars, cost) {
|
|
41
|
+
if (cost.chars === 0)
|
|
42
|
+
return 0;
|
|
43
|
+
return Math.round((lineChars * cost.tokens) / cost.chars);
|
|
44
|
+
}
|
|
45
|
+
/** CJK ideographs, kana, full-width forms and CJK punctuation. */
|
|
46
|
+
const CJK_RE = /[\u3000-\u303f\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\uff00-\uffef]/g;
|
|
47
|
+
/**
|
|
48
|
+
* Cost of one skill body.
|
|
49
|
+
* @param content - the body text as written (trailing whitespace is normalized
|
|
50
|
+
* away by the on-disk accounting, so callers need not pre-trim).
|
|
51
|
+
* @returns the cost breakdown; an empty body answers all-zero.
|
|
52
|
+
*/
|
|
53
|
+
export function bodyCost(content) {
|
|
54
|
+
const onDisk = skillMdOnDisk(content);
|
|
55
|
+
const cjk = [...onDisk.matchAll(CJK_RE)].length;
|
|
56
|
+
const chars = onDisk.length;
|
|
57
|
+
const ascii = chars - cjk;
|
|
58
|
+
return {
|
|
59
|
+
chars,
|
|
60
|
+
cjk,
|
|
61
|
+
ascii,
|
|
62
|
+
tokens: Math.round(cjk * TOKEN_CJK_WEIGHT + ascii * TOKEN_ASCII_WEIGHT),
|
|
63
|
+
tokensLow: Math.round(cjk * COST_CJK_TOKEN_LOW + ascii * COST_ASCII_TOKEN_LOW),
|
|
64
|
+
tokensHigh: Math.round(cjk * COST_CJK_TOKEN_HIGH + ascii * COST_ASCII_TOKEN_HIGH),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=cost.js.map
|
package/lib/types/curator.d.ts
CHANGED
|
@@ -93,6 +93,7 @@ export interface CuratorRunReport {
|
|
|
93
93
|
llmReviewEnabled?: boolean;
|
|
94
94
|
/** V6-35 (0.3.36): lenient-parse shape notes from the LLM nomination block. */
|
|
95
95
|
nominationsWarnings?: string[];
|
|
96
|
+
scanWarnings?: string[];
|
|
96
97
|
/** 0.5.0 V1 (design §16.6-④): archived entries past the retention window that
|
|
97
98
|
* the CURRENT policy keeps rather than deletes. Present (even empty) only when
|
|
98
99
|
* the retention policy was `report`, so a report cannot read as "nothing
|
|
@@ -117,6 +118,7 @@ export interface CuratorReportInput {
|
|
|
117
118
|
snapshotPath?: string;
|
|
118
119
|
llmReviewEnabled?: boolean;
|
|
119
120
|
nominationsWarnings?: readonly string[];
|
|
121
|
+
scanWarnings?: readonly string[];
|
|
120
122
|
wouldPrune?: readonly string[] | null;
|
|
121
123
|
}
|
|
122
124
|
export declare function buildCuratorRunReport(input: CuratorReportInput): CuratorRunReport;
|