@lmzhen/dsh-evolution-merge 0.30.17 → 0.30.19

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.
Files changed (187) hide show
  1. package/README.md +25 -7
  2. package/data/minhash_params.json +1063 -0
  3. package/lib/index.js +7308 -1800
  4. package/lib/types/adapters/channel.d.ts +1 -0
  5. package/lib/types/adapters/channel.js +15 -1
  6. package/lib/types/adapters/engine-screen.d.ts +1 -1
  7. package/lib/types/adapters/engine-screen.js +5 -2
  8. package/lib/types/adapters/library.js +4 -1
  9. package/lib/types/adapters/model-runner.d.ts +0 -5
  10. package/lib/types/adapters/model-runner.js +8 -2
  11. package/lib/types/adapters/model.d.ts +31 -0
  12. package/lib/types/adapters/model.js +47 -0
  13. package/lib/types/adapters/select.js +12 -8
  14. package/lib/types/approve-command.d.ts +14 -1
  15. package/lib/types/approve-command.js +72 -44
  16. package/lib/types/authoring-flow.d.ts +19 -1
  17. package/lib/types/authoring-flow.js +49 -15
  18. package/lib/types/channel-port.d.ts +3 -1
  19. package/lib/types/channel-port.js +8 -3
  20. package/lib/types/command-defs.d.ts +5 -0
  21. package/lib/types/command-defs.js +33 -0
  22. package/lib/types/command-surface.d.ts +5 -0
  23. package/lib/types/command-surface.js +11 -0
  24. package/lib/types/commands.d.ts +3 -4
  25. package/lib/types/commands.js +33 -72
  26. package/lib/types/commit-assembly.d.ts +16 -1
  27. package/lib/types/commit-assembly.js +72 -11
  28. package/lib/types/commit-execute.d.ts +14 -2
  29. package/lib/types/commit-execute.js +76 -14
  30. package/lib/types/commit-plan.d.ts +7 -1
  31. package/lib/types/commit-plan.js +4 -0
  32. package/lib/types/commit-receipt.d.ts +3 -0
  33. package/lib/types/commit-recovery.d.ts +7 -2
  34. package/lib/types/commit-recovery.js +73 -96
  35. package/lib/types/contracts.d.ts +44 -1
  36. package/lib/types/contracts.js +1 -1
  37. package/lib/types/coordinator.d.ts +3 -2
  38. package/lib/types/coordinator.js +4 -3
  39. package/lib/types/decision.js +1 -1
  40. package/lib/types/doctor.d.ts +18 -1
  41. package/lib/types/doctor.js +164 -10
  42. package/lib/types/domain/authoring-manifest.d.ts +26 -0
  43. package/lib/types/domain/authoring-manifest.js +43 -0
  44. package/lib/types/domain/claim.d.ts +4 -1
  45. package/lib/types/domain/claim.js +2 -2
  46. package/lib/types/domain/commit-journal-store.js +5 -1
  47. package/lib/types/domain/commit-journal.js +1 -1
  48. package/lib/types/domain/consistency.d.ts +22 -0
  49. package/lib/types/domain/consistency.js +50 -0
  50. package/lib/types/domain/engine-criteria.d.ts +10 -0
  51. package/lib/types/domain/engine-criteria.js +11 -0
  52. package/lib/types/domain/engine-input.d.ts +10 -0
  53. package/lib/types/domain/engine-input.js +26 -0
  54. package/lib/types/domain/errors.d.ts +15 -1
  55. package/lib/types/domain/errors.js +23 -4
  56. package/lib/types/domain/evidence-hash.d.ts +3 -0
  57. package/lib/types/domain/evidence-hash.js +14 -0
  58. package/lib/types/domain/failure-route.d.ts +14 -0
  59. package/lib/types/domain/failure-route.js +12 -0
  60. package/lib/types/domain/ledger.js +10 -1
  61. package/lib/types/domain/live-facts.d.ts +31 -0
  62. package/lib/types/domain/live-facts.js +54 -0
  63. package/lib/types/domain/merge-limits.d.ts +18 -0
  64. package/lib/types/domain/merge-limits.js +22 -0
  65. package/lib/types/domain/merge-report.d.ts +41 -0
  66. package/lib/types/domain/merge-report.js +2 -0
  67. package/lib/types/domain/merge-snapshot.d.ts +99 -0
  68. package/lib/types/domain/merge-snapshot.js +82 -0
  69. package/lib/types/domain/migrate-v3.d.ts +20 -0
  70. package/lib/types/domain/migrate-v3.js +68 -0
  71. package/lib/types/domain/pair-ledger.d.ts +34 -0
  72. package/lib/types/domain/pair-ledger.js +96 -0
  73. package/lib/types/domain/pre-1b-backup.d.ts +43 -0
  74. package/lib/types/domain/pre-1b-backup.js +140 -0
  75. package/lib/types/domain/provider-key.d.ts +2 -0
  76. package/lib/types/domain/provider-key.js +11 -0
  77. package/lib/types/domain/read-probe.d.ts +5 -0
  78. package/lib/types/domain/read-probe.js +11 -0
  79. package/lib/types/domain/reconcile.d.ts +47 -0
  80. package/lib/types/domain/reconcile.js +178 -0
  81. package/lib/types/domain/resolve-pair.d.ts +2 -9
  82. package/lib/types/domain/resolve-pair.js +2 -15
  83. package/lib/types/domain/row-tx.d.ts +27 -0
  84. package/lib/types/domain/row-tx.js +31 -0
  85. package/lib/types/domain/row-v3-bridge.d.ts +6 -0
  86. package/lib/types/domain/row-v3-bridge.js +47 -0
  87. package/lib/types/domain/row-v3.d.ts +113 -0
  88. package/lib/types/domain/row-v3.js +107 -0
  89. package/lib/types/domain/row-v4.d.ts +148 -0
  90. package/lib/types/domain/row-v4.js +159 -0
  91. package/lib/types/domain/row.d.ts +1 -0
  92. package/lib/types/domain/run-coordinator.d.ts +51 -0
  93. package/lib/types/domain/run-coordinator.js +45 -0
  94. package/lib/types/domain/schema4.d.ts +53 -0
  95. package/lib/types/domain/schema4.js +129 -0
  96. package/lib/types/domain/selection-policy.d.ts +14 -0
  97. package/lib/types/domain/selection-policy.js +6 -0
  98. package/lib/types/domain/skipped-expiry.d.ts +5 -0
  99. package/lib/types/domain/skipped-expiry.js +16 -0
  100. package/lib/types/domain/stage-aggregate.d.ts +96 -0
  101. package/lib/types/domain/stage-aggregate.js +293 -0
  102. package/lib/types/domain/staging-index.d.ts +15 -0
  103. package/lib/types/domain/staging-index.js +41 -0
  104. package/lib/types/domain/state-bootstrap.d.ts +43 -0
  105. package/lib/types/domain/state-bootstrap.js +267 -0
  106. package/lib/types/domain/state-v3.d.ts +4 -0
  107. package/lib/types/domain/state-v3.js +4 -0
  108. package/lib/types/domain/undo-journal.d.ts +19 -0
  109. package/lib/types/domain/undo-journal.js +20 -0
  110. package/lib/types/domain/undo-receipt.d.ts +18 -0
  111. package/lib/types/domain/undo-receipt.js +22 -0
  112. package/lib/types/domain/validation-plan.d.ts +65 -0
  113. package/lib/types/domain/validation-plan.js +49 -0
  114. package/lib/types/facts.d.ts +26 -0
  115. package/lib/types/facts.js +65 -0
  116. package/lib/types/gate-filter.d.ts +31 -0
  117. package/lib/types/gate-filter.js +64 -0
  118. package/lib/types/index.d.ts +7 -2
  119. package/lib/types/index.js +185 -82
  120. package/lib/types/ladder.js +1 -1
  121. package/lib/types/merge-command.d.ts +3 -1
  122. package/lib/types/merge-command.js +10 -6
  123. package/lib/types/merge-routes.d.ts +12 -7
  124. package/lib/types/merge-routes.js +87 -23
  125. package/lib/types/merge-tool.d.ts +1 -1
  126. package/lib/types/merge-tool.js +2 -2
  127. package/lib/types/metrics.d.ts +16 -0
  128. package/lib/types/metrics.js +27 -0
  129. package/lib/types/new-child-authoring.d.ts +5 -1
  130. package/lib/types/new-child-authoring.js +71 -13
  131. package/lib/types/params.d.ts +2 -4
  132. package/lib/types/params.js +13 -0
  133. package/lib/types/pending-actions.d.ts +2 -2
  134. package/lib/types/pending-actions.js +17 -17
  135. package/lib/types/prompts.d.ts +2 -4
  136. package/lib/types/prompts.js +21 -8
  137. package/lib/types/propose.d.ts +26 -0
  138. package/lib/types/propose.js +69 -0
  139. package/lib/types/read-commands.d.ts +4 -4
  140. package/lib/types/read-commands.js +19 -26
  141. package/lib/types/recovery-coordinator.d.ts +34 -0
  142. package/lib/types/recovery-coordinator.js +84 -0
  143. package/lib/types/recovery-projection.d.ts +31 -0
  144. package/lib/types/recovery-projection.js +96 -0
  145. package/lib/types/resume-command.d.ts +7 -1
  146. package/lib/types/resume-command.js +50 -14
  147. package/lib/types/revision.d.ts +17 -0
  148. package/lib/types/revision.js +39 -3
  149. package/lib/types/round2-outcome.d.ts +2 -1
  150. package/lib/types/round2-outcome.js +68 -12
  151. package/lib/types/run-shell.d.ts +16 -2
  152. package/lib/types/run-shell.js +49 -5
  153. package/lib/types/runtime.d.ts +4 -6
  154. package/lib/types/runtime.js +341 -86
  155. package/lib/types/scan-command.d.ts +1 -0
  156. package/lib/types/scan-command.js +2 -0
  157. package/lib/types/scan-ports.d.ts +21 -1
  158. package/lib/types/scan-ports.js +51 -23
  159. package/lib/types/scan-run.d.ts +17 -0
  160. package/lib/types/scan-run.js +221 -63
  161. package/lib/types/screening-port.d.ts +47 -0
  162. package/lib/types/screening-port.js +93 -0
  163. package/lib/types/screening.d.ts +30 -0
  164. package/lib/types/screening.js +75 -0
  165. package/lib/types/stage-tool-registry.d.ts +7 -0
  166. package/lib/types/stage-tool-registry.js +12 -1
  167. package/lib/types/state.d.ts +15 -1
  168. package/lib/types/state.js +39 -17
  169. package/lib/types/status.d.ts +8 -0
  170. package/lib/types/status.js +64 -0
  171. package/lib/types/surface/body.d.ts +4 -0
  172. package/lib/types/surface/body.js +16 -0
  173. package/lib/types/surface/merge-view.d.ts +14 -0
  174. package/lib/types/surface/merge-view.js +126 -0
  175. package/lib/types/target-claim.d.ts +1 -0
  176. package/lib/types/target-claim.js +63 -18
  177. package/lib/types/undo-operation.d.ts +24 -0
  178. package/lib/types/undo-operation.js +135 -0
  179. package/lib/types/unit-claim.d.ts +5 -0
  180. package/lib/types/unit-claim.js +15 -0
  181. package/lib/types/wire-projection.d.ts +36 -0
  182. package/lib/types/wire-projection.js +69 -0
  183. package/lib/types/wire.d.ts +273 -0
  184. package/lib/types/wire.js +3 -0
  185. package/package.json +10 -4
  186. package/lib/types/routes.d.ts +0 -70
  187. package/lib/types/routes.js +0 -78
