@lmzhen/dsh-evolution-merge 0.33.0 → 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 +126 -60
- 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 +5 -3
- package/package.json +4 -4
package/lib/index.js
CHANGED
|
@@ -3700,9 +3700,11 @@ 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
|
|
3704
|
-
|
|
3705
|
-
|
|
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
|
|
3706
3708
|
};
|
|
3707
3709
|
return {
|
|
3708
3710
|
wireVersion: 1,
|
|
@@ -5057,7 +5059,8 @@ const MERGE_ROUTES = {
|
|
|
5057
5059
|
view: MERGE_PATHS.view,
|
|
5058
5060
|
rows: MERGE_PATHS.rows,
|
|
5059
5061
|
body: MERGE_PATHS.body ?? MERGE_BASE + "body",
|
|
5060
|
-
history: MERGE_PATHS.history ?? MERGE_BASE + "history"
|
|
5062
|
+
history: MERGE_PATHS.history ?? MERGE_BASE + "history",
|
|
5063
|
+
decision: MERGE_BASE + "decision"
|
|
5061
5064
|
};
|
|
5062
5065
|
function makeMergeRoutes(deps, _audit) {
|
|
5063
5066
|
const absent = () => json(404, {
|
|
@@ -5172,7 +5175,6 @@ function makeMergeRoutes(deps, _audit) {
|
|
|
5172
5175
|
const id = query.get("id");
|
|
5173
5176
|
const name = query.get("name");
|
|
5174
5177
|
const side = query.get("side");
|
|
5175
|
-
if (id !== null && name !== null) return bad("id and name are mutually exclusive");
|
|
5176
5178
|
if ((id === null || id === "") && (name === null || name === "")) return json(404, {
|
|
5177
5179
|
wireVersion: 1,
|
|
5178
5180
|
code: "merge-body-not-found",
|
|
@@ -5185,21 +5187,21 @@ function makeMergeRoutes(deps, _audit) {
|
|
|
5185
5187
|
code: "merge-state-blocked",
|
|
5186
5188
|
readiness: snapshot.blocked.readiness
|
|
5187
5189
|
});
|
|
5188
|
-
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));
|
|
5189
5192
|
if (refs.length === 0) return json(404, {
|
|
5190
5193
|
wireVersion: 1,
|
|
5191
5194
|
code: "merge-body-not-found",
|
|
5192
5195
|
message: "merge body not found"
|
|
5193
5196
|
});
|
|
5194
|
-
if (
|
|
5197
|
+
if (refs.length > 1) return bad("merge body is ambiguous; pass id, name and side");
|
|
5195
5198
|
const row = snapshot.rows.find((item) => item.row.id === refs[0].rowId);
|
|
5196
5199
|
if (row?.stage?.state === "lost" || row?.stage?.state === "expired") return json(404, {
|
|
5197
5200
|
wireVersion: 1,
|
|
5198
5201
|
code: "merge-body-not-found",
|
|
5199
5202
|
message: "merge stage is lost"
|
|
5200
5203
|
});
|
|
5201
|
-
const
|
|
5202
|
-
const ref = (sideFilter === void 0 ? refs[0] : refs.find((item) => item.side === sideFilter)) ?? refs[0];
|
|
5204
|
+
const ref = refs[0];
|
|
5203
5205
|
return json(200, buildBodyView(snapshot, ref, (source.bodyRef === void 0 ? null : await source.bodyRef(ref)) ?? ""));
|
|
5204
5206
|
}
|
|
5205
5207
|
const readiness = await notReady(source);
|
|
@@ -5235,6 +5237,35 @@ function makeMergeRoutes(deps, _audit) {
|
|
|
5235
5237
|
if (readiness !== null) return readiness;
|
|
5236
5238
|
return json(200, toHistoryView(await source.history?.() ?? [], Date.now()));
|
|
5237
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
|
+
}
|
|
5238
5269
|
}
|
|
5239
5270
|
];
|
|
5240
5271
|
}
|
|
@@ -9262,16 +9293,22 @@ function unitFor(deps) {
|
|
|
9262
9293
|
function createMergeRuntime(deps) {
|
|
9263
9294
|
const readOnly = createReadOnlyMergeService(deps.io, deps.home);
|
|
9264
9295
|
const timeout = deps.timeoutMinutes > 0 ? deps.timeoutMinutes : void 0;
|
|
9265
|
-
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 = () => ({
|
|
9266
9303
|
io: deps.io,
|
|
9267
9304
|
home: deps.home,
|
|
9268
9305
|
registry: deps.registry,
|
|
9269
|
-
platformWrite:
|
|
9306
|
+
platformWrite: platformWriteOf(),
|
|
9270
9307
|
now: deps.now,
|
|
9271
9308
|
...deps.model === void 0 ? {} : { model: deps.model },
|
|
9272
9309
|
...deps.isOwnerActive === void 0 ? {} : { isOwnerActive: deps.isOwnerActive },
|
|
9273
9310
|
...timeout === void 0 ? {} : { timeoutMinutes: timeout }
|
|
9274
|
-
};
|
|
9311
|
+
});
|
|
9275
9312
|
const stage = deps.stage;
|
|
9276
9313
|
const stageHooks = stage === void 0 ? {} : {
|
|
9277
9314
|
onChild: (context) => {
|
|
@@ -9285,6 +9322,7 @@ function createMergeRuntime(deps) {
|
|
|
9285
9322
|
const parent = context?.parent ?? deps.parent;
|
|
9286
9323
|
const agentless = context?.agentless === true;
|
|
9287
9324
|
const detached = deps.detached !== false;
|
|
9325
|
+
const params = paramsOf();
|
|
9288
9326
|
if (command.verb === "pair") {
|
|
9289
9327
|
const signal = new AbortController().signal;
|
|
9290
9328
|
if (command.a === command.b) return fail("pair", "pair-same-source", "merge pair 需要两个不同技能源:" + command.a, "重新指定两个不同的 family-managed 技能");
|
|
@@ -9324,9 +9362,9 @@ function createMergeRuntime(deps) {
|
|
|
9324
9362
|
if (existing?.state === "pending") return fail("pair", "pair-pending", "该 pair 已在待批准队列:" + existing.id, "先运行 /evolution merge approve/reject/discard " + existing.id, existing.runId);
|
|
9325
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);
|
|
9326
9364
|
const forced = command.force === true ? forcedDecisionFor(command.a, command.b) : void 0;
|
|
9327
|
-
const approval =
|
|
9365
|
+
const approval = params.mergeApproval;
|
|
9328
9366
|
const result = await runScanCommand({
|
|
9329
|
-
...common,
|
|
9367
|
+
...common(),
|
|
9330
9368
|
approval,
|
|
9331
9369
|
cards,
|
|
9332
9370
|
...detached ? { detached: true } : {},
|
|
@@ -9341,12 +9379,12 @@ function createMergeRuntime(deps) {
|
|
|
9341
9379
|
parent,
|
|
9342
9380
|
allow: deps.allow,
|
|
9343
9381
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9344
|
-
timeoutMs:
|
|
9382
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9345
9383
|
approval,
|
|
9346
9384
|
commitPort: skillLibraryPort(deps.skills),
|
|
9347
9385
|
now: deps.now,
|
|
9348
9386
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9349
|
-
...
|
|
9387
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9350
9388
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9351
9389
|
...stageHooks
|
|
9352
9390
|
});
|
|
@@ -9380,12 +9418,12 @@ function createMergeRuntime(deps) {
|
|
|
9380
9418
|
for (const card of cards) if (await deps.isBlocked?.(card.name) === true) blocked.add(card.name);
|
|
9381
9419
|
const managed = new Set(cards.filter((card) => card.managed === true && card.protectedBy == null && !blocked.has(card.name)).map((card) => card.name));
|
|
9382
9420
|
const loadedState = await loadMergeState(deps.io, deps.home);
|
|
9383
|
-
const skipped = skippedPairKeys(loadedState.state.rows,
|
|
9421
|
+
const skipped = skippedPairKeys(loadedState.state.rows, params.mergeSkippedTtlDays, deps.now());
|
|
9384
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));
|
|
9385
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);
|
|
9386
9424
|
const result = await runScanCommand({
|
|
9387
|
-
...common,
|
|
9388
|
-
approval:
|
|
9425
|
+
...common(),
|
|
9426
|
+
approval: params.mergeApproval,
|
|
9389
9427
|
notes,
|
|
9390
9428
|
cards,
|
|
9391
9429
|
...detached ? { detached: true } : {},
|
|
@@ -9417,12 +9455,12 @@ function createMergeRuntime(deps) {
|
|
|
9417
9455
|
parent,
|
|
9418
9456
|
allow: deps.allow,
|
|
9419
9457
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9420
|
-
timeoutMs:
|
|
9421
|
-
approval:
|
|
9458
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9459
|
+
approval: params.mergeApproval,
|
|
9422
9460
|
commitPort: skillLibraryPort(deps.skills),
|
|
9423
9461
|
now: deps.now,
|
|
9424
9462
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9425
|
-
...
|
|
9463
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9426
9464
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9427
9465
|
...stageHooks
|
|
9428
9466
|
})
|
|
@@ -9450,8 +9488,8 @@ function createMergeRuntime(deps) {
|
|
|
9450
9488
|
b: row.b
|
|
9451
9489
|
};
|
|
9452
9490
|
const result = await runScanCommand({
|
|
9453
|
-
...common,
|
|
9454
|
-
approval:
|
|
9491
|
+
...common(),
|
|
9492
|
+
approval: params.mergeApproval,
|
|
9455
9493
|
notes: ["candidates --retry " + row.id],
|
|
9456
9494
|
cards,
|
|
9457
9495
|
makePorts: (runId) => createChannelScanPorts({
|
|
@@ -9468,12 +9506,12 @@ function createMergeRuntime(deps) {
|
|
|
9468
9506
|
parent,
|
|
9469
9507
|
allow: deps.allow,
|
|
9470
9508
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9471
|
-
timeoutMs:
|
|
9472
|
-
approval:
|
|
9509
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9510
|
+
approval: params.mergeApproval,
|
|
9473
9511
|
commitPort: skillLibraryPort(deps.skills),
|
|
9474
9512
|
now: deps.now,
|
|
9475
9513
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9476
|
-
...
|
|
9514
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9477
9515
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9478
9516
|
...stageHooks
|
|
9479
9517
|
})
|
|
@@ -9515,7 +9553,7 @@ function createMergeRuntime(deps) {
|
|
|
9515
9553
|
io: deps.io,
|
|
9516
9554
|
home: deps.home,
|
|
9517
9555
|
now: deps.now,
|
|
9518
|
-
ttlDays:
|
|
9556
|
+
ttlDays: params.mergeSkippedTtlDays,
|
|
9519
9557
|
...managed === void 0 ? {} : { managed },
|
|
9520
9558
|
...live === void 0 ? {} : { live }
|
|
9521
9559
|
});
|
|
@@ -9530,7 +9568,7 @@ function createMergeRuntime(deps) {
|
|
|
9530
9568
|
}
|
|
9531
9569
|
if (command.verb === "doctor") return {
|
|
9532
9570
|
ok: true,
|
|
9533
|
-
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")
|
|
9534
9572
|
};
|
|
9535
9573
|
if (command.verb === "report" || command.verb === "runs" || command.verb === "cancel") {
|
|
9536
9574
|
const registry = deps.registry;
|
|
@@ -9542,7 +9580,7 @@ function createMergeRuntime(deps) {
|
|
|
9542
9580
|
io: deps.io,
|
|
9543
9581
|
home: deps.home,
|
|
9544
9582
|
runs: mergeRuns,
|
|
9545
|
-
approval:
|
|
9583
|
+
approval: params.mergeApproval,
|
|
9546
9584
|
now: deps.now
|
|
9547
9585
|
})))
|
|
9548
9586
|
};
|
|
@@ -9658,13 +9696,13 @@ function createMergeRuntime(deps) {
|
|
|
9658
9696
|
round2: () => failed("author", "resume-needs-child", "fuse resume needs a new child")
|
|
9659
9697
|
};
|
|
9660
9698
|
const result = await runResumeCommand({
|
|
9661
|
-
...common,
|
|
9699
|
+
...common(),
|
|
9662
9700
|
rowId: row.id,
|
|
9663
9701
|
ports: basePorts,
|
|
9664
|
-
approval:
|
|
9702
|
+
approval: params.mergeApproval,
|
|
9665
9703
|
...agentless ? { agentless: true } : {},
|
|
9666
9704
|
...detached ? { detached: true } : {},
|
|
9667
|
-
pairTimeoutMinutes:
|
|
9705
|
+
pairTimeoutMinutes: params.mergePairTimeoutMinutes,
|
|
9668
9706
|
...deps.isOwnerActive === void 0 ? {} : { isOwnerActive: deps.isOwnerActive },
|
|
9669
9707
|
makePorts: (runId) => createChannelScanPorts({
|
|
9670
9708
|
io: deps.io,
|
|
@@ -9676,12 +9714,12 @@ function createMergeRuntime(deps) {
|
|
|
9676
9714
|
parent,
|
|
9677
9715
|
allow: deps.allow,
|
|
9678
9716
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9679
|
-
timeoutMs:
|
|
9680
|
-
approval:
|
|
9717
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9718
|
+
approval: params.mergeApproval,
|
|
9681
9719
|
commitPort: skillLibraryPort(deps.skills),
|
|
9682
9720
|
now: deps.now,
|
|
9683
9721
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9684
|
-
...
|
|
9722
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9685
9723
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked }
|
|
9686
9724
|
}),
|
|
9687
9725
|
round2WithRun: createNewChildRound2({
|
|
@@ -9693,11 +9731,11 @@ function createMergeRuntime(deps) {
|
|
|
9693
9731
|
parentRunId: (runId) => runId,
|
|
9694
9732
|
allow: deps.allow,
|
|
9695
9733
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9696
|
-
timeoutMs:
|
|
9734
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9697
9735
|
commitPort: skillLibraryPort(deps.skills),
|
|
9698
9736
|
now: deps.now,
|
|
9699
9737
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9700
|
-
...
|
|
9738
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9701
9739
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9702
9740
|
...stageHooks
|
|
9703
9741
|
})
|
|
@@ -9812,11 +9850,11 @@ function createMergeRuntime(deps) {
|
|
|
9812
9850
|
}
|
|
9813
9851
|
}
|
|
9814
9852
|
const result = await runApproveCommand({
|
|
9815
|
-
...common,
|
|
9853
|
+
...common(),
|
|
9816
9854
|
rowId: command.id,
|
|
9817
9855
|
...agentless ? { agentless: true } : {},
|
|
9818
9856
|
...detached ? { detached: true } : {},
|
|
9819
|
-
pairTimeoutMinutes:
|
|
9857
|
+
pairTimeoutMinutes: params.mergePairTimeoutMinutes,
|
|
9820
9858
|
ports: {
|
|
9821
9859
|
stage: createStageForCards({
|
|
9822
9860
|
io: deps.io,
|
|
@@ -9834,11 +9872,11 @@ function createMergeRuntime(deps) {
|
|
|
9834
9872
|
parentRunId: (runId) => runId,
|
|
9835
9873
|
allow: deps.allow,
|
|
9836
9874
|
...deps.cwd === void 0 ? {} : { cwd: deps.cwd },
|
|
9837
|
-
timeoutMs:
|
|
9875
|
+
timeoutMs: params.mergePairTimeoutMinutes * 6e4,
|
|
9838
9876
|
commitPort: skillLibraryPort(deps.skills),
|
|
9839
9877
|
now: deps.now,
|
|
9840
9878
|
...unitFor(deps) === void 0 ? {} : { unit: unitFor(deps) },
|
|
9841
|
-
...
|
|
9879
|
+
...params.limitsSnapshot === void 0 ? {} : { limits: params.limitsSnapshot.skillLimits },
|
|
9842
9880
|
...deps.isBlocked === void 0 ? {} : { isBlocked: deps.isBlocked },
|
|
9843
9881
|
...stageHooks
|
|
9844
9882
|
})
|
|
@@ -9865,7 +9903,7 @@ function createMergeRuntime(deps) {
|
|
|
9865
9903
|
io: deps.io,
|
|
9866
9904
|
home: deps.home,
|
|
9867
9905
|
runs: runs !== void 0 && runs.some((item) => item.id === found.id) ? runs : [...runs ?? [], found],
|
|
9868
|
-
approval:
|
|
9906
|
+
approval: params.mergeApproval,
|
|
9869
9907
|
now: deps.now
|
|
9870
9908
|
});
|
|
9871
9909
|
if (snapshot.blocked !== void 0) return fail("status", snapshot.blocked.code, snapshot.blocked.message, "先修复合并状态(/evolution merge doctor),再查看运行");
|
|
@@ -9882,7 +9920,7 @@ function createMergeRuntime(deps) {
|
|
|
9882
9920
|
const list = cards ?? await deps.cards(new AbortController().signal).catch(() => []);
|
|
9883
9921
|
return list.length === 0 ? void 0 : liveRowFactsOf(list);
|
|
9884
9922
|
};
|
|
9885
|
-
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);
|
|
9886
9924
|
if (text !== null) return {
|
|
9887
9925
|
ok: true,
|
|
9888
9926
|
text
|
|
@@ -10546,8 +10584,9 @@ const inject = [];
|
|
|
10546
10584
|
function apply(ctx, rawConfig = {}) {
|
|
10547
10585
|
registerOwnerSettings(ctx, "evolution-merge", Config);
|
|
10548
10586
|
configureMergePresentation(ctx);
|
|
10549
|
-
|
|
10587
|
+
const startupParams = resolveMergeParams(rawConfig);
|
|
10550
10588
|
let mergeRun;
|
|
10589
|
+
let decide;
|
|
10551
10590
|
let source;
|
|
10552
10591
|
let doctorFact;
|
|
10553
10592
|
const mergeService = {
|
|
@@ -10569,7 +10608,17 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10569
10608
|
};
|
|
10570
10609
|
const mergeContext = ctx;
|
|
10571
10610
|
if (typeof mergeContext.provide === "function") mergeContext.provide("evolutionMerge", mergeService);
|
|
10572
|
-
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));
|
|
10573
10622
|
ctx.inject(["commands", "evolutionIo"], (scope) => {
|
|
10574
10623
|
const registry = scope.get("evolutionIo");
|
|
10575
10624
|
if (registry === void 0) return;
|
|
@@ -10620,13 +10669,15 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10620
10669
|
});
|
|
10621
10670
|
}, "evolution-merge: restore supersede");
|
|
10622
10671
|
const limitsSnapshot = resolveLimitsSnapshot(ctx.get("evolutionPolicy")?.get?.());
|
|
10623
|
-
const
|
|
10624
|
-
|
|
10625
|
-
|
|
10626
|
-
...params,
|
|
10627
|
-
limitsSnapshot,
|
|
10628
|
-
platformWrite
|
|
10672
|
+
const getPlatformWrite = () => {
|
|
10673
|
+
const hostApproval = ctx.get("evolutionApproval");
|
|
10674
|
+
return typeof hostApproval?.platformWrite === "boolean" ? hostApproval.platformWrite : resolveMergeParams(rawConfig).platformWrite ?? false;
|
|
10629
10675
|
};
|
|
10676
|
+
const getParams = () => ({
|
|
10677
|
+
...resolveMergeParams(rawConfig),
|
|
10678
|
+
limitsSnapshot,
|
|
10679
|
+
platformWrite: getPlatformWrite()
|
|
10680
|
+
});
|
|
10630
10681
|
const skills = newSkillLibrary({
|
|
10631
10682
|
io,
|
|
10632
10683
|
ctx,
|
|
@@ -10669,7 +10720,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10669
10720
|
io,
|
|
10670
10721
|
home,
|
|
10671
10722
|
now: () => Date.now(),
|
|
10672
|
-
ttlDays:
|
|
10723
|
+
ttlDays: getParams().mergeSkippedTtlDays,
|
|
10673
10724
|
...live === void 0 ? {} : { live }
|
|
10674
10725
|
});
|
|
10675
10726
|
if (reconciled.actions.length > 0) ctx.logger?.info?.("merge reconcile: " + String(reconciled.actions.length) + " action(s)");
|
|
@@ -10706,7 +10757,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10706
10757
|
} : void 0;
|
|
10707
10758
|
});
|
|
10708
10759
|
const stage = new StageToolRegistry();
|
|
10709
|
-
const engineInput = resolveEngineInput({ modelsRoot:
|
|
10760
|
+
const engineInput = resolveEngineInput({ modelsRoot: startupParams.mergeModelsRoot });
|
|
10710
10761
|
const engineConfig = engineConfigFromRoot(engineInput.modelsRoot);
|
|
10711
10762
|
const llm = ctx.get("llm");
|
|
10712
10763
|
const defaultModel = ctx.get("agentDefaultModel");
|
|
@@ -10762,12 +10813,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10762
10813
|
});
|
|
10763
10814
|
source = {
|
|
10764
10815
|
async snapshot() {
|
|
10816
|
+
const current = getParams();
|
|
10765
10817
|
return await readMergeSnapshot({
|
|
10766
10818
|
io,
|
|
10767
10819
|
home,
|
|
10768
10820
|
runs: runs.runs("merge"),
|
|
10769
|
-
approval:
|
|
10770
|
-
platformWrite,
|
|
10821
|
+
approval: current.mergeApproval,
|
|
10822
|
+
platformWrite: getPlatformWrite(),
|
|
10771
10823
|
hostOnly: hostCapabilities.hostOnly,
|
|
10772
10824
|
live: async () => liveRowFactsOf(await readSkillCards(skills)),
|
|
10773
10825
|
history: async () => {
|
|
@@ -10819,7 +10871,7 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10819
10871
|
return await resolveBody(io, home, ref, async () => ref.side === "archive" ? await skills.readArchived(ref.name) : await skills.read(ref.name));
|
|
10820
10872
|
},
|
|
10821
10873
|
approval() {
|
|
10822
|
-
return
|
|
10874
|
+
return getParams().mergeApproval;
|
|
10823
10875
|
},
|
|
10824
10876
|
readiness() {
|
|
10825
10877
|
return ready;
|
|
@@ -10855,13 +10907,13 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10855
10907
|
registry: runs,
|
|
10856
10908
|
skills,
|
|
10857
10909
|
now: () => Date.now(),
|
|
10858
|
-
|
|
10859
|
-
|
|
10910
|
+
getParams,
|
|
10911
|
+
getPlatformWrite,
|
|
10860
10912
|
...defaultModel === void 0 ? {} : { model: defaultModel.currentSelection().model },
|
|
10861
10913
|
channel,
|
|
10862
10914
|
parent: void 0,
|
|
10863
10915
|
allow: ["merge_stage"],
|
|
10864
|
-
timeoutMinutes:
|
|
10916
|
+
timeoutMinutes: startupParams.mergeRunTimeoutMinutes,
|
|
10865
10917
|
hostCapabilities,
|
|
10866
10918
|
isBlocked: async (name) => {
|
|
10867
10919
|
return await ctx.get("evolutionCurator")?.isBlocked?.(name) === true;
|
|
@@ -10893,6 +10945,20 @@ function apply(ctx, rawConfig = {}) {
|
|
|
10893
10945
|
};
|
|
10894
10946
|
return runMergeCommand(runtime, rawInput, invocation);
|
|
10895
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
|
+
};
|
|
10896
10962
|
const command = createMergeCommand(runtime);
|
|
10897
10963
|
if (command !== null) scope.effect(() => commands.register(command), "evolution-merge: command");
|
|
10898
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);
|
|
@@ -88,9 +88,11 @@ function runView(run, readAt, includeResultRef = false) {
|
|
|
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
90
|
// must not keep the last in-flight 0/N. The UI shows what happened, not the last tick.
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
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,
|
|
94
96
|
};
|
|
95
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' }] };
|
|
96
98
|
}
|
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"
|