@lmzhen/dsh-evolution-merge 0.32.6 → 0.34.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/lib/index.js +128 -61
- package/lib/types/index.js +31 -11
- package/lib/types/merge-routes.d.ts +13 -0
- package/lib/types/merge-routes.js +37 -7
- package/lib/types/runtime.d.ts +7 -2
- package/lib/types/runtime.js +43 -34
- package/lib/types/surface/merge-view.js +9 -1
- package/package.json +4 -4
package/lib/index.js
CHANGED
|
@@ -3700,6 +3700,12 @@ function runView(run, readAt, includeResultRef = false) {
|
|
|
3700
3700
|
const record = run.record;
|
|
3701
3701
|
const state = run.meta?.phase === "undone" ? "undone" : run.meta?.stopReason === "interrupted" ? "interrupted" : run.meta?.approval === "pending" ? "awaiting-approval" : record.state;
|
|
3702
3702
|
const stopReason = run.meta?.stopReason ?? (record.state === "cancelled" ? "cancelled" : record.state === "failed" ? "failed" : record.state === "succeeded" ? "completed" : void 0);
|
|
3703
|
+
const reportPairs = run.report?.processedPairs?.length ?? run.report?.pairs.length;
|
|
3704
|
+
const terminal = state === "succeeded" || state === "undone";
|
|
3705
|
+
const progress = record.progress === void 0 && reportPairs === void 0 ? null : {
|
|
3706
|
+
done: terminal ? Math.max(record.progress?.done ?? 0, reportPairs ?? 0, run.pairs.length) : record.progress?.done ?? reportPairs ?? 0,
|
|
3707
|
+
total: record.progress?.total ?? reportPairs ?? 0
|
|
3708
|
+
};
|
|
3703
3709
|
return {
|
|
3704
3710
|
wireVersion: 1,
|
|
3705
3711
|
readAt,
|
|
@@ -3707,10 +3713,7 @@ function runView(run, readAt, includeResultRef = false) {
|
|
|
3707
3713
|
state,
|
|
3708
3714
|
command: record.command ?? "scan",
|
|
3709
3715
|
...record.stage === void 0 ? {} : { stage: record.stage },
|
|
3710
|
-
progress
|
|
3711
|
-
done: record.progress.done,
|
|
3712
|
-
total: record.progress.total
|
|
3713
|
-
},
|
|
3716
|
+
progress,
|
|
3714
3717
|
startedAt: record.startedAt,
|
|
3715
3718
|
...record.endedAt === void 0 ? {} : { endedAt: record.endedAt },
|
|
3716
3719
|
...stopReason === void 0 ? {} : { stopReason },
|
|
@@ -5056,7 +5059,8 @@ const MERGE_ROUTES = {
|
|
|
5056
5059
|
view: MERGE_PATHS.view,
|
|
5057
5060
|
rows: MERGE_PATHS.rows,
|
|
5058
5061
|
body: MERGE_PATHS.body ?? MERGE_BASE + "body",
|
|
5059
|
-
history: MERGE_PATHS.history ?? MERGE_BASE + "history"
|
|
5062
|
+
history: MERGE_PATHS.history ?? MERGE_BASE + "history",
|
|
5063
|
+
decision: MERGE_BASE + "decision"
|
|
5060
5064
|
};
|
|
5061
5065
|
function makeMergeRoutes(deps, _audit) {
|
|
5062
5066
|
const absent = () => json(404, {
|
|
@@ -5171,7 +5175,6 @@ function makeMergeRoutes(deps, _audit) {
|
|
|
5171
5175
|
const id = query.get("id");
|
|
5172
5176
|
const name = query.get("name");
|
|
5173
5177
|
const side = query.get("side");
|
|
5174
|
-
if (id !== null && name !== null) return bad("id and name are mutually exclusive");
|
|
5175
5178
|
if ((id === null || id === "") && (name === null || name === "")) return json(404, {
|
|
5176
5179
|
wireVersion: 1,
|
|
5177
5180
|
code: "merge-body-not-found",
|
|
@@ -5184,21 +5187,21 @@ function makeMergeRoutes(deps, _audit) {
|
|
|
5184
5187
|
code: "merge-state-blocked",
|
|
5185
5188
|
readiness: snapshot.blocked.readiness
|
|
5186
5189
|
});
|
|
5187
|
-
const
|
|
5190
|
+
const sideFilter = side === "a" ? "live" : side === "b" ? "archive" : side === "live" || side === "archive" ? side : void 0;
|
|
5191
|
+
const refs = snapshot.bodyRefs.filter((ref) => (id === null || id === "" || ref.rowId === id) && (name === null || name === "" || ref.name === name) && (sideFilter === void 0 || ref.side === sideFilter));
|
|
5188
5192
|
if (refs.length === 0) return json(404, {
|
|
5189
5193
|
wireVersion: 1,
|
|
5190
5194
|
code: "merge-body-not-found",
|
|
5191
5195
|
message: "merge body not found"
|
|
5192
5196
|
});
|
|
5193
|
-
if (
|
|
5197
|
+
if (refs.length > 1) return bad("merge body is ambiguous; pass id, name and side");
|
|
5194
5198
|
const row = snapshot.rows.find((item) => item.row.id === refs[0].rowId);
|
|
5195
5199
|
if (row?.stage?.state === "lost" || row?.stage?.state === "expired") return json(404, {
|
|
5196
5200
|
wireVersion: 1,
|
|
5197
5201
|
code: "merge-body-not-found",
|
|
5198
5202
|
message: "merge stage is lost"
|
|
5199
5203
|
});
|
|
5200
|
-
const
|
|
5201
|
-
const ref = (sideFilter === void 0 ? refs[0] : refs.find((item) => item.side === sideFilter)) ?? refs[0];
|
|
5204
|
+
const ref = refs[0];
|
|
5202
5205
|
return json(200, buildBodyView(snapshot, ref, (source.bodyRef === void 0 ? null : await source.bodyRef(ref)) ?? ""));
|
|
5203
5206
|
}
|
|
5204
5207
|
const readiness = await notReady(source);
|
|
@@ -5234,6 +5237,35 @@ function makeMergeRoutes(deps, _audit) {
|
|
|
5234
5237
|
if (readiness !== null) return readiness;
|
|
5235
5238
|
return json(200, toHistoryView(await source.history?.() ?? [], Date.now()));
|
|
5236
5239
|
}
|
|
5240
|
+
},
|
|
5241
|
+
{
|
|
5242
|
+
path: MERGE_ROUTES.decision,
|
|
5243
|
+
methods: ["POST"],
|
|
5244
|
+
requestBody: "buffered",
|
|
5245
|
+
fetch: async (request) => {
|
|
5246
|
+
if (request.method !== "POST") return methodNotAllowed(["POST"]);
|
|
5247
|
+
if (deps.decide === void 0) return absent();
|
|
5248
|
+
let body;
|
|
5249
|
+
try {
|
|
5250
|
+
body = await request.json();
|
|
5251
|
+
} catch {
|
|
5252
|
+
return bad("decision body must be JSON");
|
|
5253
|
+
}
|
|
5254
|
+
if (typeof body !== "object" || body === null) return bad("decision body must be an object");
|
|
5255
|
+
const input = body;
|
|
5256
|
+
if (typeof input.rowId !== "string" || input.rowId === "") return bad("rowId is required");
|
|
5257
|
+
if (input.verb !== "approve" && input.verb !== "reject") return bad("verb must be approve or reject");
|
|
5258
|
+
if (typeof input.sessionId !== "string" || input.sessionId === "") return bad("sessionId is required");
|
|
5259
|
+
const result = await deps.decide({
|
|
5260
|
+
rowId: input.rowId,
|
|
5261
|
+
verb: input.verb,
|
|
5262
|
+
sessionId: input.sessionId
|
|
5263
|
+
}, _audit);
|
|
5264
|
+
return json(result.ok ? 200 : result.code === "E-305" ? 409 : 400, {
|
|
5265
|
+
wireVersion: 1,
|
|
5266
|
+
...result
|
|
5267
|
+
});
|
|
5268
|
+
}
|
|
5237
5269
|
}
|
|
5238
5270
|
];
|
|
5239
5271
|
}
|
|
@@ -9261,16 +9293,22 @@ function unitFor(deps) {
|
|
|
9261
9293
|
function createMergeRuntime(deps) {
|
|
9262
9294
|
const readOnly = createReadOnlyMergeService(deps.io, deps.home);
|
|
9263
9295
|
const timeout = deps.timeoutMinutes > 0 ? deps.timeoutMinutes : void 0;
|
|
9264
|
-
const
|
|
9296
|
+
const paramsOf = () => {
|
|
9297
|
+
if (deps.getParams !== void 0) return deps.getParams();
|
|
9298
|
+
if (deps.params !== void 0) return deps.params;
|
|
9299
|
+
throw new Error("merge runtime requires getParams or params");
|
|
9300
|
+
};
|
|
9301
|
+
const platformWriteOf = () => deps.getPlatformWrite?.() ?? deps.platformWrite ?? false;
|
|
9302
|
+
const common = () => ({
|
|
9265
9303
|
io: deps.io,
|
|
9266
9304
|
home: deps.home,
|
|
9267
9305
|
registry: deps.registry,
|
|
9268
|
-
platformWrite:
|
|
9306
|
+
platformWrite: platformWriteOf(),
|
|
9269
9307
|
now: deps.now,
|
|
9270
9308
|
...deps.model === void 0 ? {} : { model: deps.model },
|
|
9271
9309
|
...deps.isOwnerActive === void 0 ? {} : { isOwnerActive: deps.isOwnerActive },
|
|
9272
9310
|
...timeout === void 0 ? {} : { timeoutMinutes: timeout }
|
|
9273
|
-
};
|
|
9311
|
+
});
|
|
9274
9312
|
const stage = deps.stage;
|
|
9275
9313
|
const stageHooks = stage === void 0 ? {} : {
|
|
9276
9314
|
onChild: (context) => {
|
|
@@ -9284,6 +9322,7 @@ function createMergeRuntime(deps) {
|
|
|
9284
9322
|
const parent = context?.parent ?? deps.parent;
|
|
9285
9323
|
const agentless = context?.agentless === true;
|
|
9286
9324
|
const detached = deps.detached !== false;
|
|
9325
|
+
const params = paramsOf();
|
|
9287
9326
|
if (command.verb === "pair") {
|
|
9288
9327
|
const signal = new AbortController().signal;
|
|
9289
9328
|
if (command.a === command.b) return fail("pair", "pair-same-source", "merge pair 需要两个不同技能源:" + command.a, "重新指定两个不同的 family-managed 技能");
|
|
@@ -9323,9 +9362,9 @@ function createMergeRuntime(deps) {
|
|
|
9323
9362
|
if (existing?.state === "pending") return fail("pair", "pair-pending", "该 pair 已在待批准队列:" + existing.id, "先运行 /evolution merge approve/reject/discard " + existing.id, existing.runId);
|
|
9324
9363
|
if (!cleared && (existing?.state === "approved" || existing?.state === "fuse")) return fail("pair", "pair-fuse", "该 pair 已在待合并队列,可复用决策重跑", "运行 /evolution merge resume --retry " + (existing.runId ?? existing.id), existing.runId);
|
|
9325
9364
|
const forced = command.force === true ? forcedDecisionFor(command.a, command.b) : void 0;
|
|
9326
|
-
const approval =
|
|
9365
|
+
const approval = params.mergeApproval;
|
|
9327
9366
|
const result = await runScanCommand({
|
|
9328
|
-
...common,
|
|
9367
|
+
...common(),
|
|
9329
9368
|
approval,
|
|
9330
9369
|
cards,
|
|
9331
9370
|
...detached ? { detached: true } : {},
|
|
@@ -9340,12 +9379,12 @@ function createMergeRuntime(deps) {
|
|
|
9340
9379
|
parent,
|
|
9341
9380
|
allow: deps.allow,
|
|
9342
9381
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9343
|
-
timeoutMs:
|
|
9382
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9344
9383
|
approval,
|
|
9345
9384
|
commitPort: skillLibraryPort(deps.skills),
|
|
9346
9385
|
now: deps.now,
|
|
9347
9386
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9348
|
-
...
|
|
9387
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9349
9388
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9350
9389
|
...stageHooks
|
|
9351
9390
|
});
|
|
@@ -9379,12 +9418,12 @@ function createMergeRuntime(deps) {
|
|
|
9379
9418
|
for (const card of cards) if (await deps.isBlocked?.(card.name) === true) blocked.add(card.name);
|
|
9380
9419
|
const managed = new Set(cards.filter((card) => card.managed === true && card.protectedBy == null && !blocked.has(card.name)).map((card) => card.name));
|
|
9381
9420
|
const loadedState = await loadMergeState(deps.io, deps.home);
|
|
9382
|
-
const skipped = skippedPairKeys(loadedState.state.rows,
|
|
9421
|
+
const skipped = skippedPairKeys(loadedState.state.rows, params.mergeSkippedTtlDays, deps.now());
|
|
9383
9422
|
const held = new Set(loadedState.state.rows.filter((row) => row.queue === "pending" || row.queue === "fuse" || row.queue === "verify" && (row.runId !== void 0 && deps.now() - Date.parse(row.updatedAt) < 10 * 6e4 || !canRetry(row))).map((row) => row.pairKey));
|
|
9384
9423
|
const notes = loadedState.state.rows.filter((row) => row.state === "pending" || row.state === "approved" || row.state === "fuse" || row.state === "skipped").map((row) => "conflict:" + row.state + ":" + row.pairKey + ":" + row.id);
|
|
9385
9424
|
const result = await runScanCommand({
|
|
9386
|
-
...common,
|
|
9387
|
-
approval:
|
|
9425
|
+
...common(),
|
|
9426
|
+
approval: params.mergeApproval,
|
|
9388
9427
|
notes,
|
|
9389
9428
|
cards,
|
|
9390
9429
|
...detached ? { detached: true } : {},
|
|
@@ -9416,12 +9455,12 @@ function createMergeRuntime(deps) {
|
|
|
9416
9455
|
parent,
|
|
9417
9456
|
allow: deps.allow,
|
|
9418
9457
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9419
|
-
timeoutMs:
|
|
9420
|
-
approval:
|
|
9458
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9459
|
+
approval: params.mergeApproval,
|
|
9421
9460
|
commitPort: skillLibraryPort(deps.skills),
|
|
9422
9461
|
now: deps.now,
|
|
9423
9462
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9424
|
-
...
|
|
9463
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9425
9464
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9426
9465
|
...stageHooks
|
|
9427
9466
|
})
|
|
@@ -9449,8 +9488,8 @@ function createMergeRuntime(deps) {
|
|
|
9449
9488
|
b: row.b
|
|
9450
9489
|
};
|
|
9451
9490
|
const result = await runScanCommand({
|
|
9452
|
-
...common,
|
|
9453
|
-
approval:
|
|
9491
|
+
...common(),
|
|
9492
|
+
approval: params.mergeApproval,
|
|
9454
9493
|
notes: ["candidates --retry " + row.id],
|
|
9455
9494
|
cards,
|
|
9456
9495
|
makePorts: (runId) => createChannelScanPorts({
|
|
@@ -9467,12 +9506,12 @@ function createMergeRuntime(deps) {
|
|
|
9467
9506
|
parent,
|
|
9468
9507
|
allow: deps.allow,
|
|
9469
9508
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9470
|
-
timeoutMs:
|
|
9471
|
-
approval:
|
|
9509
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9510
|
+
approval: params.mergeApproval,
|
|
9472
9511
|
commitPort: skillLibraryPort(deps.skills),
|
|
9473
9512
|
now: deps.now,
|
|
9474
9513
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9475
|
-
...
|
|
9514
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9476
9515
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9477
9516
|
...stageHooks
|
|
9478
9517
|
})
|
|
@@ -9514,7 +9553,7 @@ function createMergeRuntime(deps) {
|
|
|
9514
9553
|
io: deps.io,
|
|
9515
9554
|
home: deps.home,
|
|
9516
9555
|
now: deps.now,
|
|
9517
|
-
ttlDays:
|
|
9556
|
+
ttlDays: params.mergeSkippedTtlDays,
|
|
9518
9557
|
...managed === void 0 ? {} : { managed },
|
|
9519
9558
|
...live === void 0 ? {} : { live }
|
|
9520
9559
|
});
|
|
@@ -9529,7 +9568,7 @@ function createMergeRuntime(deps) {
|
|
|
9529
9568
|
}
|
|
9530
9569
|
if (command.verb === "doctor") return {
|
|
9531
9570
|
ok: true,
|
|
9532
|
-
text: (await mergeDoctor(deps.io, deps.home,
|
|
9571
|
+
text: (await mergeDoctor(deps.io, deps.home, params.mergeModelsRoot)).map((row) => row.key + " [" + row.status + "] " + row.label + ":" + row.detail).join("\n")
|
|
9533
9572
|
};
|
|
9534
9573
|
if (command.verb === "report" || command.verb === "runs" || command.verb === "cancel") {
|
|
9535
9574
|
const registry = deps.registry;
|
|
@@ -9541,7 +9580,7 @@ function createMergeRuntime(deps) {
|
|
|
9541
9580
|
io: deps.io,
|
|
9542
9581
|
home: deps.home,
|
|
9543
9582
|
runs: mergeRuns,
|
|
9544
|
-
approval:
|
|
9583
|
+
approval: params.mergeApproval,
|
|
9545
9584
|
now: deps.now
|
|
9546
9585
|
})))
|
|
9547
9586
|
};
|
|
@@ -9657,13 +9696,13 @@ function createMergeRuntime(deps) {
|
|
|
9657
9696
|
round2: () => failed("author", "resume-needs-child", "fuse resume needs a new child")
|
|
9658
9697
|
};
|
|
9659
9698
|
const result = await runResumeCommand({
|
|
9660
|
-
...common,
|
|
9699
|
+
...common(),
|
|
9661
9700
|
rowId: row.id,
|
|
9662
9701
|
ports: basePorts,
|
|
9663
|
-
approval:
|
|
9702
|
+
approval: params.mergeApproval,
|
|
9664
9703
|
...agentless ? { agentless: true } : {},
|
|
9665
9704
|
...detached ? { detached: true } : {},
|
|
9666
|
-
pairTimeoutMinutes:
|
|
9705
|
+
pairTimeoutMinutes: params.mergePairTimeoutMinutes,
|
|
9667
9706
|
...deps.isOwnerActive === void 0 ? {} : { isOwnerActive: deps.isOwnerActive },
|
|
9668
9707
|
makePorts: (runId) => createChannelScanPorts({
|
|
9669
9708
|
io: deps.io,
|
|
@@ -9675,12 +9714,12 @@ function createMergeRuntime(deps) {
|
|
|
9675
9714
|
parent,
|
|
9676
9715
|
allow: deps.allow,
|
|
9677
9716
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9678
|
-
timeoutMs:
|
|
9679
|
-
approval:
|
|
9717
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9718
|
+
approval: params.mergeApproval,
|
|
9680
9719
|
commitPort: skillLibraryPort(deps.skills),
|
|
9681
9720
|
now: deps.now,
|
|
9682
9721
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9683
|
-
...
|
|
9722
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9684
9723
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked }
|
|
9685
9724
|
}),
|
|
9686
9725
|
round2WithRun: createNewChildRound2({
|
|
@@ -9692,11 +9731,11 @@ function createMergeRuntime(deps) {
|
|
|
9692
9731
|
parentRunId: (runId) => runId,
|
|
9693
9732
|
allow: deps.allow,
|
|
9694
9733
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9695
|
-
timeoutMs:
|
|
9734
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9696
9735
|
commitPort: skillLibraryPort(deps.skills),
|
|
9697
9736
|
now: deps.now,
|
|
9698
9737
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9699
|
-
...
|
|
9738
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9700
9739
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9701
9740
|
...stageHooks
|
|
9702
9741
|
})
|
|
@@ -9811,11 +9850,11 @@ function createMergeRuntime(deps) {
|
|
|
9811
9850
|
}
|
|
9812
9851
|
}
|
|
9813
9852
|
const result = await runApproveCommand({
|
|
9814
|
-
...common,
|
|
9853
|
+
...common(),
|
|
9815
9854
|
rowId: command.id,
|
|
9816
9855
|
...agentless ? { agentless: true } : {},
|
|
9817
9856
|
...detached ? { detached: true } : {},
|
|
9818
|
-
pairTimeoutMinutes:
|
|
9857
|
+
pairTimeoutMinutes: params.mergePairTimeoutMinutes,
|
|
9819
9858
|
ports: {
|
|
9820
9859
|
stage: createStageForCards({
|
|
9821
9860
|
io: deps.io,
|
|
@@ -9833,11 +9872,11 @@ function createMergeRuntime(deps) {
|
|
|
9833
9872
|
parentRunId: (runId) => runId,
|
|
9834
9873
|
allow: deps.allow,
|
|
9835
9874
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9836
|
-
timeoutMs:
|
|
9875
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9837
9876
|
commitPort: skillLibraryPort(deps.skills),
|
|
9838
9877
|
now: deps.now,
|
|
9839
9878
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9840
|
-
...
|
|
9879
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9841
9880
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9842
9881
|
...stageHooks
|
|
9843
9882
|
})
|
|
@@ -9864,7 +9903,7 @@ function createMergeRuntime(deps) {
|
|
|
9864
9903
|
io: deps.io,
|
|
9865
9904
|
home: deps.home,
|
|
9866
9905
|
runs: runs !== void 0 && runs.some((item) => item.id === found.id) ? runs : [...runs ?? [], found],
|
|
9867
|
-
approval:
|
|
9906
|
+
approval: params.mergeApproval,
|
|
9868
9907
|
now: deps.now
|
|
9869
9908
|
});
|
|
9870
9909
|
if (snapshot.blocked !== void 0) return fail("status", snapshot.blocked.code, snapshot.blocked.message, "先修复合并状态(/evolution merge doctor),再查看运行");
|
|
@@ -9881,7 +9920,7 @@ function createMergeRuntime(deps) {
|
|
|
9881
9920
|
const list = cards ?? await deps.cards(new AbortController().signal).catch(() => []);
|
|
9882
9921
|
return list.length === 0 ? void 0 : liveRowFactsOf(list);
|
|
9883
9922
|
};
|
|
9884
|
-
const text = await handleReadOnlyMergeCommand(command, deps.io, deps.home, cards, runs,
|
|
9923
|
+
const text = await handleReadOnlyMergeCommand(command, deps.io, deps.home, cards, runs, params.mergeApproval, liveForRead);
|
|
9885
9924
|
if (text !== null) return {
|
|
9886
9925
|
ok: true,
|
|
9887
9926
|
text
|
|
@@ -10545,8 +10584,9 @@ const inject = [];
|
|
|
10545
10584
|
function apply(ctx, rawConfig = {}) {
|
|
10546
10585
|
registerOwnerSettings(ctx, "evolution-merge", Config);
|
|
10547
10586
|
configureMergePresentation(ctx);
|
|
10548
|
-
|
|
10587
|
+
const startupParams = resolveMergeParams(rawConfig);
|
|
10549
10588
|
let mergeRun;
|
|
10589
|
+
let decide;
|
|
10550
10590
|
let source;
|
|
10551
10591
|
let doctorFact;
|
|
10552
10592
|
const mergeService = {
|
|
@@ -10568,7 +10608,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10568
10608
|
};
|
|
10569
10609
|
const mergeContext = ctx;
|
|
10570
10610
|
if (typeof mergeContext.provide === "function") mergeContext.provide("evolutionMerge", mergeService);
|
|
10571
|
-
registerHostRoutes(ctx, (audit) => makeMergeRoutes({
|
|
10611
|
+
registerHostRoutes(ctx, (audit) => makeMergeRoutes({
|
|
10612
|
+
getSource: () => source,
|
|
10613
|
+
decide: async (request, _audit) => {
|
|
10614
|
+
if (decide === void 0) return {
|
|
10615
|
+
ok: false,
|
|
10616
|
+
code: "E-305",
|
|
10617
|
+
message: "合并服务尚未完成挂载"
|
|
10618
|
+
};
|
|
10619
|
+
return await decide(request);
|
|
10620
|
+
}
|
|
10621
|
+
}, audit));
|
|
10572
10622
|
ctx.inject(["commands", "evolutionIo"], (scope) => {
|
|
10573
10623
|
const registry = scope.get("evolutionIo");
|
|
10574
10624
|
if (registry === void 0) return;
|
|
@@ -10619,13 +10669,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10619
10669
|
});
|
|
10620
10670
|
}, "evolution-merge: restore supersede");
|
|
10621
10671
|
const limitsSnapshot = resolveLimitsSnapshot(ctx.get("evolutionPolicy")?.get?.());
|
|
10622
|
-
const
|
|
10623
|
-
|
|
10624
|
-
|
|
10625
|
-
...params,
|
|
10626
|
-
limitsSnapshot,
|
|
10627
|
-
platformWrite
|
|
10672
|
+
const getPlatformWrite = () => {
|
|
10673
|
+
const hostApproval = ctx.get("evolutionApproval");
|
|
10674
|
+
return typeof hostApproval?.platformWrite === "boolean" ? hostApproval.platformWrite : resolveMergeParams(rawConfig).platformWrite ?? false;
|
|
10628
10675
|
};
|
|
10676
|
+
const getParams = () => ({
|
|
10677
|
+
...resolveMergeParams(rawConfig),
|
|
10678
|
+
limitsSnapshot,
|
|
10679
|
+
platformWrite: getPlatformWrite()
|
|
10680
|
+
});
|
|
10629
10681
|
const skills = newSkillLibrary({
|
|
10630
10682
|
io,
|
|
10631
10683
|
ctx,
|
|
@@ -10668,7 +10720,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10668
10720
|
io,
|
|
10669
10721
|
home,
|
|
10670
10722
|
now: () => Date.now(),
|
|
10671
|
-
ttlDays:
|
|
10723
|
+
ttlDays: getParams().mergeSkippedTtlDays,
|
|
10672
10724
|
...live === void 0 ? {} : { live }
|
|
10673
10725
|
});
|
|
10674
10726
|
if (reconciled.actions.length > 0) ctx.logger?.info?.("merge reconcile: " + String(reconciled.actions.length) + " action(s)");
|
|
@@ -10705,7 +10757,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10705
10757
|
} : void 0;
|
|
10706
10758
|
});
|
|
10707
10759
|
const stage = new StageToolRegistry();
|
|
10708
|
-
const engineInput = resolveEngineInput({ modelsRoot:
|
|
10760
|
+
const engineInput = resolveEngineInput({ modelsRoot: startupParams.mergeModelsRoot });
|
|
10709
10761
|
const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
|
|
10710
10762
|
const llm = ctx.get("llm");
|
|
10711
10763
|
const defaultModel = ctx.get("agentDefaultModel");
|
|
@@ -10761,12 +10813,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10761
10813
|
});
|
|
10762
10814
|
source = {
|
|
10763
10815
|
async snapshot() {
|
|
10816
|
+
const current = getParams();
|
|
10764
10817
|
return await readMergeSnapshot({
|
|
10765
10818
|
io,
|
|
10766
10819
|
home,
|
|
10767
10820
|
runs: runs.runs("merge"),
|
|
10768
|
-
approval:
|
|
10769
|
-
platformWrite,
|
|
10821
|
+
approval: current.mergeApproval,
|
|
10822
|
+
platformWrite: getPlatformWrite(),
|
|
10770
10823
|
hostOnly: hostCapabilities.hostOnly,
|
|
10771
10824
|
live: async () => liveRowFactsOf(await readSkillCards(skills)),
|
|
10772
10825
|
history: async () => {
|
|
@@ -10818,7 +10871,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10818
10871
|
return await resolveBody(io, home, ref, async () => ref.side === "archive" ? await skills.readArchived(ref.name) : await skills.read(ref.name));
|
|
10819
10872
|
},
|
|
10820
10873
|
approval() {
|
|
10821
|
-
return
|
|
10874
|
+
return getParams().mergeApproval;
|
|
10822
10875
|
},
|
|
10823
10876
|
readiness() {
|
|
10824
10877
|
return ready;
|
|
@@ -10854,13 +10907,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10854
10907
|
registry: runs,
|
|
10855
10908
|
skills,
|
|
10856
10909
|
now: () => Date.now(),
|
|
10857
|
-
|
|
10858
|
-
|
|
10910
|
+
getParams,
|
|
10911
|
+
getPlatformWrite,
|
|
10859
10912
|
...defaultModel === void 0 ? {} : { model: defaultModel.currentSelection().model },
|
|
10860
10913
|
channel,
|
|
10861
10914
|
parent: void 0,
|
|
10862
10915
|
allow: ["merge_stage"],
|
|
10863
|
-
timeoutMinutes:
|
|
10916
|
+
timeoutMinutes: startupParams.mergeRunTimeoutMinutes,
|
|
10864
10917
|
hostCapabilities,
|
|
10865
10918
|
isBlocked: async (name) => {
|
|
10866
10919
|
return await ctx.get("evolutionCurator")?.isBlocked?.(name) === true;
|
|
@@ -10892,6 +10945,20 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10892
10945
|
};
|
|
10893
10946
|
return runMergeCommand(runtime, rawInput, invocation);
|
|
10894
10947
|
};
|
|
10948
|
+
const agents = ctx.get("agents");
|
|
10949
|
+
decide = async ({ rowId, verb, sessionId }) => {
|
|
10950
|
+
const agent = agents?.get(sessionId);
|
|
10951
|
+
if (agent === void 0 || agent === null) return {
|
|
10952
|
+
ok: false,
|
|
10953
|
+
code: "E-305",
|
|
10954
|
+
message: "当前会话没有可用的 Agent;请在会话打开时操作,或复制命令到 CLI 执行"
|
|
10955
|
+
};
|
|
10956
|
+
const result = await mergeService.run("/evolution merge " + verb + " " + rowId, { agent });
|
|
10957
|
+
return {
|
|
10958
|
+
ok: result.ok,
|
|
10959
|
+
message: result.text
|
|
10960
|
+
};
|
|
10961
|
+
};
|
|
10895
10962
|
const command = createMergeCommand(runtime);
|
|
10896
10963
|
if (command !== null) scope.effect(() => commands.register(command), "evolution-merge: command");
|
|
10897
10964
|
ctx.inject(["tools"], (toolScope) => {
|
package/lib/types/index.js
CHANGED
|
@@ -137,8 +137,9 @@ export const inject = [];
|
|
|
137
137
|
export function apply(ctx, rawConfig = {}) {
|
|
138
138
|
registerOwnerSettings(ctx, 'evolution-merge', Config);
|
|
139
139
|
configureMergePresentation(ctx);
|
|
140
|
-
|
|
140
|
+
const startupParams = resolveMergeParams(rawConfig);
|
|
141
141
|
let mergeRun;
|
|
142
|
+
let decide;
|
|
142
143
|
let source;
|
|
143
144
|
let doctorFact;
|
|
144
145
|
const mergeService = {
|
|
@@ -157,7 +158,14 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
157
158
|
;
|
|
158
159
|
mergeContext.provide('evolutionMerge', mergeService);
|
|
159
160
|
}
|
|
160
|
-
registerHostRoutes(ctx, audit => makeMergeRoutes({
|
|
161
|
+
registerHostRoutes(ctx, audit => makeMergeRoutes({
|
|
162
|
+
getSource: () => source,
|
|
163
|
+
decide: async (request, _audit) => {
|
|
164
|
+
if (decide === undefined)
|
|
165
|
+
return { ok: false, code: 'E-305', message: '合并服务尚未完成挂载' };
|
|
166
|
+
return await decide(request);
|
|
167
|
+
},
|
|
168
|
+
}, audit));
|
|
161
169
|
ctx.inject(['commands', 'evolutionIo'], (scope) => {
|
|
162
170
|
const registry = scope.get('evolutionIo');
|
|
163
171
|
if (registry === undefined)
|
|
@@ -188,9 +196,11 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
188
196
|
}, 'evolution-merge: restore supersede');
|
|
189
197
|
const policySnapshot = ctx.get('evolutionPolicy');
|
|
190
198
|
const limitsSnapshot = resolveLimitsSnapshot(policySnapshot?.get?.());
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
const getPlatformWrite = () => {
|
|
200
|
+
const hostApproval = ctx.get('evolutionApproval');
|
|
201
|
+
return typeof hostApproval?.platformWrite === 'boolean' ? hostApproval.platformWrite : (resolveMergeParams(rawConfig).platformWrite ?? false);
|
|
202
|
+
};
|
|
203
|
+
const getParams = () => ({ ...resolveMergeParams(rawConfig), limitsSnapshot, platformWrite: getPlatformWrite() });
|
|
194
204
|
const skills = newSkillLibrary({
|
|
195
205
|
io, ctx, authority: authorityFrom({ origin: 'subagent' }),
|
|
196
206
|
limits: limitsSnapshot.skillLimits,
|
|
@@ -225,7 +235,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
225
235
|
const pending = v4.state.rows.some(row => row.state === 'pending');
|
|
226
236
|
const library = pending ? await cards().catch(() => []) : [];
|
|
227
237
|
const live = library.length === 0 ? undefined : liveRowFactsOf(library);
|
|
228
|
-
const reconciled = await reconcileMergeState({ io, home, now: () => Date.now(), ttlDays:
|
|
238
|
+
const reconciled = await reconcileMergeState({ io, home, now: () => Date.now(), ttlDays: getParams().mergeSkippedTtlDays, ...(live === undefined ? {} : { live }) });
|
|
229
239
|
if (reconciled.actions.length > 0)
|
|
230
240
|
ctx.logger?.info?.('merge reconcile: ' + String(reconciled.actions.length) + ' action(s)');
|
|
231
241
|
await transactIo(io, reportsSweepLockTarget(), async () => {
|
|
@@ -242,7 +252,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
242
252
|
return state.kind === 'unavailable' ? { code: 'E-323', detail: state.reason } : undefined;
|
|
243
253
|
});
|
|
244
254
|
const stage = new StageToolRegistry();
|
|
245
|
-
const engineInput = resolveEngineInput({ modelsRoot:
|
|
255
|
+
const engineInput = resolveEngineInput({ modelsRoot: startupParams.mergeModelsRoot });
|
|
246
256
|
const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
|
|
247
257
|
const llm = ctx.get('llm');
|
|
248
258
|
const defaultModel = ctx.get('agentDefaultModel');
|
|
@@ -283,7 +293,8 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
283
293
|
});
|
|
284
294
|
source = {
|
|
285
295
|
async snapshot() {
|
|
286
|
-
|
|
296
|
+
const current = getParams();
|
|
297
|
+
return await readMergeSnapshot({ io, home, runs: runs.runs('merge'), approval: current.mergeApproval, platformWrite: getPlatformWrite(), hostOnly: hostCapabilities.hostOnly, live: async () => liveRowFactsOf(await readSkillCards(skills)), history: async () => {
|
|
287
298
|
const items = new Map();
|
|
288
299
|
const receipts = [...await readCommitReceipts(io, home)].sort((a, b) => a.at - b.at);
|
|
289
300
|
const generationOf = new Map(receipts.map((receipt, index) => [receipt.runId, index + 1]));
|
|
@@ -310,7 +321,7 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
310
321
|
} });
|
|
311
322
|
},
|
|
312
323
|
async bodyRef(ref) { return await resolveBody(io, home, ref, async () => ref.side === 'archive' ? await skills.readArchived(ref.name) : await skills.read(ref.name)); },
|
|
313
|
-
approval() { return
|
|
324
|
+
approval() { return getParams().mergeApproval; },
|
|
314
325
|
readiness() { return ready; },
|
|
315
326
|
commands() {
|
|
316
327
|
return ['merge scan', 'merge doctor', 'merge status', 'merge pending', 'merge skipped', 'merge report', 'merge runs', 'merge cancel', 'merge reconcile'];
|
|
@@ -327,9 +338,9 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
327
338
|
if (commands === undefined)
|
|
328
339
|
return;
|
|
329
340
|
const runtime = createMergeRuntime({
|
|
330
|
-
io, home, registry: runs, skills, now: () => Date.now(),
|
|
341
|
+
io, home, registry: runs, skills, now: () => Date.now(), getParams, getPlatformWrite,
|
|
331
342
|
...(defaultModel === undefined ? {} : { model: defaultModel.currentSelection().model }),
|
|
332
|
-
channel, parent: undefined, allow: ['merge_stage'], timeoutMinutes:
|
|
343
|
+
channel, parent: undefined, allow: ['merge_stage'], timeoutMinutes: startupParams.mergeRunTimeoutMinutes,
|
|
333
344
|
hostCapabilities,
|
|
334
345
|
isBlocked: async (name) => {
|
|
335
346
|
const curator = ctx.get('evolutionCurator');
|
|
@@ -358,6 +369,15 @@ export function apply(ctx, rawConfig = {}) {
|
|
|
358
369
|
return { ok: false, text: formatMergeError(commandError('merge', state.code, state.message, '先修复合并状态(/evolution merge doctor),再重试')) };
|
|
359
370
|
return runMergeCommand(runtime, rawInput, invocation);
|
|
360
371
|
};
|
|
372
|
+
const agents = ctx.get('agents');
|
|
373
|
+
decide = async ({ rowId, verb, sessionId }) => {
|
|
374
|
+
const agent = agents?.get(sessionId);
|
|
375
|
+
if (agent === undefined || agent === null) {
|
|
376
|
+
return { ok: false, code: 'E-305', message: '当前会话没有可用的 Agent;请在会话打开时操作,或复制命令到 CLI 执行' };
|
|
377
|
+
}
|
|
378
|
+
const result = await mergeService.run('/evolution merge ' + verb + ' ' + rowId, { agent });
|
|
379
|
+
return { ok: result.ok, message: result.text };
|
|
380
|
+
};
|
|
361
381
|
const command = createMergeCommand(runtime);
|
|
362
382
|
if (command !== null)
|
|
363
383
|
scope.effect(() => commands.register(command), 'evolution-merge: command');
|
|
@@ -12,7 +12,19 @@ export declare const MERGE_ROUTES: {
|
|
|
12
12
|
rows: `/api/dsh-evolution/${string}`;
|
|
13
13
|
body: `/api/dsh-evolution/${string}`;
|
|
14
14
|
history: `/api/dsh-evolution/${string}`;
|
|
15
|
+
decision: `/api/dsh-evolution/${string}`;
|
|
15
16
|
};
|
|
17
|
+
export interface MergeDecisionRequest {
|
|
18
|
+
readonly rowId: string;
|
|
19
|
+
readonly verb: 'approve' | 'reject';
|
|
20
|
+
readonly sessionId: string;
|
|
21
|
+
}
|
|
22
|
+
export interface MergeDecisionResult {
|
|
23
|
+
readonly ok: boolean;
|
|
24
|
+
readonly message: string;
|
|
25
|
+
readonly code?: string;
|
|
26
|
+
readonly runId?: string;
|
|
27
|
+
}
|
|
16
28
|
export interface MergeHistoryItem {
|
|
17
29
|
readonly name: string;
|
|
18
30
|
readonly kind: 'merged' | 'deleted' | 'split' | 'other';
|
|
@@ -35,5 +47,6 @@ export interface MergeRouteSource {
|
|
|
35
47
|
}
|
|
36
48
|
export declare function makeMergeRoutes(deps: {
|
|
37
49
|
getSource: () => MergeRouteSource | undefined;
|
|
50
|
+
decide?: (request: MergeDecisionRequest, audit: DecisionAudit) => Promise<MergeDecisionResult>;
|
|
38
51
|
}, _audit: DecisionAudit): ConnectionFetchRouteLike[];
|
|
39
52
|
//# sourceMappingURL=merge-routes.d.ts.map
|
|
@@ -9,6 +9,7 @@ export const MERGE_ROUTES = {
|
|
|
9
9
|
rows: MERGE_PATHS.rows,
|
|
10
10
|
body: (MERGE_PATHS.body ?? MERGE_BASE + 'body'),
|
|
11
11
|
history: (MERGE_PATHS.history ?? MERGE_BASE + 'history'),
|
|
12
|
+
decision: (MERGE_BASE + 'decision'),
|
|
12
13
|
};
|
|
13
14
|
export function makeMergeRoutes(deps, _audit) {
|
|
14
15
|
const absent = () => json(404, { wireVersion: 1, code: 'E-325', message: 'merge service is not mounted' });
|
|
@@ -110,8 +111,6 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
110
111
|
const id = query.get('id');
|
|
111
112
|
const name = query.get('name');
|
|
112
113
|
const side = query.get('side');
|
|
113
|
-
if (id !== null && name !== null)
|
|
114
|
-
return bad('id and name are mutually exclusive');
|
|
115
114
|
if ((id === null || id === '') && (name === null || name === ''))
|
|
116
115
|
return json(404, { wireVersion: 1, code: 'merge-body-not-found', message: 'merge body requires id or name' });
|
|
117
116
|
if (source.snapshot !== undefined) {
|
|
@@ -120,16 +119,18 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
120
119
|
return json(503, { wireVersion: 1, code: 'merge-state-blocked', readiness: snapshot.blocked.readiness });
|
|
121
120
|
// Body/history are read-only projections of already-written files; a host without
|
|
122
121
|
// the live agent data plane can still read past reports and archived bodies.
|
|
123
|
-
const
|
|
122
|
+
const sideFilter = side === 'a' ? 'live' : side === 'b' ? 'archive' : side === 'live' || side === 'archive' ? side : undefined;
|
|
123
|
+
const refs = snapshot.bodyRefs.filter(ref => (id === null || id === '' || ref.rowId === id)
|
|
124
|
+
&& (name === null || name === '' || ref.name === name)
|
|
125
|
+
&& (sideFilter === undefined || ref.side === sideFilter));
|
|
124
126
|
if (refs.length === 0)
|
|
125
127
|
return json(404, { wireVersion: 1, code: 'merge-body-not-found', message: 'merge body not found' });
|
|
126
|
-
if (
|
|
127
|
-
return bad('
|
|
128
|
+
if (refs.length > 1)
|
|
129
|
+
return bad('merge body is ambiguous; pass id, name and side');
|
|
128
130
|
const row = snapshot.rows.find(item => item.row.id === refs[0].rowId);
|
|
129
131
|
if (row?.stage?.state === 'lost' || row?.stage?.state === 'expired')
|
|
130
132
|
return json(404, { wireVersion: 1, code: 'merge-body-not-found', message: 'merge stage is lost' });
|
|
131
|
-
const
|
|
132
|
-
const ref = (sideFilter === undefined ? refs[0] : refs.find(item => item.side === sideFilter)) ?? refs[0];
|
|
133
|
+
const ref = refs[0];
|
|
133
134
|
const content = source.bodyRef === undefined ? null : await source.bodyRef(ref);
|
|
134
135
|
return json(200, buildBodyView(snapshot, ref, content ?? ''));
|
|
135
136
|
}
|
|
@@ -167,6 +168,35 @@ export function makeMergeRoutes(deps, _audit) {
|
|
|
167
168
|
return json(200, toHistoryView(await source.history?.() ?? [], Date.now()));
|
|
168
169
|
},
|
|
169
170
|
},
|
|
171
|
+
{
|
|
172
|
+
path: MERGE_ROUTES.decision,
|
|
173
|
+
methods: ['POST'],
|
|
174
|
+
requestBody: 'buffered',
|
|
175
|
+
fetch: async (request) => {
|
|
176
|
+
if (request.method !== 'POST')
|
|
177
|
+
return methodNotAllowed(['POST']);
|
|
178
|
+
if (deps.decide === undefined)
|
|
179
|
+
return absent();
|
|
180
|
+
let body;
|
|
181
|
+
try {
|
|
182
|
+
body = await request.json();
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return bad('decision body must be JSON');
|
|
186
|
+
}
|
|
187
|
+
if (typeof body !== 'object' || body === null)
|
|
188
|
+
return bad('decision body must be an object');
|
|
189
|
+
const input = body;
|
|
190
|
+
if (typeof input.rowId !== 'string' || input.rowId === '')
|
|
191
|
+
return bad('rowId is required');
|
|
192
|
+
if (input.verb !== 'approve' && input.verb !== 'reject')
|
|
193
|
+
return bad('verb must be approve or reject');
|
|
194
|
+
if (typeof input.sessionId !== 'string' || input.sessionId === '')
|
|
195
|
+
return bad('sessionId is required');
|
|
196
|
+
const result = await deps.decide({ rowId: input.rowId, verb: input.verb, sessionId: input.sessionId }, _audit);
|
|
197
|
+
return json(result.ok ? 200 : result.code === 'E-305' ? 409 : 400, { wireVersion: 1, ...result });
|
|
198
|
+
},
|
|
199
|
+
},
|
|
170
200
|
];
|
|
171
201
|
}
|
|
172
202
|
//# sourceMappingURL=merge-routes.js.map
|
package/lib/types/runtime.d.ts
CHANGED
|
@@ -18,8 +18,13 @@ export interface MergeRuntimeDeps {
|
|
|
18
18
|
readonly registry: MergeRegistryPort;
|
|
19
19
|
readonly skills: SkillLibrary;
|
|
20
20
|
readonly now: () => number;
|
|
21
|
-
|
|
22
|
-
readonly
|
|
21
|
+
/** Live params: re-resolve at each operation, never cache an apply-time value. */
|
|
22
|
+
readonly getParams?: () => MergeParams;
|
|
23
|
+
/** Legacy fixture shape; used only when getParams is absent. */
|
|
24
|
+
readonly params?: MergeParams;
|
|
25
|
+
readonly getPlatformWrite?: () => boolean;
|
|
26
|
+
/** Legacy fixture shape; used only when getPlatformWrite is absent. */
|
|
27
|
+
readonly platformWrite?: boolean;
|
|
23
28
|
readonly channel: ConversationChannelLike;
|
|
24
29
|
readonly hostCapabilities?: HostCapabilities;
|
|
25
30
|
readonly parent: unknown;
|
package/lib/types/runtime.js
CHANGED
|
@@ -78,13 +78,21 @@ export function unitFor(deps) {
|
|
|
78
78
|
export function createMergeRuntime(deps) {
|
|
79
79
|
const readOnly = createReadOnlyMergeService(deps.io, deps.home);
|
|
80
80
|
const timeout = deps.timeoutMinutes > 0 ? deps.timeoutMinutes : undefined;
|
|
81
|
-
const
|
|
81
|
+
const paramsOf = () => {
|
|
82
|
+
if (deps.getParams !== undefined)
|
|
83
|
+
return deps.getParams();
|
|
84
|
+
if (deps.params !== undefined)
|
|
85
|
+
return deps.params;
|
|
86
|
+
throw new Error('merge runtime requires getParams or params');
|
|
87
|
+
};
|
|
88
|
+
const platformWriteOf = () => deps.getPlatformWrite?.() ?? deps.platformWrite ?? false;
|
|
89
|
+
const common = () => ({
|
|
82
90
|
io: deps.io, home: deps.home, registry: deps.registry,
|
|
83
|
-
platformWrite:
|
|
91
|
+
platformWrite: platformWriteOf(), now: deps.now,
|
|
84
92
|
...(deps.model === undefined ? {} : { model: deps.model }),
|
|
85
93
|
...(deps.isOwnerActive === undefined ? {} : { isOwnerActive: deps.isOwnerActive }),
|
|
86
94
|
...(timeout === undefined ? {} : { timeoutMinutes: timeout }),
|
|
87
|
-
};
|
|
95
|
+
});
|
|
88
96
|
const stage = deps.stage;
|
|
89
97
|
const stageHooks = stage === undefined ? {} : {
|
|
90
98
|
onChild: (context) => { stage.bind(context); },
|
|
@@ -95,6 +103,7 @@ export function createMergeRuntime(deps) {
|
|
|
95
103
|
const parent = context?.parent ?? deps.parent;
|
|
96
104
|
const agentless = context?.agentless === true;
|
|
97
105
|
const detached = deps.detached !== false;
|
|
106
|
+
const params = paramsOf();
|
|
98
107
|
if (command.verb === 'pair') {
|
|
99
108
|
const signal = new AbortController().signal;
|
|
100
109
|
if (command.a === command.b)
|
|
@@ -155,9 +164,9 @@ export function createMergeRuntime(deps) {
|
|
|
155
164
|
return fail('pair', 'pair-fuse', '该 pair 已在待合并队列,可复用决策重跑', '运行 /evolution merge resume --retry ' + (existing.runId ?? existing.id), existing.runId);
|
|
156
165
|
}
|
|
157
166
|
const forced = command.force === true ? forcedDecisionFor(command.a, command.b) : undefined;
|
|
158
|
-
const approval =
|
|
167
|
+
const approval = params.mergeApproval;
|
|
159
168
|
const result = await runScanCommand({
|
|
160
|
-
...common,
|
|
169
|
+
...common(),
|
|
161
170
|
approval,
|
|
162
171
|
cards,
|
|
163
172
|
...(detached ? { detached: true } : {}),
|
|
@@ -167,10 +176,10 @@ export function createMergeRuntime(deps) {
|
|
|
167
176
|
screening: () => Promise.resolve(pair),
|
|
168
177
|
channel: deps.channel, parent, allow: deps.allow,
|
|
169
178
|
...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
|
|
170
|
-
timeoutMs:
|
|
179
|
+
timeoutMs: params.mergePairTimeoutMinutes * 60_000,
|
|
171
180
|
approval,
|
|
172
181
|
commitPort: skillLibraryPort(deps.skills), now: deps.now, ...(unitFor(deps) === undefined ? {} : { unit: unitFor(deps) }),
|
|
173
|
-
...(
|
|
182
|
+
...(params.limitsSnapshot === undefined ? {} : { limits: params.limitsSnapshot.skillLimits }),
|
|
174
183
|
...(deps.isBlocked === undefined ? {} : { isBlocked: deps.isBlocked }),
|
|
175
184
|
...stageHooks,
|
|
176
185
|
});
|
|
@@ -201,7 +210,7 @@ export function createMergeRuntime(deps) {
|
|
|
201
210
|
.filter(card => card.managed === true && card.protectedBy == null && !blocked.has(card.name))
|
|
202
211
|
.map(card => card.name));
|
|
203
212
|
const loadedState = await loadMergeState(deps.io, deps.home);
|
|
204
|
-
const skipped = skippedPairKeys(loadedState.state.rows,
|
|
213
|
+
const skipped = skippedPairKeys(loadedState.state.rows, params.mergeSkippedTtlDays, deps.now());
|
|
205
214
|
const held = new Set(loadedState.state.rows.filter(row => row.queue === 'pending' || row.queue === 'fuse'
|
|
206
215
|
|| (row.queue === 'verify' && (row.runId !== undefined && deps.now() - Date.parse(row.updatedAt) < 10 * 60_000 || !canRetry(row))))
|
|
207
216
|
.map(row => row.pairKey));
|
|
@@ -209,8 +218,8 @@ export function createMergeRuntime(deps) {
|
|
|
209
218
|
.filter(row => row.state === 'pending' || row.state === 'approved' || row.state === 'fuse' || row.state === 'skipped')
|
|
210
219
|
.map(row => 'conflict:' + row.state + ':' + row.pairKey + ':' + row.id);
|
|
211
220
|
const result = await runScanCommand({
|
|
212
|
-
...common,
|
|
213
|
-
approval:
|
|
221
|
+
...common(),
|
|
222
|
+
approval: params.mergeApproval,
|
|
214
223
|
notes,
|
|
215
224
|
cards,
|
|
216
225
|
...(detached ? { detached: true } : {}),
|
|
@@ -238,10 +247,10 @@ export function createMergeRuntime(deps) {
|
|
|
238
247
|
},
|
|
239
248
|
channel: deps.channel, parent, allow: deps.allow,
|
|
240
249
|
...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
|
|
241
|
-
timeoutMs:
|
|
242
|
-
approval:
|
|
250
|
+
timeoutMs: params.mergePairTimeoutMinutes * 60_000,
|
|
251
|
+
approval: params.mergeApproval,
|
|
243
252
|
commitPort: skillLibraryPort(deps.skills), now: deps.now, ...(unitFor(deps) === undefined ? {} : { unit: unitFor(deps) }),
|
|
244
|
-
...(
|
|
253
|
+
...(params.limitsSnapshot === undefined ? {} : { limits: params.limitsSnapshot.skillLimits }),
|
|
245
254
|
...(deps.isBlocked === undefined ? {} : { isBlocked: deps.isBlocked }),
|
|
246
255
|
...stageHooks,
|
|
247
256
|
}),
|
|
@@ -267,8 +276,8 @@ export function createMergeRuntime(deps) {
|
|
|
267
276
|
const cards = await deps.cards(signal);
|
|
268
277
|
const pairCandidate = { a: row.a, b: row.b };
|
|
269
278
|
const result = await runScanCommand({
|
|
270
|
-
...common,
|
|
271
|
-
approval:
|
|
279
|
+
...common(),
|
|
280
|
+
approval: params.mergeApproval,
|
|
272
281
|
notes: ['candidates --retry ' + row.id],
|
|
273
282
|
cards,
|
|
274
283
|
makePorts: runId => createChannelScanPorts({
|
|
@@ -280,10 +289,10 @@ export function createMergeRuntime(deps) {
|
|
|
280
289
|
},
|
|
281
290
|
channel: deps.channel, parent, allow: deps.allow,
|
|
282
291
|
...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
|
|
283
|
-
timeoutMs:
|
|
284
|
-
approval:
|
|
292
|
+
timeoutMs: params.mergePairTimeoutMinutes * 60_000,
|
|
293
|
+
approval: params.mergeApproval,
|
|
285
294
|
commitPort: skillLibraryPort(deps.skills), now: deps.now, ...(unitFor(deps) === undefined ? {} : { unit: unitFor(deps) }),
|
|
286
|
-
...(
|
|
295
|
+
...(params.limitsSnapshot === undefined ? {} : { limits: params.limitsSnapshot.skillLimits }),
|
|
287
296
|
...(deps.isBlocked === undefined ? {} : { isBlocked: deps.isBlocked }),
|
|
288
297
|
...stageHooks,
|
|
289
298
|
}),
|
|
@@ -318,7 +327,7 @@ export function createMergeRuntime(deps) {
|
|
|
318
327
|
const managed = cards === undefined || cards.length === 0 ? undefined : new Set(cards.map(card => card.name));
|
|
319
328
|
const live = cards === undefined || cards.length === 0 ? undefined : liveRowFactsOf(cards);
|
|
320
329
|
try {
|
|
321
|
-
const report = await reconcileMergeState({ io: deps.io, home: deps.home, now: deps.now, ttlDays:
|
|
330
|
+
const report = await reconcileMergeState({ io: deps.io, home: deps.home, now: deps.now, ttlDays: params.mergeSkippedTtlDays, ...(managed === undefined ? {} : { managed }), ...(live === undefined ? {} : { live }) });
|
|
322
331
|
if (report.state === 'blocked')
|
|
323
332
|
return fail('reconcile', report.code ?? 'merge-state-blocked', report.message ?? '合并状态不可读', '先修复合并状态,再运行 /evolution merge doctor');
|
|
324
333
|
return { ok: true, text: formatReconcileReport(report) };
|
|
@@ -328,14 +337,14 @@ export function createMergeRuntime(deps) {
|
|
|
328
337
|
}
|
|
329
338
|
}
|
|
330
339
|
if (command.verb === 'doctor') {
|
|
331
|
-
const rows = await mergeDoctor(deps.io, deps.home,
|
|
340
|
+
const rows = await mergeDoctor(deps.io, deps.home, params.mergeModelsRoot);
|
|
332
341
|
return { ok: true, text: rows.map(row => row.key + ' [' + row.status + '] ' + row.label + ':' + row.detail).join('\n') };
|
|
333
342
|
}
|
|
334
343
|
if (command.verb === 'report' || command.verb === 'runs' || command.verb === 'cancel') {
|
|
335
344
|
const registry = deps.registry;
|
|
336
345
|
if (command.verb === 'runs') {
|
|
337
346
|
const mergeRuns = registry.runs('merge');
|
|
338
|
-
const snapshot = await readMergeSnapshot({ io: deps.io, home: deps.home, runs: mergeRuns, approval:
|
|
347
|
+
const snapshot = await readMergeSnapshot({ io: deps.io, home: deps.home, runs: mergeRuns, approval: params.mergeApproval, now: deps.now });
|
|
339
348
|
return { ok: true, text: formatRunsView(buildRunsView(snapshot)) };
|
|
340
349
|
}
|
|
341
350
|
if (command.verb === 'cancel') {
|
|
@@ -443,27 +452,27 @@ export function createMergeRuntime(deps) {
|
|
|
443
452
|
round2: () => failed('author', 'resume-needs-child', 'fuse resume needs a new child'),
|
|
444
453
|
};
|
|
445
454
|
const result = await runResumeCommand({
|
|
446
|
-
...common, rowId: row.id, ports: basePorts, approval:
|
|
455
|
+
...common(), rowId: row.id, ports: basePorts, approval: params.mergeApproval,
|
|
447
456
|
...(agentless ? { agentless: true } : {}),
|
|
448
457
|
...(detached ? { detached: true } : {}),
|
|
449
|
-
pairTimeoutMinutes:
|
|
458
|
+
pairTimeoutMinutes: params.mergePairTimeoutMinutes,
|
|
450
459
|
...(deps.isOwnerActive === undefined ? {} : { isOwnerActive: deps.isOwnerActive }),
|
|
451
460
|
makePorts: runId => createChannelScanPorts({
|
|
452
461
|
io: deps.io, home: deps.home, runId, cards, screening: pair,
|
|
453
462
|
channel: deps.channel, parent, allow: deps.allow,
|
|
454
463
|
...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
|
|
455
|
-
timeoutMs:
|
|
456
|
-
approval:
|
|
457
|
-
...(
|
|
464
|
+
timeoutMs: params.mergePairTimeoutMinutes * 60_000,
|
|
465
|
+
approval: params.mergeApproval, commitPort: skillLibraryPort(deps.skills), now: deps.now, ...(unitFor(deps) === undefined ? {} : { unit: unitFor(deps) }),
|
|
466
|
+
...(params.limitsSnapshot === undefined ? {} : { limits: params.limitsSnapshot.skillLimits }),
|
|
458
467
|
...(deps.isBlocked === undefined ? {} : { isBlocked: deps.isBlocked }),
|
|
459
468
|
}),
|
|
460
469
|
round2WithRun: createNewChildRound2({
|
|
461
470
|
io: deps.io, home: deps.home, cards, channel: deps.channel, parent,
|
|
462
471
|
parentRunId: (runId) => runId,
|
|
463
472
|
allow: deps.allow, ...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
|
|
464
|
-
timeoutMs:
|
|
473
|
+
timeoutMs: params.mergePairTimeoutMinutes * 60_000,
|
|
465
474
|
commitPort: skillLibraryPort(deps.skills), now: deps.now, ...(unitFor(deps) === undefined ? {} : { unit: unitFor(deps) }),
|
|
466
|
-
...(
|
|
475
|
+
...(params.limitsSnapshot === undefined ? {} : { limits: params.limitsSnapshot.skillLimits }),
|
|
467
476
|
...(deps.isBlocked === undefined ? {} : { isBlocked: deps.isBlocked }),
|
|
468
477
|
...stageHooks,
|
|
469
478
|
}),
|
|
@@ -563,20 +572,20 @@ export function createMergeRuntime(deps) {
|
|
|
563
572
|
}
|
|
564
573
|
}
|
|
565
574
|
const result = await runApproveCommand({
|
|
566
|
-
...common,
|
|
575
|
+
...common(),
|
|
567
576
|
rowId: command.id,
|
|
568
577
|
...(agentless ? { agentless: true } : {}),
|
|
569
578
|
...(detached ? { detached: true } : {}),
|
|
570
|
-
pairTimeoutMinutes:
|
|
579
|
+
pairTimeoutMinutes: params.mergePairTimeoutMinutes,
|
|
571
580
|
ports: {
|
|
572
581
|
stage: createStageForCards({ io: deps.io, home: deps.home, runId: command.id, cards, now: deps.now }),
|
|
573
582
|
round2: createNewChildRound2({
|
|
574
583
|
io: deps.io, home: deps.home, cards, channel: deps.channel, parent,
|
|
575
584
|
parentRunId: (runId) => runId,
|
|
576
585
|
allow: deps.allow, ...(deps.cwd === undefined ? {} : { cwd: deps.cwd }),
|
|
577
|
-
timeoutMs:
|
|
586
|
+
timeoutMs: params.mergePairTimeoutMinutes * 60_000,
|
|
578
587
|
commitPort: skillLibraryPort(deps.skills), now: deps.now, ...(unitFor(deps) === undefined ? {} : { unit: unitFor(deps) }),
|
|
579
|
-
...(
|
|
588
|
+
...(params.limitsSnapshot === undefined ? {} : { limits: params.limitsSnapshot.skillLimits }),
|
|
580
589
|
...(deps.isBlocked === undefined ? {} : { isBlocked: deps.isBlocked }),
|
|
581
590
|
...stageHooks,
|
|
582
591
|
}),
|
|
@@ -597,7 +606,7 @@ export function createMergeRuntime(deps) {
|
|
|
597
606
|
const found = runs?.find(item => item.id === command.id) ?? registry.find(command.id);
|
|
598
607
|
if (found !== undefined && found.kind === 'merge') {
|
|
599
608
|
// CLI consumer of the RunDetail envelope: current pair, per-pair outcome, stop reason.
|
|
600
|
-
const snapshot = await readMergeSnapshot({ io: deps.io, home: deps.home, runs: runs !== undefined && runs.some(item => item.id === found.id) ? runs : [...(runs ?? []), found], approval:
|
|
609
|
+
const snapshot = await readMergeSnapshot({ io: deps.io, home: deps.home, runs: runs !== undefined && runs.some(item => item.id === found.id) ? runs : [...(runs ?? []), found], approval: params.mergeApproval, now: deps.now });
|
|
601
610
|
if (snapshot.blocked !== undefined)
|
|
602
611
|
return fail('status', snapshot.blocked.code, snapshot.blocked.message, '先修复合并状态(/evolution merge doctor),再查看运行');
|
|
603
612
|
const detail = buildRunDetail(snapshot, found.id);
|
|
@@ -615,7 +624,7 @@ export function createMergeRuntime(deps) {
|
|
|
615
624
|
const list = cards ?? await deps.cards(new AbortController().signal).catch(() => []);
|
|
616
625
|
return list.length === 0 ? undefined : liveRowFactsOf(list);
|
|
617
626
|
};
|
|
618
|
-
const text = await handleReadOnlyMergeCommand(command, deps.io, deps.home, cards, runs,
|
|
627
|
+
const text = await handleReadOnlyMergeCommand(command, deps.io, deps.home, cards, runs, params.mergeApproval, liveForRead);
|
|
619
628
|
if (text !== null)
|
|
620
629
|
return { ok: true, text };
|
|
621
630
|
return readOnly.execute(command);
|
|
@@ -86,7 +86,15 @@ function runView(run, readAt, includeResultRef = false) {
|
|
|
86
86
|
: run.meta?.approval === 'pending' ? 'awaiting-approval'
|
|
87
87
|
: record.state;
|
|
88
88
|
const stopReason = run.meta?.stopReason ?? (record.state === 'cancelled' ? 'cancelled' : record.state === 'failed' ? 'failed' : record.state === 'succeeded' ? 'completed' : undefined);
|
|
89
|
-
|
|
89
|
+
// The maker records progress while running; a terminal run that actually produced pairs
|
|
90
|
+
// must not keep the last in-flight 0/N. The UI shows what happened, not the last tick.
|
|
91
|
+
const reportPairs = run.report?.processedPairs?.length ?? run.report?.pairs.length;
|
|
92
|
+
const terminal = state === 'succeeded' || state === 'undone';
|
|
93
|
+
const progress = record.progress === undefined && reportPairs === undefined ? null : {
|
|
94
|
+
done: terminal ? Math.max(record.progress?.done ?? 0, reportPairs ?? 0, run.pairs.length) : record.progress?.done ?? reportPairs ?? 0,
|
|
95
|
+
total: record.progress?.total ?? reportPairs ?? 0,
|
|
96
|
+
};
|
|
97
|
+
return { wireVersion: 1, readAt, id: record.id, state, command: record.command ?? 'scan', ...(record.stage === undefined ? {} : { stage: record.stage }), progress, startedAt: record.startedAt, ...(record.endedAt === undefined ? {} : { endedAt: record.endedAt }), ...(stopReason === undefined ? {} : { stopReason }), ...(record.failure === undefined ? {} : { failure: { stage: 'settle', code: 'run-failed', retryable: false, retryFrom: 'none', detail: record.failure } }), ...(record.parentRunId === undefined ? {} : { parentRunId: record.parentRunId }), ...(run.pairs[0]?.pairKey === undefined ? {} : { currentPairKey: run.pairs[0].pairKey }), ...(record.resultRef === undefined ? {} : { resultRef: record.resultRef }), actions: [{ id: 'status', command: base + 'status ' + record.id, confirm: false, enabled: true }, { id: 'report', command: base + 'report ' + record.id, confirm: false, enabled: record.resultRef !== undefined }, { id: 'cancel', command: base + 'cancel ' + record.id, confirm: false, enabled: record.state === 'running' }] };
|
|
90
98
|
}
|
|
91
99
|
export function buildFacts(snapshot) {
|
|
92
100
|
const ordered = [...snapshot.runs].sort((a, b) => b.record.startedAt - a.record.startedAt);
|
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.
|
|
4
|
+
"version": "0.34.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@deepseek-ai/schemastery": "^3.18.5",
|
|
35
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
36
|
-
"@lmzhen/dsh-evolution-commands": "^0.
|
|
37
|
-
"@lmzhen/dsh-evolution-merge-engine": "0.
|
|
35
|
+
"@lmzhen/dsh-evolution-core": "^0.34.0",
|
|
36
|
+
"@lmzhen/dsh-evolution-commands": "^0.34.0",
|
|
37
|
+
"@lmzhen/dsh-evolution-merge-engine": "0.34.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@deepseek-ai/cordis": "^4.0.5"
|