@@ -8,6 +8,7 @@ interface PlatformTurn {
8
8
  readonly childId: unknown;
9
9
  readonly stopReason: string;
10
10
  readonly text: string;
11
+ readonly usage?: unknown;
11
12
  }
12
13
  type PlatformOutcome = {
13
14
  readonly kind: 'ok';
@@ -1,10 +1,24 @@
1
+ function usageOf(value) {
2
+ if (value === null || typeof value !== 'object')
3
+ return undefined;
4
+ const raw = value;
5
+ const num = (input) => typeof input === 'number' && Number.isFinite(input) ? input : undefined;
6
+ const inputTokens = num(raw.inputTokens);
7
+ const outputTokens = num(raw.outputTokens);
8
+ const totalTokens = num(raw.totalTokens);
9
+ const costCny = num(raw.costCny);
10
+ if (inputTokens === undefined && outputTokens === undefined && totalTokens === undefined && costCny === undefined)
11
+ return undefined;
12
+ return { ...(inputTokens === undefined ? {} : { inputTokens }), ...(outputTokens === undefined ? {} : { outputTokens }), ...(totalTokens === undefined ? {} : { totalTokens }), ...(costCny === undefined ? {} : { costCny }) };
13
+ }
1
14
  function convert(outcome) {
2
15
  if (outcome.kind !== 'ok')
3
16
  return outcome;
17
+ const usage = usageOf(outcome.turn.usage);
4
18
  return {
5
19
  kind: 'ok',
6
20
  childId: String(outcome.childId),
7
- turn: { text: outcome.turn.text, stopReason: outcome.turn.stopReason },
21
+ turn: { text: outcome.turn.text, stopReason: outcome.turn.stopReason, ...(usage === undefined ? {} : { usage }) },
8
22
  };
9
23
  }
10
24
  export function subagentChannelAdapter(channel) {
@@ -47,7 +47,7 @@ export interface EngineScreeningDeps {
47
47
  readonly select: (report: Record<string, unknown>) => CandidateSelection;
48
48
  readonly cfg?: Record<string, unknown>;
49
49
  readonly runner?: (prompt: string, cfg?: Record<string, unknown>) => Promise<string>;
50
- readonly onSelection?: (selection: CandidateSelection, runId: string) => Promise<void>;
50
+ readonly onSelection?: (selection: CandidateSelection, runId: string, cards: readonly SkillCard[]) => Promise<void>;
51
51
  }
52
52
  /** Write the exact card bodies as a support-free corpus snapshot and return its root. */
53
53
  export declare function snapshotCards(io: EvolutionIoLike, home: string, runId: string, cards: readonly SkillCard[]): Promise<string>;
@@ -38,8 +38,11 @@ export function createEngineScreening(deps) {
38
38
  }
39
39
  const selection = deps.select(result.report);
40
40
  if (deps.onSelection !== undefined)
41
- await deps.onSelection(selection, runId);
42
- return Object.assign(selection.candidates.slice(), selection.candidatePairs === undefined ? {} : { candidatePairs: selection.candidatePairs });
41
+ await deps.onSelection(selection, runId, cards);
42
+ const degraded = selection.reportOnly
43
+ .filter(row => row.error === true)
44
+ .map(row => { const value = row; return 'gate error: ' + String(value.a ?? '?') + ' + ' + String(value.b ?? '?'); });
45
+ return Object.assign(selection.candidates.slice(), selection.candidatePairs === undefined ? {} : { candidatePairs: selection.candidatePairs }, degraded.length === 0 ? {} : { degraded }, selection.reportOnly.length === 0 ? {} : { reportOnly: selection.reportOnly });
43
46
  };
44
47
  }
