@lmzhen/dsh-evolution-merge 0.30.25 → 0.30.28
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/lib/index.js +60 -22
- package/lib/types/authoring-flow.js +7 -2
- package/lib/types/domain/validation-plan.d.ts +2 -0
- package/lib/types/domain/validation-plan.js +8 -4
- package/lib/types/host-capabilities.d.ts +22 -0
- package/lib/types/host-capabilities.js +24 -0
- package/lib/types/index.js +7 -9
- package/lib/types/new-child-authoring.js +1 -1
- package/lib/types/runtime.d.ts +2 -0
- package/lib/types/runtime.js +12 -0
- package/package.json +5 -5
package/lib/index.js
CHANGED
|
@@ -8217,7 +8217,7 @@ function buildValidationPlan(input) {
|
|
|
8217
8217
|
const staged = new Set(files.map((file) => file.path));
|
|
8218
8218
|
const manifest = new Set(manifestFiles);
|
|
8219
8219
|
const forwardRefs = [...refs].filter((ref) => !staged.has(ref)).sort();
|
|
8220
|
-
const reverseRefs = files.map((file) => file.path).filter((path) => !refs.has(path) && manifest.has(path)).sort();
|
|
8220
|
+
const reverseRefs = files.map((file) => file.path).filter((path) => path !== "SKILL.md" && !refs.has(path) && manifest.has(path)).sort();
|
|
8221
8221
|
const orphanFiles = [...new Set(input.existingSupport ?? [])].filter((path) => !staged.has(path)).sort();
|
|
8222
8222
|
const byPath = /* @__PURE__ */ new Map();
|
|
8223
8223
|
const supportSources = input.supportSources ?? [];
|
|
@@ -8234,12 +8234,14 @@ function buildValidationPlan(input) {
|
|
|
8234
8234
|
path,
|
|
8235
8235
|
sources: value.sources
|
|
8236
8236
|
}));
|
|
8237
|
-
const
|
|
8237
|
+
const participants = input.participants === void 0 ? void 0 : new Set(input.participants);
|
|
8238
|
+
const participantCards = participants === void 0 ? input.cards ?? [] : (input.cards ?? []).filter((card) => participants.has(card.name));
|
|
8239
|
+
const sourceThreatFiles = participantCards.flatMap((card) => (card.files ?? []).filter((file) => scanContentThreats(file.content) !== null).map((file) => card.name + "/" + file.path));
|
|
8238
8240
|
const threat = {
|
|
8239
|
-
body: scanContentThreats(input.tree.body) !== null ||
|
|
8241
|
+
body: scanContentThreats(input.tree.body) !== null || participantCards.some((card) => scanContentThreats(card.body) !== null),
|
|
8240
8242
|
files: [...files.filter((file) => scanContentThreats(file.content) !== null).map((file) => file.path), ...sourceThreatFiles]
|
|
8241
8243
|
};
|
|
8242
|
-
const sourceRevisions = input.sourceRevisions ?? Object.fromEntries(
|
|
8244
|
+
const sourceRevisions = input.sourceRevisions ?? Object.fromEntries(participantCards.map((card) => [card.name, digest(card.body)]));
|
|
8243
8245
|
const revision = digest(JSON.stringify({
|
|
8244
8246
|
target: input.target,
|
|
8245
8247
|
body: digest(input.tree.body),
|
|
@@ -8377,7 +8379,8 @@ async function runAuthoring(options) {
|
|
|
8377
8379
|
message: "target must be a bare skill name: " + options.target
|
|
8378
8380
|
};
|
|
8379
8381
|
const targetCard = options.cards.find((card) => card.name === options.target);
|
|
8380
|
-
const
|
|
8382
|
+
const requestedMode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
|
|
8383
|
+
const mode = requestedMode === "merge-into-existing" && targetCard === void 0 ? "create-umbrella" : requestedMode;
|
|
8381
8384
|
const createMode = mode === "create-umbrella";
|
|
8382
8385
|
if (!createMode && targetCard !== void 0) {
|
|
8383
8386
|
if (targetCard.managed !== true) return {
|
|
@@ -8472,6 +8475,7 @@ async function runAuthoring(options) {
|
|
|
8472
8475
|
source: "staged"
|
|
8473
8476
|
})),
|
|
8474
8477
|
manifestFiles,
|
|
8478
|
+
...options.sources === void 0 ? {} : { participants: [...options.sources, options.target] },
|
|
8475
8479
|
existingSupport: (options.cards.find((card) => card.name === options.target)?.files ?? []).map((file) => file.path)
|
|
8476
8480
|
});
|
|
8477
8481
|
const findings = [...validateStagedTree(tree.value, limitsSnapshot.skillLimits), ...validationPlan.findings];
|
|
@@ -8889,6 +8893,11 @@ async function runNewChildAuthoring(options, row, decision, signal) {
|
|
|
8889
8893
|
source: "staged"
|
|
8890
8894
|
})),
|
|
8891
8895
|
manifestFiles,
|
|
8896
|
+
participants: [
|
|
8897
|
+
row.a,
|
|
8898
|
+
row.b,
|
|
8899
|
+
decision.target
|
|
8900
|
+
],
|
|
8892
8901
|
existingSupport: (options.cards.find((card) => card.name === decision.target)?.files ?? []).map((file) => file.path),
|
|
8893
8902
|
supportSources: options.cards.filter((card) => card.name === row.a || card.name === row.b).flatMap((card) => (card.files ?? []).map((file) => ({
|
|
8894
8903
|
path: file.path,
|
|
@@ -9080,6 +9089,11 @@ function failRun(stage, hint, next, result) {
|
|
|
9080
9089
|
text: formatMergeError(result.error)
|
|
9081
9090
|
};
|
|
9082
9091
|
}
|
|
9092
|
+
async function hostOnlyFailure(verb, capabilities) {
|
|
9093
|
+
if (capabilities === void 0) return void 0;
|
|
9094
|
+
const state = await capabilities.hostOnly();
|
|
9095
|
+
return state === void 0 ? void 0 : fail(verb, "e-323", "host data plane unavailable: " + state.code + (state.detail === void 0 ? "" : " (" + state.detail + ")"), "run /evolution merge doctor");
|
|
9096
|
+
}
|
|
9083
9097
|
/**
|
|
9084
9098
|
* The approved/fuse row that already holds a target. Pair/approve move a row to
|
|
9085
9099
|
* approved/fuse BEFORE their run starts, so this row IS the in-flight claim a second
|
|
@@ -9162,6 +9176,8 @@ function createMergeRuntime(deps) {
|
|
|
9162
9176
|
text: "merge: 技能名不能包含 provider/name 或 provider:name 语法:" + command.a + " / " + command.b + " (provider-conflict)\nusage: /evolution merge pair <a> <b>"
|
|
9163
9177
|
};
|
|
9164
9178
|
if (agentless) return fail("pair", "e-305", errorText("e-305-this-invocation-carries-no", { a1: "pair" }), "在 GUI 或 CLI 的会话里运行 /evolution merge pair " + command.a + " " + command.b);
|
|
9179
|
+
const pairHost = await hostOnlyFailure("pair", deps.hostCapabilities);
|
|
9180
|
+
if (pairHost !== void 0) return pairHost;
|
|
9165
9181
|
const pairKeyValue = pairKey(command.a, command.b);
|
|
9166
9182
|
const inFlight = inFlightPairRun(await readRunMetas(deps.io, deps.home), pairKeyValue);
|
|
9167
9183
|
let ownerActive = false;
|
|
@@ -9239,6 +9255,8 @@ function createMergeRuntime(deps) {
|
|
|
9239
9255
|
}
|
|
9240
9256
|
if (command.verb === "scan") {
|
|
9241
9257
|
if (agentless) return fail("scan", "e-305", errorText("e-305-this-invocation-carries-no", { a1: "scan" }), "在 GUI 或 CLI 的会话里运行 /evolution merge scan");
|
|
9258
|
+
const scanHost = await hostOnlyFailure("scan", deps.hostCapabilities);
|
|
9259
|
+
if (scanHost !== void 0) return scanHost;
|
|
9242
9260
|
const signal = new AbortController().signal;
|
|
9243
9261
|
const cards = await deps.cards(signal);
|
|
9244
9262
|
const blocked = /* @__PURE__ */ new Set();
|
|
@@ -9756,6 +9774,31 @@ function createMergeRuntime(deps) {
|
|
|
9756
9774
|
} };
|
|
9757
9775
|
}
|
|
9758
9776
|
//#endregion
|
|
9777
|
+
//#region lib/types/host-capabilities.js
|
|
9778
|
+
/**
|
|
9779
|
+
* The host's data-plane capability as ONE value shared by snapshot, doctor, HTTP and run preflight.
|
|
9780
|
+
* The probe is injected so the Cordis wiring stays in index.ts; a rejected probe is the same E-323
|
|
9781
|
+
* answer as an unavailable one, never an unhandled promise.
|
|
9782
|
+
* @module @lmzhen/dsh-evolution-merge
|
|
9783
|
+
*/
|
|
9784
|
+
/**
|
|
9785
|
+
* Wrap one probe with the shared error normalization.
|
|
9786
|
+
* @param probe - the platform-specific availability check.
|
|
9787
|
+
* @returns the capability value consumed by snapshot and runtime.
|
|
9788
|
+
*/
|
|
9789
|
+
function createHostCapabilities(probe) {
|
|
9790
|
+
return { hostOnly: async () => {
|
|
9791
|
+
try {
|
|
9792
|
+
return await probe();
|
|
9793
|
+
} catch (error) {
|
|
9794
|
+
return {
|
|
9795
|
+
code: "E-323",
|
|
9796
|
+
detail: error instanceof Error ? error.message : String(error)
|
|
9797
|
+
};
|
|
9798
|
+
}
|
|
9799
|
+
} };
|
|
9800
|
+
}
|
|
9801
|
+
//#endregion
|
|
9759
9802
|
//#region lib/types/params.js
|
|
9760
9803
|
/**
|
|
9761
9804
|
* Merge parameter resolution. E2/E3 values arrive as one JSON object; this is the only
|
|
@@ -10534,6 +10577,16 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10534
10577
|
});
|
|
10535
10578
|
const platformChannel = createSubagentChannel(ctx);
|
|
10536
10579
|
const channel = subagentChannelAdapter(platformChannel);
|
|
10580
|
+
const hostCapabilities = createHostCapabilities(async () => {
|
|
10581
|
+
const state = await platformChannel.prepare({
|
|
10582
|
+
kind: "reviewer",
|
|
10583
|
+
allow: ["skill"]
|
|
10584
|
+
});
|
|
10585
|
+
return state.kind === "unavailable" ? {
|
|
10586
|
+
code: "E-323",
|
|
10587
|
+
detail: state.reason
|
|
10588
|
+
} : void 0;
|
|
10589
|
+
});
|
|
10537
10590
|
const stage = new StageToolRegistry();
|
|
10538
10591
|
const engineInput = resolveEngineInput({ modelsRoot: params.mergeModelsRoot });
|
|
10539
10592
|
const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
|
|
@@ -10597,23 +10650,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10597
10650
|
runs: runs.runs("merge"),
|
|
10598
10651
|
approval: params.mergeApproval,
|
|
10599
10652
|
platformWrite,
|
|
10600
|
-
hostOnly:
|
|
10601
|
-
try {
|
|
10602
|
-
const state = await platformChannel.prepare({
|
|
10603
|
-
kind: "reviewer",
|
|
10604
|
-
allow: ["skill"]
|
|
10605
|
-
});
|
|
10606
|
-
return state.kind === "unavailable" ? {
|
|
10607
|
-
code: "E-323",
|
|
10608
|
-
detail: state.reason
|
|
10609
|
-
} : void 0;
|
|
10610
|
-
} catch (error) {
|
|
10611
|
-
return {
|
|
10612
|
-
code: "E-323",
|
|
10613
|
-
detail: error instanceof Error ? error.message : String(error)
|
|
10614
|
-
};
|
|
10615
|
-
}
|
|
10616
|
-
},
|
|
10653
|
+
hostOnly: hostCapabilities.hostOnly,
|
|
10617
10654
|
live: async () => liveRowFactsOf(await readSkillCards(skills)),
|
|
10618
10655
|
history: async () => {
|
|
10619
10656
|
const items = /* @__PURE__ */ new Map();
|
|
@@ -10707,6 +10744,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10707
10744
|
parent: void 0,
|
|
10708
10745
|
allow: ["merge_stage"],
|
|
10709
10746
|
timeoutMinutes: params.mergeRunTimeoutMinutes,
|
|
10747
|
+
hostCapabilities,
|
|
10710
10748
|
isBlocked: async (name) => {
|
|
10711
10749
|
return await ctx.get("evolutionCurator")?.isBlocked?.(name) === true;
|
|
10712
10750
|
},
|
|
@@ -10,7 +10,12 @@ export async function runAuthoring(options) {
|
|
|
10
10
|
if (options.target.includes('/') || options.target.includes(':'))
|
|
11
11
|
return { ok: false, code: 'target-invalid', message: 'target must be a bare skill name: ' + options.target };
|
|
12
12
|
const targetCard = options.cards.find(card => card.name === options.target);
|
|
13
|
-
|
|
13
|
+
// A decision can name a fresh target yet say merge-into-existing (the model's vocabulary is
|
|
14
|
+
// about intent, not existence). The target card is the authoritative existence fact: when it is
|
|
15
|
+
// absent, the only safe mode is create-umbrella; otherwise commit-assembly correctly refuses to
|
|
16
|
+
// re-create a target that disappeared later.
|
|
17
|
+
const requestedMode = options.mode ?? (targetCard === undefined ? 'create-umbrella' : 'merge-into-existing');
|
|
18
|
+
const mode = requestedMode === 'merge-into-existing' && targetCard === undefined ? 'create-umbrella' : requestedMode;
|
|
14
19
|
const createMode = mode === 'create-umbrella';
|
|
15
20
|
if (!createMode && targetCard !== undefined) {
|
|
16
21
|
if (targetCard.managed !== true)
|
|
@@ -60,7 +65,7 @@ export async function runAuthoring(options) {
|
|
|
60
65
|
const limitsSnapshot = limitsSnapshotOf(options.limits ?? DEFAULT_SKILL_LIMITS);
|
|
61
66
|
const participants = new Set(options.sources ?? []);
|
|
62
67
|
const supportSources = options.cards.filter(card => participants.has(card.name)).flatMap(card => (card.files ?? []).map(file => ({ path: file.path, content: file.content, source: card.name })));
|
|
63
|
-
const validationPlan = buildValidationPlan({ tree: tree.value, target: options.target, limits: limitsSnapshot, cards: options.cards, supportSources, ...(options.promptVersion === undefined ? {} : { promptVersion: options.promptVersion }), ...(options.promptHash === undefined ? {} : { promptHash: options.promptHash }), supportFiles: tree.value.files.map(file => ({ path: file.path, content: file.content, source: 'staged' })), manifestFiles, existingSupport: (options.cards.find(card => card.name === options.target)?.files ?? []).map(file => file.path) });
|
|
68
|
+
const validationPlan = buildValidationPlan({ tree: tree.value, target: options.target, limits: limitsSnapshot, cards: options.cards, supportSources, ...(options.promptVersion === undefined ? {} : { promptVersion: options.promptVersion }), ...(options.promptHash === undefined ? {} : { promptHash: options.promptHash }), supportFiles: tree.value.files.map(file => ({ path: file.path, content: file.content, source: 'staged' })), manifestFiles, ...(options.sources === undefined ? {} : { participants: [...options.sources, options.target] }), existingSupport: (options.cards.find(card => card.name === options.target)?.files ?? []).map(file => file.path) });
|
|
64
69
|
const findings = [...validateStagedTree(tree.value, limitsSnapshot.skillLimits), ...validationPlan.findings];
|
|
65
70
|
const lengths = { bodyBytes: Buffer.byteLength(tree.value.body, 'utf8'), fileBytes: tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0), totalBytes: Buffer.byteLength(tree.value.body, 'utf8') + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0) };
|
|
66
71
|
if (hasBlockingFinding(findings))
|
|
@@ -61,5 +61,7 @@ export declare function buildValidationPlan(input: {
|
|
|
61
61
|
}[];
|
|
62
62
|
readonly promptVersion?: string;
|
|
63
63
|
readonly promptHash?: string;
|
|
64
|
+
/** Skills that participate in this merge (pair sources and target); omit for legacy all-card behavior. */
|
|
65
|
+
readonly participants?: readonly string[];
|
|
64
66
|
}): ValidationPlan;
|
|
65
67
|
//# sourceMappingURL=validation-plan.d.ts.map
|
|
@@ -10,7 +10,9 @@ export function buildValidationPlan(input) {
|
|
|
10
10
|
const staged = new Set(files.map(file => file.path));
|
|
11
11
|
const manifest = new Set(manifestFiles);
|
|
12
12
|
const forwardRefs = [...refs].filter(ref => !staged.has(ref)).sort();
|
|
13
|
-
|
|
13
|
+
// SKILL.md is the body itself, never a support reference; a child may list it in the
|
|
14
|
+
// authoring manifest, but it must not require the body to reference itself.
|
|
15
|
+
const reverseRefs = files.map(file => file.path).filter(path => path !== 'SKILL.md' && !refs.has(path) && manifest.has(path)).sort();
|
|
14
16
|
const existing = new Set(input.existingSupport ?? []);
|
|
15
17
|
const orphanFiles = [...existing].filter(path => !staged.has(path)).sort();
|
|
16
18
|
const byPath = new Map();
|
|
@@ -25,9 +27,11 @@ export function buildValidationPlan(input) {
|
|
|
25
27
|
byPath.set(file.path, entry);
|
|
26
28
|
}
|
|
27
29
|
const supportConflicts = [...byPath.entries()].filter(([, value]) => value.contents.size > 1).map(([path, value]) => ({ path, sources: value.sources }));
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
const
|
|
30
|
+
const participants = input.participants === undefined ? undefined : new Set(input.participants);
|
|
31
|
+
const participantCards = participants === undefined ? (input.cards ?? []) : (input.cards ?? []).filter(card => participants.has(card.name));
|
|
32
|
+
const sourceThreatFiles = participantCards.flatMap(card => (card.files ?? []).filter(file => scanContentThreats(file.content) !== null).map(file => card.name + '/' + file.path));
|
|
33
|
+
const threat = { body: scanContentThreats(input.tree.body) !== null || participantCards.some(card => scanContentThreats(card.body) !== null), files: [...files.filter(file => scanContentThreats(file.content) !== null).map(file => file.path), ...sourceThreatFiles] };
|
|
34
|
+
const sourceRevisions = input.sourceRevisions ?? Object.fromEntries(participantCards.map(card => [card.name, digest(card.body)]));
|
|
31
35
|
const revision = digest(JSON.stringify({ target: input.target, body: digest(input.tree.body), files: files.map(file => [file.path, file.digest]), q09: { forwardRefs, reverseRefs, orphanFiles }, supportConflicts, threat, sourceRevisions, targetRevision: input.targetRevision ?? null }));
|
|
32
36
|
const findings = [];
|
|
33
37
|
const stageDigest = digest(JSON.stringify(files.map(file => [file.path, file.digest])));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's data-plane capability as ONE value shared by snapshot, doctor, HTTP and run preflight.
|
|
3
|
+
* The probe is injected so the Cordis wiring stays in index.ts; a rejected probe is the same E-323
|
|
4
|
+
* answer as an unavailable one, never an unhandled promise.
|
|
5
|
+
* @module @lmzhen/dsh-evolution-merge
|
|
6
|
+
*/
|
|
7
|
+
/** The one unavailable answer the merge surface understands. */
|
|
8
|
+
export interface HostOnlyUnavailable {
|
|
9
|
+
readonly code: 'E-323';
|
|
10
|
+
readonly detail?: string;
|
|
11
|
+
}
|
|
12
|
+
/** A host capability probe; undefined means the data plane is usable. */
|
|
13
|
+
export interface HostCapabilities {
|
|
14
|
+
readonly hostOnly: () => Promise<HostOnlyUnavailable | undefined>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Wrap one probe with the shared error normalization.
|
|
18
|
+
* @param probe - the platform-specific availability check.
|
|
19
|
+
* @returns the capability value consumed by snapshot and runtime.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createHostCapabilities(probe: () => Promise<HostOnlyUnavailable | undefined>): HostCapabilities;
|
|
22
|
+
//# sourceMappingURL=host-capabilities.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host's data-plane capability as ONE value shared by snapshot, doctor, HTTP and run preflight.
|
|
3
|
+
* The probe is injected so the Cordis wiring stays in index.ts; a rejected probe is the same E-323
|
|
4
|
+
* answer as an unavailable one, never an unhandled promise.
|
|
5
|
+
* @module @lmzhen/dsh-evolution-merge
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Wrap one probe with the shared error normalization.
|
|
9
|
+
* @param probe - the platform-specific availability check.
|
|
10
|
+
* @returns the capability value consumed by snapshot and runtime.
|
|
11
|
+
*/
|
|
12
|
+
export function createHostCapabilities(probe) {
|
|
13
|
+
return {
|
|
14
|
+
hostOnly: async () => {
|
|
15
|
+
try {
|
|
16
|
+
return await probe();
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { code: 'E-323', detail: error instanceof Error ? error.message : String(error) };
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=host-capabilities.js.map
|
package/lib/types/index.js
CHANGED
|
@@ -31,6 +31,7 @@ import { pairKey, rowIdOf } from "./contracts.js";
|
|
|
31
31
|
import { makeMergeRoutes } from "./merge-routes.js";
|
|
32
32
|
import { createMergeCommand, runMergeCommand } from "./merge-command.js";
|
|
33
33
|
import { createMergeRuntime } from "./runtime.js";
|
|
34
|
+
import { createHostCapabilities } from "./host-capabilities.js";
|
|
34
35
|
import { resolveMergeParams } from "./params.js";
|
|
35
36
|
import { subagentChannelAdapter } from "./adapters/channel.js";
|
|
36
37
|
import { StageToolRegistry } from "./stage-tool-registry.js";
|
|
@@ -234,6 +235,10 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
234
235
|
}).catch((error) => { ctx.logger.warn('merge state readiness failed: ' + (error instanceof Error ? error.message : String(error))); });
|
|
235
236
|
const platformChannel = createSubagentChannel(ctx);
|
|
236
237
|
const channel = subagentChannelAdapter(platformChannel);
|
|
238
|
+
const hostCapabilities = createHostCapabilities(async () => {
|
|
239
|
+
const state = await platformChannel.prepare({ kind: 'reviewer', allow: ['skill'] });
|
|
240
|
+
return state.kind === 'unavailable' ? { code: 'E-323', detail: state.reason } : undefined;
|
|
241
|
+
});
|
|
237
242
|
const stage = new StageToolRegistry();
|
|
238
243
|
const engineInput = resolveEngineInput({ modelsRoot: params.mergeModelsRoot });
|
|
239
244
|
const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
|
|
@@ -276,15 +281,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
276
281
|
});
|
|
277
282
|
source = {
|
|
278
283
|
async snapshot() {
|
|
279
|
-
return await readMergeSnapshot({ io, home, runs: runs.runs('merge'), approval: params.mergeApproval, platformWrite, hostOnly: async () => {
|
|
280
|
-
try {
|
|
281
|
-
const state = await platformChannel.prepare({ kind: 'reviewer', allow: ['skill'] });
|
|
282
|
-
return state.kind === 'unavailable' ? { code: 'E-323', detail: state.reason } : undefined;
|
|
283
|
-
}
|
|
284
|
-
catch (error) {
|
|
285
|
-
return { code: 'E-323', detail: error instanceof Error ? error.message : String(error) };
|
|
286
|
-
}
|
|
287
|
-
}, live: async () => liveRowFactsOf(await readSkillCards(skills)), history: async () => {
|
|
284
|
+
return await readMergeSnapshot({ io, home, runs: runs.runs('merge'), approval: params.mergeApproval, platformWrite, hostOnly: hostCapabilities.hostOnly, live: async () => liveRowFactsOf(await readSkillCards(skills)), history: async () => {
|
|
288
285
|
const items = new Map();
|
|
289
286
|
const receipts = [...await readCommitReceipts(io, home)].sort((a, b) => a.at - b.at);
|
|
290
287
|
const generationOf = new Map(receipts.map((receipt, index) => [receipt.runId, index + 1]));
|
|
@@ -331,6 +328,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
331
328
|
io, home, registry: runs, skills, now: () => Date.now(), params, platformWrite,
|
|
332
329
|
...(defaultModel === undefined ? {} : { model: defaultModel.currentSelection().model }),
|
|
333
330
|
channel, parent: undefined, allow: ['merge_stage'], timeoutMinutes: params.mergeRunTimeoutMinutes,
|
|
331
|
+
hostCapabilities,
|
|
334
332
|
isBlocked: async (name) => {
|
|
335
333
|
const curator = ctx.get('evolutionCurator');
|
|
336
334
|
return (await curator?.isBlocked?.(name)) === true;
|
|
@@ -90,7 +90,7 @@ export async function runNewChildAuthoring(options, row, decision, signal) {
|
|
|
90
90
|
if (missing !== null)
|
|
91
91
|
return { kind: 'failed', failure: { stage: 'author', code: 'merge-manifest-missing', message: 'manifest file missing from staged tree: ' + missing, retryable: true, retryFrom: 'agent2' } };
|
|
92
92
|
const limitsSnapshot = limitsSnapshotOf(options.limits ?? DEFAULT_SKILL_LIMITS);
|
|
93
|
-
const validationPlan = buildValidationPlan({ tree: tree.value, target: decision.target, limits: limitsSnapshot, cards: options.cards, promptVersion: MERGE_PROMPT_VERSION, promptHash: promptHashOf(buildRound2Prompt(row, decision)), supportFiles: tree.value.files.map(file => ({ path: file.path, content: file.content, source: 'staged' })), manifestFiles, existingSupport: (options.cards.find(card => card.name === decision.target)?.files ?? []).map(file => file.path), supportSources: options.cards.filter(card => card.name === row.a || card.name === row.b).flatMap(card => (card.files ?? []).map(file => ({ path: file.path, content: file.content, source: card.name }))) });
|
|
93
|
+
const validationPlan = buildValidationPlan({ tree: tree.value, target: decision.target, limits: limitsSnapshot, cards: options.cards, promptVersion: MERGE_PROMPT_VERSION, promptHash: promptHashOf(buildRound2Prompt(row, decision)), supportFiles: tree.value.files.map(file => ({ path: file.path, content: file.content, source: 'staged' })), manifestFiles, participants: [row.a, row.b, decision.target], existingSupport: (options.cards.find(card => card.name === decision.target)?.files ?? []).map(file => file.path), supportSources: options.cards.filter(card => card.name === row.a || card.name === row.b).flatMap(card => (card.files ?? []).map(file => ({ path: file.path, content: file.content, source: card.name }))) });
|
|
94
94
|
const findings = [...validateStagedTree(tree.value, limitsSnapshot.skillLimits), ...validationPlan.findings];
|
|
95
95
|
if (hasBlockingFinding(findings)) {
|
|
96
96
|
const lengths = { bodyBytes: Buffer.byteLength(tree.value.body, 'utf8'), fileBytes: tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0), totalBytes: Buffer.byteLength(tree.value.body, 'utf8') + tree.value.files.reduce((sum, file) => sum + Buffer.byteLength(file.content, 'utf8'), 0) };
|
package/lib/types/runtime.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { MergeRegistryPort } from './run-shell.ts';
|
|
|
6
6
|
import type { MergeCommandService } from './merge-command.ts';
|
|
7
7
|
import type { StageToolContext } from './stage-tool.ts';
|
|
8
8
|
import type { ConversationChannelLike } from './channel-port.ts';
|
|
9
|
+
import type { HostCapabilities } from './host-capabilities.ts';
|
|
9
10
|
export declare function forcedDecisionFor(a: string, b: string): MergeDecision;
|
|
10
11
|
export interface MergeStageBindingPort {
|
|
11
12
|
bind(context: StageToolContext): void;
|
|
@@ -20,6 +21,7 @@ export interface MergeRuntimeDeps {
|
|
|
20
21
|
readonly params: MergeParams;
|
|
21
22
|
readonly platformWrite: boolean;
|
|
22
23
|
readonly channel: ConversationChannelLike;
|
|
24
|
+
readonly hostCapabilities?: HostCapabilities;
|
|
23
25
|
readonly parent: unknown;
|
|
24
26
|
readonly allow: readonly string[];
|
|
25
27
|
readonly cwd?: string;
|
package/lib/types/runtime.js
CHANGED
|
@@ -38,6 +38,12 @@ function fail(stage, code, hint, next, runId) {
|
|
|
38
38
|
function failRun(stage, hint, next, result) {
|
|
39
39
|
return result.error === undefined ? fail(stage, 'run-failed', hint, next, result.runId) : { ok: false, text: formatMergeError(result.error) };
|
|
40
40
|
}
|
|
41
|
+
async function hostOnlyFailure(verb, capabilities) {
|
|
42
|
+
if (capabilities === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
const state = await capabilities.hostOnly();
|
|
45
|
+
return state === undefined ? undefined : fail(verb, 'e-323', 'host data plane unavailable: ' + state.code + (state.detail === undefined ? '' : ' (' + state.detail + ')'), 'run /evolution merge doctor');
|
|
46
|
+
}
|
|
41
47
|
/**
|
|
42
48
|
* The approved/fuse row that already holds a target. Pair/approve move a row to
|
|
43
49
|
* approved/fuse BEFORE their run starts, so this row IS the in-flight claim a second
|
|
@@ -100,6 +106,9 @@ export function createMergeRuntime(deps) {
|
|
|
100
106
|
// the three-line shape instead of opening a run that is guaranteed to fail.
|
|
101
107
|
if (agentless)
|
|
102
108
|
return fail('pair', 'e-305', errorText('e-305-this-invocation-carries-no', { a1: 'pair' }), '在 GUI 或 CLI 的会话里运行 /evolution merge pair ' + command.a + ' ' + command.b);
|
|
109
|
+
const pairHost = await hostOnlyFailure('pair', deps.hostCapabilities);
|
|
110
|
+
if (pairHost !== undefined)
|
|
111
|
+
return pairHost;
|
|
103
112
|
// FR-EXEC-17/NFR-04: two pair runs for one pair must not race for the same row.
|
|
104
113
|
const pairKeyValue = pairKey(command.a, command.b);
|
|
105
114
|
const inFlight = inFlightPairRun(await readRunMetas(deps.io, deps.home), pairKeyValue);
|
|
@@ -179,6 +188,9 @@ export function createMergeRuntime(deps) {
|
|
|
179
188
|
if (command.verb === 'scan') {
|
|
180
189
|
if (agentless)
|
|
181
190
|
return fail('scan', 'e-305', errorText('e-305-this-invocation-carries-no', { a1: 'scan' }), '在 GUI 或 CLI 的会话里运行 /evolution merge scan');
|
|
191
|
+
const scanHost = await hostOnlyFailure('scan', deps.hostCapabilities);
|
|
192
|
+
if (scanHost !== undefined)
|
|
193
|
+
return scanHost;
|
|
182
194
|
const signal = new AbortController().signal;
|
|
183
195
|
const cards = await deps.cards(signal);
|
|
184
196
|
const blocked = new Set();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-evolution-merge",
|
|
3
3
|
"description": "Skill auto-merge domain: screening, two-round authoring, queues and commit orchestration (community build)",
|
|
4
|
-
"version": "0.30.
|
|
4
|
+
"version": "0.30.28",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.30.
|
|
36
|
-
"@lmzhen/dsh-evolution-commands": "^0.30.
|
|
37
|
-
"@lmzhen/dsh-evolution-merge-engine": "0.30.
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.30.28",
|
|
36
|
+
"@lmzhen/dsh-evolution-commands": "^0.30.28",
|
|
37
|
+
"@lmzhen/dsh-evolution-merge-engine": "0.30.28"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.5"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
|
-
"dsh": ">=0.2.1-rc.
|
|
43
|
+
"dsh": ">=0.2.1-rc.2 <0.2.2-0"
|
|
44
44
|
}
|
|
45
45
|
}
|