@lmzhen/dsh-evolution-merge 0.30.25 → 0.30.27

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 CHANGED
@@ -8377,7 +8377,8 @@ async function runAuthoring(options) {
8377
8377
  message: "target must be a bare skill name: " + options.target
8378
8378
  };
8379
8379
  const targetCard = options.cards.find((card) => card.name === options.target);
8380
- const mode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
8380
+ const requestedMode = options.mode ?? (targetCard === void 0 ? "create-umbrella" : "merge-into-existing");
8381
+ const mode = requestedMode === "merge-into-existing" && targetCard === void 0 ? "create-umbrella" : requestedMode;
8381
8382
  const createMode = mode === "create-umbrella";
8382
8383
  if (!createMode && targetCard !== void 0) {
8383
8384
  if (targetCard.managed !== true) return {
@@ -9080,6 +9081,11 @@ function failRun(stage, hint, next, result) {
9080
9081
  text: formatMergeError(result.error)
9081
9082
  };
9082
9083
  }
9084
+ async function hostOnlyFailure(verb, capabilities) {
9085
+ if (capabilities === void 0) return void 0;
9086
+ const state = await capabilities.hostOnly();
9087
+ 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");
9088
+ }
9083
9089
  /**
9084
9090
  * The approved/fuse row that already holds a target. Pair/approve move a row to
9085
9091
  * approved/fuse BEFORE their run starts, so this row IS the in-flight claim a second
@@ -9162,6 +9168,8 @@ function createMergeRuntime(deps) {
9162
9168
  text: "merge: 技能名不能包含 provider/name 或 provider:name 语法:" + command.a + " / " + command.b + " (provider-conflict)\nusage: /evolution merge pair <a> <b>"
9163
9169
  };
9164
9170
  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);
9171
+ const pairHost = await hostOnlyFailure("pair", deps.hostCapabilities);
9172
+ if (pairHost !== void 0) return pairHost;
9165
9173
  const pairKeyValue = pairKey(command.a, command.b);
9166
9174
  const inFlight = inFlightPairRun(await readRunMetas(deps.io, deps.home), pairKeyValue);
9167
9175
  let ownerActive = false;
@@ -9239,6 +9247,8 @@ function createMergeRuntime(deps) {
9239
9247
  }
9240
9248
  if (command.verb === "scan") {
9241
9249
  if (agentless) return fail("scan", "e-305", errorText("e-305-this-invocation-carries-no", { a1: "scan" }), "在 GUI 或 CLI 的会话里运行 /evolution merge scan");
9250
+ const scanHost = await hostOnlyFailure("scan", deps.hostCapabilities);
9251
+ if (scanHost !== void 0) return scanHost;
9242
9252
  const signal = new AbortController().signal;
9243
9253
  const cards = await deps.cards(signal);
9244
9254
  const blocked = /* @__PURE__ */ new Set();
@@ -9756,6 +9766,31 @@ function createMergeRuntime(deps) {
9756
9766
  } };
9757
9767
  }
9758
9768
  //#endregion
9769
+ //#region lib/types/host-capabilities.js
9770
+ /**
9771
+ * The host's data-plane capability as ONE value shared by snapshot, doctor, HTTP and run preflight.
9772
+ * The probe is injected so the Cordis wiring stays in index.ts; a rejected probe is the same E-323
9773
+ * answer as an unavailable one, never an unhandled promise.
9774
+ * @module @lmzhen/dsh-evolution-merge
9775
+ */
9776
+ /**
9777
+ * Wrap one probe with the shared error normalization.
9778
+ * @param probe - the platform-specific availability check.
9779
+ * @returns the capability value consumed by snapshot and runtime.
9780
+ */
9781
+ function createHostCapabilities(probe) {
9782
+ return { hostOnly: async () => {
9783
+ try {
9784
+ return await probe();
9785
+ } catch (error) {
9786
+ return {
9787
+ code: "E-323",
9788
+ detail: error instanceof Error ? error.message : String(error)
9789
+ };
9790
+ }
9791
+ } };
9792
+ }
9793
+ //#endregion
9759
9794
  //#region lib/types/params.js
9760
9795
  /**
9761
9796
  * Merge parameter resolution. E2/E3 values arrive as one JSON object; this is the only
@@ -10534,6 +10569,16 @@ function apply(ctx, rawConfig = {}) {
10534
10569
  });
10535
10570
  const platformChannel = createSubagentChannel(ctx);
10536
10571
  const channel = subagentChannelAdapter(platformChannel);
10572
+ const hostCapabilities = createHostCapabilities(async () => {
10573
+ const state = await platformChannel.prepare({
10574
+ kind: "reviewer",
10575
+ allow: ["skill"]
10576
+ });
10577
+ return state.kind === "unavailable" ? {
10578
+ code: "E-323",
10579
+ detail: state.reason
10580
+ } : void 0;
10581
+ });
10537
10582
  const stage = new StageToolRegistry();
10538
10583
  const engineInput = resolveEngineInput({ modelsRoot: params.mergeModelsRoot });
10539
10584
  const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
@@ -10597,23 +10642,7 @@ function apply(ctx, rawConfig = {}) {
10597
10642
  runs: runs.runs("merge"),
10598
10643
  approval: params.mergeApproval,
10599
10644
  platformWrite,
10600
- hostOnly: async () => {
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
- },
10645
+ hostOnly: hostCapabilities.hostOnly,
10617
10646
  live: async () => liveRowFactsOf(await readSkillCards(skills)),
10618
10647
  history: async () => {
10619
10648
  const items = /* @__PURE__ */ new Map();
@@ -10707,6 +10736,7 @@ function apply(ctx, rawConfig = {}) {
10707
10736
  parent: void 0,
10708
10737
  allow: ["merge_stage"],
10709
10738
  timeoutMinutes: params.mergeRunTimeoutMinutes,
10739
+ hostCapabilities,
10710
10740
  isBlocked: async (name) => {
10711
10741
  return await ctx.get("evolutionCurator")?.isBlocked?.(name) === true;
10712
10742
  },
@@ -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
- const mode = options.mode ?? (targetCard === undefined ? 'create-umbrella' : 'merge-into-existing');
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)
@@ -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
@@ -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;
@@ -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;
@@ -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.25",
4
+ "version": "0.30.27",
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.25",
36
- "@lmzhen/dsh-evolution-commands": "^0.30.25",
37
- "@lmzhen/dsh-evolution-merge-engine": "0.30.25"
35
+ "@lmzhen/dsh-evolution-core": "^0.30.27",
36
+ "@lmzhen/dsh-evolution-commands": "^0.30.27",
37
+ "@lmzhen/dsh-evolution-merge-engine": "0.30.27"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/cordis": "^4.0.5"
41
41
  },
42
42
  "engines": {
43
- "dsh": ">=0.2.1-rc.1 <0.2.2-0"
43
+ "dsh": ">=0.2.1-rc.2 <0.2.2-0"
44
44
  }
45
45
  }