45
48
  //# sourceMappingURL=engine-screen.js.map
@@ -8,7 +8,10 @@ export function skillLibraryPort(skills) {
8
8
  const names = await archivedNames.call(skills);
9
9
  return names === null ? true : names.includes(name);
10
10
  },
11
- commitMerge: plan => skills.commitMerge(plan),
11
+ isNameOccupied: async (name) => {
12
+ const probe = skills.skillDirExists;
13
+ return typeof probe === 'function' ? await probe.call(skills, name) : false;
14
+ },
12
15
  async readSupportFiles(name) {
13
16
  if (typeof skills.listSupportFiles !== 'function')
14
17
  return [];
@@ -1,8 +1,3 @@
1
- /**
2
- * DeepSeek judge runner over the platform llm service. The engine calls
3
- * runner(prompt, cfg) and expects raw model text; the platform owns provider
4
- * selection, usage accounting and transport.
5
- */
6
1
  export interface ModelRoute {
7
2
  readonly provider: string;
8
3
  readonly model: string;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * DeepSeek judge runner over the platform llm service. The engine calls
3
+ * runner(prompt, cfg) and expects raw model text; the platform owns provider
4
+ * selection, usage accounting and transport.
5
+ */
6
+ import { normalizeProviderKey } from "../domain/provider-key.js";
1
7
  function append(chunk, text) {
2
8
  if (chunk.type === 'text-delta' || chunk.type === 'text')
3
9
  return text + (chunk.text ?? '');
@@ -12,7 +18,7 @@ export function createEngineRunner(llm, route) {
12
18
  let text = '';
13
19
  if (llm.stream !== undefined) {
14
20
  for await (const chunk of llm.stream({
15
- provider: selection.provider,
21
+ provider: normalizeProviderKey(selection.provider),
16
22
  model: selection.model,
17
23
  messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
18
24
  temperature: 0,
@@ -22,7 +28,7 @@ export function createEngineRunner(llm, route) {
22
28
  }
23
29
  if (llm.prepareCall === undefined)
24
30
  throw new Error('llm service has neither stream nor prepareCall');
25
- const call = await llm.prepareCall({ provider: selection.provider, model: selection.model, temperature: 0 });
31
+ const call = await llm.prepareCall({ provider: normalizeProviderKey(selection.provider), model: selection.model, temperature: 0 });
26
32
  for await (const chunk of call.stream({
27
33
  ...call.config,
28
34
  messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Structural ModelPort adapter over the platform llm service. The platform exposes either
3
+ * `stream()` (the current shape) or the older prepareCall()/stream pair; both return raw text
4
+ * because the gate and proposer own JSON extraction.
5
+ */
6
+ import type { ModelPort } from '../propose.ts';
7
+ export interface ModelRoute {
8
+ readonly provider: string;
9
+ readonly model: string;
10
+ }
11
+ export interface LlmChunkLike {
12
+ readonly type: string;
13
+ readonly text?: string;
14
+ readonly usage?: unknown;
15
+ readonly reason?: {
16
+ readonly kind?: string;
17
+ readonly failure?: {
18
+ readonly message?: string;
19
+ };
20
+ };
21
+ }
22
+ export interface LlmCallLike {
23
+ readonly config: Record<string, unknown>;
24
+ stream(input: Record<string, unknown>): AsyncIterable<LlmChunkLike>;
25
+ }
26
+ export interface LlmServiceLike {
27
+ prepareCall?(config: Record<string, unknown>, signal?: AbortSignal): Promise<LlmCallLike>;
28
+ stream?(options: Record<string, unknown>): AsyncIterable<LlmChunkLike>;
29
+ }
30
+ export declare function createModelPort(llm: LlmServiceLike, route: () => ModelRoute): ModelPort;
31
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1,47 @@
1
+ import { normalizeProviderKey } from "../domain/provider-key.js";
2
+ function append(chunk, text) {
3
+ if (chunk.type === 'text-delta' || chunk.type === 'text')
4
+ return text + (chunk.text ?? '');
5
+ if (chunk.type === 'finish' && chunk.reason?.kind === 'error') {
6
+ throw new Error('model call failed: ' + (chunk.reason.failure?.message ?? 'unknown'));
7
+ }
8
+ return text;
9
+ }
10
+ export function createModelPort(llm, route) {
11
+ return {
12
+ async callJson(request) {
13
+ const selection = route();
14
+ let text = '';
15
+ if (llm.stream !== undefined) {
16
+ for await (const chunk of llm.stream({
17
+ provider: normalizeProviderKey(selection.provider),
18
+ model: selection.model,
19
+ system: request.system,
20
+ messages: [{ role: 'user', content: [{ type: 'text', text: request.user }] }],
21
+ maxTokens: request.maxTokens,
22
+ temperature: 0,
23
+ signal: request.signal,
24
+ }))
25
+ text = append(chunk, text);
26
+ return text;
27
+ }
28
+ if (llm.prepareCall === undefined)
29
+ throw new Error('llm service has neither stream nor prepareCall');
30
+ const call = await llm.prepareCall({
31
+ provider: selection.provider,
32
+ model: selection.model,
33
+ maxTokens: request.maxTokens,
34
+ temperature: 0,
35
+ }, request.signal);
36
+ for await (const chunk of call.stream({
37
+ ...call.config,
38
+ system: request.system,
39
+ messages: [{ role: 'user', content: [{ type: 'text', text: request.user }] }],
40
+ signal: request.signal,
41
+ }))
42
+ text = append(chunk, text);
43
+ return text;
44
+ },
45
+ };
46
+ }
47
+ //# sourceMappingURL=model.js.map
@@ -1,5 +1,5 @@
1
1
  /** Host selector correction: engine selector does not route judge errors to candidates. */
2
- import { pairKeyOf, selectCandidates, toEngineCandidate } from '@lmzhen/dsh-evolution-merge-engine';
2
+ import { pairKeyOf, selectCandidates } from '@lmzhen/dsh-evolution-merge-engine';
3
3
  export function selectWithErrors(report) {
4
4
  const base = selectCandidates(report);
5
5
  const doc = (report.reportDoc ?? report);
@@ -13,22 +13,26 @@ export function selectWithErrors(report) {
13
13
  if (a !== '' && b !== '')
14
14
  errors.add(pairKeyOf(a, b));
15
15
  }
16
- const candidates = base.candidates.filter(candidate => !errors.has(pairKeyOf(candidate.a, candidate.b)));
17
- const skipped = base.skipped.filter(item => !errors.has(item.pairKey));
16
+ const candidates = base.candidates.filter(candidate => !errors.has(pairKeyOf(candidate.a, candidate.b))).filter(candidate => {
17
+ const gate = candidate.gate;
18
+ return gate?.level === 'L2' || gate?.verdict === 1;
19
+ });
18
20
  const reportOnly = base.reportOnly.filter((row) => {
19
21
  const value = row;
20
22
  if (typeof value.a !== 'string' || typeof value.b !== 'string')
21
23
  return true;
22
24
  return !errors.has(pairKeyOf(value.a, value.b));
23
25
  });
26
+ for (const candidate of base.candidates) {
27
+ const level = candidate.gate?.level;
28
+ if (level !== 'L2')
29
+ reportOnly.push(candidate);
30
+ }
31
+ const skipped = base.skipped.filter(item => !errors.has(item.pairKey));
24
32
  for (const row of rows) {
25
33
  if (row.error !== true)
26
34
  continue;
27
- const a = typeof row.a === 'string' ? row.a : '';
28
- const b = typeof row.b === 'string' ? row.b : '';
29
- if (a === '' || b === '')
30
- continue;
31
- candidates.push(toEngineCandidate(row, typeof row.reason === 'string' ? row.reason : 'judge-error'));
35
+ reportOnly.push(row);
32
36
  }
33
37
  return { ...base, candidates, skipped, reportOnly };
34
38
  }
@@ -2,11 +2,19 @@
2
2
  * approve command: one pending row is atomically taken to fuse, then a new run executes
3
3
  * round2 with the persisted decision. A second approve loses at approvePending (not-pending).
4
4
  */
5
- import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
5
+ import { type EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
6
6
  import type { MergeDecision, MergeRow } from './contracts.ts';
7
+ import { type StageEntry } from './domain/stage-aggregate.ts';
7
8
  import type { MergeRegistryPort, WithMergeRunResult } from './run-shell.ts';
8
9
  import type { ScanRound2Outcome } from './scan-run.ts';
9
10
  export interface ApprovePorts {
11
+ stage?(row: MergeRow, decision: MergeDecision): Promise<{
12
+ readonly ok: true;
13
+ readonly entry: StageEntry;
14
+ } | {
15
+ readonly ok: false;
16
+ readonly message: string;
17
+ }>;
10
18
  round2(row: MergeRow, decision: MergeDecision, signal: AbortSignal, runId: string): Promise<ScanRound2Outcome>;
11
19
  }
12
20
  export interface RunApproveCommandOptions {
@@ -16,7 +24,12 @@ export interface RunApproveCommandOptions {
16
24
  readonly ports: ApprovePorts;
17
25
  readonly rowId: string;
18
26
  readonly platformWrite: boolean;
27
+ readonly parentRunId?: string;
28
+ readonly detached?: boolean;
29
+ readonly pairTimeoutMinutes?: number;
19
30
  readonly now: () => number;
31
+ /** The invocation carried no agent at all (E-305), as opposed to an agent with no parent run. */
32
+ readonly agentless?: boolean;
20
33
  }
21
34
  export declare function runApproveCommand(options: RunApproveCommandOptions): Promise<WithMergeRunResult>;
22
35
  //# sourceMappingURL=approve-command.d.ts.map
@@ -1,55 +1,83 @@
1
+ /**
2
+ * approve command: one pending row is atomically taken to fuse, then a new run executes
3
+ * round2 with the persisted decision. A second approve loses at approvePending (not-pending).
4
+ */
5
+ import { errorText } from '@lmzhen/dsh-evolution-core';
1
6
  import { approvePending } from "./pending-actions.js";
7
+ import { pairLedger } from "./domain/pair-ledger.js";
8
+ import { presentStage } from "./domain/stage-aggregate.js";
9
+ import { v4ToLegacy } from "./domain/row-v4.js";
2
10
  import { loadMergeState } from "./state.js";
3
- import { acquireTargetClaim } from "./target-claim.js";
4
11
  import { withMergeRun } from "./run-shell.js";
5
12
  import { recordRound2Outcome } from "./round2-outcome.js";
13
+ import { failure } from "./domain/errors.js";
6
14
  export async function runApproveCommand(options) {
7
15
  const loaded = await loadMergeState(options.io, options.home);
8
16
  const before = loaded.state.rows.find(item => item.id === options.rowId);
9
- const target = before?.decision?.target;
10
- const claim = target === undefined ? null : await acquireTargetClaim(options.io, options.home, target, options.now());
11
- if (target !== undefined && claim === null)
12
- return { ok: false, message: 'target already claimed by another approve: ' + target };
13
- try {
14
- const taken = await approvePending(options.io, options.home, options.now, options.rowId);
15
- if (!taken.ok)
16
- return { ok: false, message: taken.message };
17
- const row = taken.row;
18
- const decision = row.decision;
19
- if (decision === undefined)
20
- return { ok: false, message: 'pending row has no decision' };
21
- return await withMergeRun({
22
- io: options.io,
23
- home: options.home,
24
- registry: options.registry,
25
- command: 'approve',
26
- approval: { platformWrite: options.platformWrite, mergeApproval: true },
27
- now: options.now,
28
- async body(ctx) {
29
- const stage = (name) => ctx.stage(name, { done: 0, total: 1 });
30
- await stage('author');
31
- let outcome;
32
- try {
33
- outcome = await options.ports.round2(row, decision, ctx.signal, ctx.runId);
34
- }
35
- catch (error) {
36
- outcome = { kind: 'failed', failure: { stage: 'author', code: 'merge-failed', message: error instanceof Error ? error.message : String(error), retryable: true, retryFrom: 'agent2' } };
37
- }
38
- const base = { scope: { candidatePairs: 1, queueBefore: { verify: 0, pending: 0, fuse: 1, skipped: 0 } }, notes: [] };
39
- const record = await recordRound2Outcome(options.io, options.home, options.now, row, outcome);
40
- return {
41
- ...base,
42
- stopReason: record.merged ? 'completed' : 'failed',
43
- pairs: [record.pair],
44
- totals: record.merged
45
- ? { merged: 1, kept: 0, pending: 0, failed: 0 }
46
- : { merged: 0, kept: 0, pending: 0, failed: 1 },
47
- };
48
- },
17
+ if (before === undefined)
18
+ return failure('approve', 'row-not-found', '找不到待批准行 ' + options.rowId, '运行 /evolution merge pending 查看当前待批准行');
19
+ if (before.state !== 'pending')
20
+ return failure('approve', 'row-not-pending', '该行当前状态是 ' + before.state + ',不能批准', '运行 /evolution merge status ' + before.id + ' 查看行状态', before.runId);
21
+ if (options.agentless === true)
22
+ return failure('approve', 'e-305', errorText('e-305-this-invocation-carries-no', { a1: 'approve' }), '在 GUI 或 CLI 的会话里运行 /evolution merge approve ' + before.id);
23
+ const parentRunId = options.parentRunId ?? before.runId;
24
+ if (parentRunId === undefined)
25
+ return failure('approve', 'merge-parent-required', '缺 parent run id(E-305 提示:该行可能由无会话调用创建,或父 run 已丢失)', '在 agent 会话中执行 /evolution merge approve ' + before.id, before.runId);
26
+ const taken = await approvePending(options.io, options.home, options.now, options.rowId);
27
+ if (!taken.ok)
28
+ return failure('approve', 'approve-' + taken.reason, taken.message, '重试;若行状态已变化,运行 /evolution merge status ' + options.rowId, before.runId);
29
+ let row = taken.row;
30
+ const decision = row.decision;
31
+ if (decision === undefined)
32
+ return failure('approve', 'row-invalid', '该待批准行没有持久化决策', '重新 pair 该技能对后再次批准', row.runId);
33
+ if (options.ports.stage !== undefined) {
34
+ const staged = await options.ports.stage(row, decision);
35
+ if (!staged.ok)
36
+ return failure('approve', 'stage-failed', staged.message, '检查暂存目录后重试;不影响已记录的决策', row.runId);
37
+ const fused = await pairLedger(options.io, options.home).transact(tx => {
38
+ const current = tx.rows().find(item => item.id === row.id);
39
+ if (current === undefined)
40
+ return null;
41
+ const moved = tx.transition(row.id, { type: 'staged' }, current.version);
42
+ return moved.ok && moved.row !== null ? v4ToLegacy(moved.row) : null;
49
43
  });
44
+ if (fused === null)
45
+ return failure('approve', 'row-changed', '行在暂存完成前被并发修改:' + row.id, '运行 /evolution merge status ' + row.id + ' 后重试', row.runId);
46
+ await presentStage(options.io, options.home, staged.entry);
47
+ row = fused;
50
48
  }
51
- finally {
52
- await claim?.release();
53
- }
49
+ return await withMergeRun({
50
+ io: options.io,
51
+ home: options.home,
52
+ registry: options.registry,
53
+ command: 'approve',
54
+ approval: { platformWrite: options.platformWrite, mergeApproval: true },
55
+ pairKey: row.pairKey,
56
+ parentRunId,
57
+ now: options.now,
58
+ ...(options.detached === true ? { detached: true } : {}),
59
+ async body(ctx) {
60
+ const stage = (name) => ctx.stage(name, { done: 0, total: 1 });
61
+ await stage('author');
62
+ let outcome;
63
+ try {
64
+ outcome = await options.ports.round2(row, decision, ctx.signal, ctx.runId);
65
+ }
66
+ catch (error) {
67
+ outcome = { kind: 'failed', failure: { stage: 'author', code: 'merge-failed', message: error instanceof Error ? error.message : String(error), retryable: true, retryFrom: 'agent2' } };
68
+ }
69
+ const base = { scope: { candidatePairs: 1, queueBefore: { verify: 0, pending: 0, fuse: 1, skipped: 0 } }, notes: [] };
70
+ const record = await recordRound2Outcome(options.io, options.home, options.now, row, outcome, true);
71
+ return {
72
+ ...base,
73
+ stopReason: record.merged ? 'completed' : 'failed',
74
+ pairs: [record.pair],
75
+ totals: record.merged
76
+ ? { merged: 1, kept: 0, pending: 0, failed: 0 }
77
+ : { merged: 0, kept: 0, pending: 0, failed: 1 },
78
+ ...(record.finalize === undefined ? {} : { finalize: record.finalize }),
79
+ };
80
+ },
81
+ });
54
82
  }
55
83
  //# sourceMappingURL=approve-command.js.map
@@ -3,11 +3,19 @@
3
3
  * rule gate, then commit through the injected CommitPort. The channel adapter and the
4
4
  * author tool live outside; this is the seam they share.
5
5
  */
6
- import type { EvolutionIoLike } from '@lmzhen/dsh-evolution-core';
6
+ import type { EvolutionIoLike, UnitTransaction } from '@lmzhen/dsh-evolution-core';
7
7
  import { type RuleFinding } from './rules.ts';
8
8
  import { type CommitPort, type CommitStagedResult } from './commit-execute.ts';
9
9
  import type { StagedTree } from './rules.ts';
10
+ import type { RevisionRef } from './contracts.ts';
10
11
  import type { SkillCard } from './skill-card.ts';
12
+ import { type SkillLimits } from '@lmzhen/dsh-evolution-core';
13
+ /** Decision-time anchors threaded from round 1 into the commit CAS. */
14
+ export interface CommitRevisionRefs {
15
+ readonly targetRevision?: RevisionRef;
16
+ readonly sourceRevisions?: Readonly<Record<string, RevisionRef>>;
17
+ readonly supportDigest?: string;
18
+ }
11
19
  export interface RunAuthoringOptions {
12
20
  readonly io: EvolutionIoLike;
13
21
  readonly home: string;
@@ -24,17 +32,27 @@ export interface RunAuthoringOptions {
24
32
  readonly commitPort: CommitPort;
25
33
  readonly now: () => number;
26
34
  readonly summary: string;
35
+ readonly isBlocked?: (name: string) => boolean | Promise<boolean>;
36
+ readonly unit?: UnitTransaction | undefined;
37
+ readonly limits?: SkillLimits;
38
+ readonly promptVersion?: string;
39
+ readonly promptHash?: string;
40
+ /** Round-1 decision anchors; when absent the commit keeps the legacy body-only CAS. */
41
+ readonly revisions?: CommitRevisionRefs;
27
42
  }
28
43
  export type AuthoringResult = {
29
44
  readonly ok: true;
30
45
  readonly tree: StagedTree;
46
+ readonly validationPlan: import('./domain/validation-plan.ts').ValidationPlan;
31
47
  readonly commit: Extract<CommitStagedResult, {
32
48
  readonly ok: true;
33
49
  }>;
34
50
  } | {
35
51
  readonly ok: false;
52
+ readonly code?: string;
36
53
  readonly message: string;
37
54
  readonly findings?: readonly RuleFinding[];
55
+ readonly lengths?: Readonly<Record<string, number>>;
38
56
  };
39
57
  export declare function runAuthoring(options: RunAuthoringOptions): Promise<AuthoringResult>;
40
58
  //# sourceMappingURL=authoring-flow.d.ts.map
@@ -2,47 +2,81 @@ import { createStaging, setStagingTarget } from "./staging.js";
2
2
  import { readStagedTree } from "./staged-tree.js";
3
3
  import { validateStagedTree, hasBlockingFinding } from "./rules.js";
4
4
  import { commitStagedRun } from "./commit-execute.js";
5
+ import { DEFAULT_SKILL_LIMITS } from '@lmzhen/dsh-evolution-core';
6
+ import { limitsSnapshotOf } from "./domain/merge-limits.js";
7
+ import { buildValidationPlan } from "./domain/validation-plan.js";
8
+ import { manifestFilesOf, missingManifestFile, parseAuthoringManifest } from "./domain/authoring-manifest.js";
5
9
  export async function runAuthoring(options) {
10
+ if (options.target.includes('/') || options.target.includes(':'))
11
+ return { ok: false, code: 'target-invalid', message: 'target must be a bare skill name: ' + options.target };
6
12
  const targetCard = options.cards.find(card => card.name === options.target);
7
- if (targetCard?.protectionUnknown === true || targetCard?.protectedBy === 'pinned')
8
- return { ok: false, message: 'target is pinned: ' + options.target };
13
+ const mode = options.mode ?? (targetCard === undefined ? 'create-umbrella' : 'merge-into-existing');
14
+ const createMode = mode === 'create-umbrella';
15
+ if (!createMode && targetCard !== undefined) {
16
+ if (targetCard.managed !== true)
17
+ return { ok: false, code: 'target-not-managed', message: 'target is not family-managed: ' + options.target };
18
+ if (targetCard.protectionUnknown === true || targetCard.protectedBy != null)
19
+ return { ok: false, code: 'target-protected', message: 'target is protected (' + (targetCard.protectedBy ?? 'unknown') + '): ' + options.target };
20
+ }
21
+ if (!createMode && await options.isBlocked?.(options.target) === true)
22
+ return { ok: false, code: 'target-blocked', message: 'target is blocked by curator: ' + options.target };
9
23
  const stageKey = options.stageKey ?? options.runId;
10
24
  if (options.reuseStaging === true) {
11
- if (!await setStagingTarget(options.io, options.home, stageKey, options.target, options.mode))
12
- return { ok: false, message: 'staging missing or unreadable: ' + stageKey };
25
+ if (!await setStagingTarget(options.io, options.home, stageKey, options.target, mode))
26
+ return { ok: false, code: 'staging-missing', message: 'staging missing or unreadable: ' + stageKey };
13
27
  }
14
28
  else {
15
29
  const created = await createStaging(options.io, options.home, {
16
30
  runId: stageKey,
17
31
  target: options.target,
18
- ...(options.mode === undefined ? {} : { mode: options.mode }),
32
+ mode,
19
33
  sources: options.cards.map(card => ({ name: card.name, body: card.body, files: card.files ?? [] })),
20
34
  promptVersion: 'merge-b/v1',
21
35
  ttlDays: 7,
22
36
  now: options.now,
23
37
  });
24
38
  if (!created.ok)
25
- return { ok: false, message: created.message };
39
+ return { ok: false, code: 'staging-create', message: created.message };
26
40
  }
41
+ let authored;
27
42
  try {
28
- await options.author({ home: options.home, runId: stageKey });
43
+ authored = await options.author({ home: options.home, runId: stageKey });
29
44
  }
30
45
  catch (error) {
31
- return { ok: false, message: 'author failed: ' + (error instanceof Error ? error.message : String(error)) };
46
+ return { ok: false, code: 'author-failed', message: 'author failed: ' + (error instanceof Error ? error.message : String(error)) };
32
47
  }
33
48
  const tree = await readStagedTree(options.io, options.home, stageKey);
34
49
  if (tree.kind !== 'present')
35
- return { ok: false, message: 'staged tree is not readable: ' + (tree.kind === 'unknown' ? tree.reason : 'absent') };
36
- const findings = validateStagedTree(tree.value);
50
+ return { ok: false, code: 'staging-missing', message: 'staged tree is not readable: ' + (tree.kind === 'unknown' ? tree.reason : 'absent') };
51
+ // FR-VAL-03: the same-child path parses the child's files[] declaration too, so a
52
+ // manifest that promises a file the tree does not contain fails here — not only on the
53
+ // new-child approve path.
54
+ const manifest = parseAuthoringManifest(authored);
55
+ const stagedPaths = tree.value.files.map(file => file.path);
56
+ const manifestFiles = manifestFilesOf(manifest, stagedPaths);
57
+ const missing = missingManifestFile(manifestFiles, stagedPaths);
58
+ if (missing !== null)
59
+ return { ok: false, code: 'merge-manifest-missing', message: 'manifest file missing from staged tree: ' + missing };
60
+ const limitsSnapshot = limitsSnapshotOf(options.limits ?? DEFAULT_SKILL_LIMITS);
61
+ const supportSources = options.cards.flatMap(card => (card.files ?? []).map(file => ({ path: file.path, content: file.content, source: card.name })));
62
+ 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) });
63
+ const findings = [...validateStagedTree(tree.value, limitsSnapshot.skillLimits), ...validationPlan.findings];
37
64
  if (hasBlockingFinding(findings))
38
- return { ok: false, message: 'staged tree failed the rule gate', findings };
65
+ return { ok: false, code: findings.some(finding => finding.rule === 'limit') ? 'merge-over-limit' : 'merge-validation', message: 'staged tree failed the rule gate', findings, 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) } };
39
66
  const commit = await commitStagedRun(options.io, options.home, stageKey, options.commitPort, {
40
- origin: 'subagent',
67
+ origin: 'foreground',
41
68
  summary: options.summary,
42
69
  now: options.now,
70
+ ...(options.isBlocked === undefined ? {} : { isBlocked: options.isBlocked }),
71
+ ...(options.unit === undefined ? {} : { unit: options.unit }),
72
+ ...(options.limits === undefined ? {} : { limits: options.limits }),
73
+ validationPlan: { planVersion: validationPlan.planVersion, stageDigest: validationPlan.stageDigest, revision: validationPlan.revision },
74
+ ...(options.revisions?.targetRevision === undefined ? {} : { targetRevision: options.revisions.targetRevision }),
75
+ ...(options.revisions?.sourceRevisions === undefined ? {} : { sourceRevisions: options.revisions.sourceRevisions }),
76
+ ...(options.revisions?.supportDigest === undefined ? {} : { supportDigest: options.revisions.supportDigest }),
43
77
  });
44
- if (!commit.ok)
45
- return { ok: false, message: commit.message };
46
- return { ok: true, tree: tree.value, commit };
78
+ if (commit.ok)
79
+ return { ok: true, tree: tree.value, validationPlan, commit };
80
+ return { ok: false, code: commit.code ?? 'commit-failed', message: commit.message };
47
81
  }
48
82
  //# sourceMappingURL=authoring-flow.js.map
@@ -3,13 +3,14 @@
3
3
  * an author callback for round 2 when the same child continues. Commit is deliberately
4
4
  * outside this adapter: the caller owns staging -> commitStagedRun.
5
5
  */
6
- import type { MergeDecision, MergeRow } from './contracts.ts';
6
+ import type { MergeDecision, MergeRow, MergeUsage } from './contracts.ts';
7
7
  import type { SkillCard } from './skill-card.ts';
8
8
  import type { ScanRound1Outcome } from './scan-run.ts';
9
9
  export interface ChannelTurnLike {
10
10
  readonly text: string;
11
11
  readonly stopReason: string;
12
12
  readonly cacheRead?: number;
13
+ readonly usage?: MergeUsage;
13
14
  }
14
15
  export type ChannelTurnOutcome = {
15
16
  readonly kind: 'ok';
@@ -51,6 +52,7 @@ export interface ChannelRound1Options {
51
52
  export interface ChannelRound1Result {
52
53
  readonly outcome: ScanRound1Outcome;
53
54
  readonly childId?: string;
55
+ readonly usage?: () => MergeUsage | undefined;
54
56
  /** Same-child round 2; absent for keep/fail/unavailable. */
55
57
  readonly author?: (row: MergeRow, decision: MergeDecision, signal: AbortSignal) => Promise<string>;
56
58
  readonly dispose: () => Promise<void>;
@@ -1,7 +1,8 @@
1
1
  import { parseDecision } from "./decision.js";
2
- import { buildRound1Prompt, buildRound2Prompt, STANDBY_PROMPT } from "./prompts.js";
2
+ import { buildRound1Prompt, buildRound2Prompt, MERGE_PROMPT_VERSION, promptHashOf, STANDBY_PROMPT } from "./prompts.js";
3
3
  export async function runRound1OverChannel(options, candidate, signal) {
4
4
  let childId;
5
+ let lastUsage;
5
6
  const dispose = async () => {
6
7
  if (childId !== undefined) {
7
8
  const id = childId;
@@ -38,7 +39,8 @@ export async function runRound1OverChannel(options, candidate, signal) {
38
39
  await dispose();
39
40
  return { outcome: { kind: 'failed', failure: { stage: 'decide', code: 'child-bind', message: String(error), retryable: true, retryFrom: 'agent1' } }, dispose };
40
41
  }
41
- const round1 = await options.channel.continueTurn(opened.childId, buildRound1Prompt(candidate, options.cards), { timeoutMs: options.timeoutMs, signal });
42
+ const prompt = buildRound1Prompt(candidate, options.cards);
43
+ const round1 = await options.channel.continueTurn(opened.childId, prompt, { timeoutMs: options.timeoutMs, signal });
42
44
  if (round1.kind !== 'ok') {
43
45
  await dispose();
44
46
  return { outcome: { kind: 'failed', failure: { stage: 'decide', code: round1.kind, message: round1.kind === 'unavailable' ? round1.reason : 'round1 aborted', retryable: round1.kind !== 'aborted', retryFrom: round1.kind === 'aborted' ? 'none' : 'agent1' } }, dispose };
@@ -52,7 +54,8 @@ export async function runRound1OverChannel(options, candidate, signal) {
52
54
  await dispose();
53
55
  return { outcome: { kind: 'failed', failure: { stage: 'decide', code: parsed.code, message: parsed.message, retryable: true, retryFrom: 'agent1' } }, dispose };
54
56
  }
55
- const decision = round1.turn.cacheRead === undefined ? parsed.decision : { ...parsed.decision, cacheRead: round1.turn.cacheRead };
57
+ lastUsage = round1.turn.usage;
58
+ const decision = { ...parsed.decision, promptVersion: MERGE_PROMPT_VERSION, promptHash: promptHashOf(prompt), ...(round1.turn.cacheRead === undefined ? {} : { cacheRead: round1.turn.cacheRead }) };
56
59
  if (!decision.shouldMerge || decision.mode === 'keep') {
57
60
  await dispose();
58
61
  return { outcome: { kind: 'keep', decision }, dispose };
@@ -62,12 +65,14 @@ export async function runRound1OverChannel(options, candidate, signal) {
62
65
  outcome: { kind: 'merge', decision },
63
66
  childId: child,
64
67
  dispose,
68
+ usage: () => lastUsage,
65
69
  async author(row, decision, turnSignal) {
66
70
  const turn = await options.channel.continueTurn(child, buildRound2Prompt(row, decision), { timeoutMs: options.timeoutMs, signal: turnSignal });
67
71
  if (turn.kind !== 'ok')
68
72
  throw new Error(turn.kind === 'unavailable' ? turn.reason : 'round2 aborted');
69
73
  if (turn.turn.stopReason !== 'completed')
70
74
  throw new Error('round2 did not complete: ' + turn.turn.stopReason);
75
+ lastUsage = turn.turn.usage;
71
76
  return turn.turn.text;
72
77
  },
73
78
  };
@@ -0,0 +1,5 @@
1
+ import type { CommandDef } from '@lmzhen/dsh-evolution-commands';
2
+ export declare const MERGE_PACKAGE_VERSION: string;
3
+ export declare const MERGE_ALIAS_DEF: CommandDef;
4
+ export declare const MERGE_COMMAND_DEFS: readonly CommandDef[];
5
+ //# sourceMappingURL=command-defs.d.ts.map