oasis_test_v2 2.2.71 → 2.2.73
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/dist/index.js +1153 -87
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -845,6 +845,18 @@ var init_runtime = __esm({
|
|
|
845
845
|
}
|
|
846
846
|
});
|
|
847
847
|
|
|
848
|
+
// ../contract/src/runtime-allocation.ts
|
|
849
|
+
var RUNTIME_FAULT_PROBE_MAX_INTERVAL_MS, RUNTIME_FAULT_PROBE_MAX_TOTAL_MS, RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT, RUNTIME_ALLOCATION_MAX_ATTEMPTS;
|
|
850
|
+
var init_runtime_allocation = __esm({
|
|
851
|
+
"../contract/src/runtime-allocation.ts"() {
|
|
852
|
+
"use strict";
|
|
853
|
+
RUNTIME_FAULT_PROBE_MAX_INTERVAL_MS = 30 * 6e4;
|
|
854
|
+
RUNTIME_FAULT_PROBE_MAX_TOTAL_MS = 6 * 60 * 6e4;
|
|
855
|
+
RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT = 60;
|
|
856
|
+
RUNTIME_ALLOCATION_MAX_ATTEMPTS = 3;
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
|
|
848
860
|
// ../contract/src/handle-passthrough.ts
|
|
849
861
|
function makePassthroughProxy(spec) {
|
|
850
862
|
const overrides = spec.overrides ?? {};
|
|
@@ -2260,6 +2272,7 @@ var init_src = __esm({
|
|
|
2260
2272
|
init_storage();
|
|
2261
2273
|
init_avatar();
|
|
2262
2274
|
init_runtime();
|
|
2275
|
+
init_runtime_allocation();
|
|
2263
2276
|
init_handle_passthrough();
|
|
2264
2277
|
init_parse_extra_args();
|
|
2265
2278
|
init_daemon_package();
|
|
@@ -6688,22 +6701,28 @@ function readCodexTokenCountLine(line2) {
|
|
|
6688
6701
|
...total ? { total } : {}
|
|
6689
6702
|
};
|
|
6690
6703
|
}
|
|
6704
|
+
function readCodexTurnContextModel(line2) {
|
|
6705
|
+
if (!line2 || line2.indexOf("turn_context") === -1) return void 0;
|
|
6706
|
+
let o;
|
|
6707
|
+
try {
|
|
6708
|
+
o = JSON.parse(line2);
|
|
6709
|
+
} catch {
|
|
6710
|
+
return void 0;
|
|
6711
|
+
}
|
|
6712
|
+
if (o?.type !== "turn_context") return void 0;
|
|
6713
|
+
return typeof o.payload?.model === "string" && o.payload.model ? o.payload.model : void 0;
|
|
6714
|
+
}
|
|
6691
6715
|
function parseCodexModel(lines) {
|
|
6692
6716
|
let model;
|
|
6693
|
-
for (const line2 of lines)
|
|
6694
|
-
if (!line2 || line2.indexOf("turn_context") === -1) continue;
|
|
6695
|
-
let o;
|
|
6696
|
-
try {
|
|
6697
|
-
o = JSON.parse(line2);
|
|
6698
|
-
} catch {
|
|
6699
|
-
continue;
|
|
6700
|
-
}
|
|
6701
|
-
if (o?.type === "turn_context" && typeof o.payload?.model === "string" && o.payload.model) {
|
|
6702
|
-
model = o.payload.model;
|
|
6703
|
-
}
|
|
6704
|
-
}
|
|
6717
|
+
for (const line2 of lines) model = readCodexTurnContextModel(line2) ?? model;
|
|
6705
6718
|
return model;
|
|
6706
6719
|
}
|
|
6720
|
+
function pickCodexModel(sources) {
|
|
6721
|
+
for (const candidate of [sources.rollout, sources.thread, sources.requested]) {
|
|
6722
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate;
|
|
6723
|
+
}
|
|
6724
|
+
return void 0;
|
|
6725
|
+
}
|
|
6707
6726
|
function codexSessionsRoot() {
|
|
6708
6727
|
const candidates = [];
|
|
6709
6728
|
const ch = process.env["CODEX_HOME"];
|
|
@@ -7031,7 +7050,7 @@ function codexTurnErrorMessage(value2) {
|
|
|
7031
7050
|
const detail = typeof error2?.additionalDetails === "string" ? error2.additionalDetails.trim() : "";
|
|
7032
7051
|
return [summary, detail].filter(Boolean).join("\n").slice(0, 4e3);
|
|
7033
7052
|
}
|
|
7034
|
-
var import_node_child_process7, import_node_crypto9, fs8, os6, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CodexRolloutUsageTail, CODEX_APP_SERVER_BLOCKED_FLAGS, CodexAdapter;
|
|
7053
|
+
var import_node_child_process7, import_node_crypto9, fs8, os6, path10, readline2, CODEX_BLOCKED_FLAGS, CODEX_ARGV_PROMPT_MAX_BYTES, CodexRolloutLineTail, CodexRolloutUsageTail, CodexRolloutModelTail, CODEX_APP_SERVER_BLOCKED_FLAGS, CodexAdapter;
|
|
7035
7054
|
var init_codex = __esm({
|
|
7036
7055
|
"../adapters/src/codex/index.ts"() {
|
|
7037
7056
|
"use strict";
|
|
@@ -7069,25 +7088,21 @@ var init_codex = __esm({
|
|
|
7069
7088
|
// Multica 实证:codex --listen stdio:// 是 daemon↔CLI 传输协议;改会断链
|
|
7070
7089
|
]);
|
|
7071
7090
|
CODEX_ARGV_PROMPT_MAX_BYTES = 1e5;
|
|
7072
|
-
|
|
7073
|
-
constructor(file
|
|
7091
|
+
CodexRolloutLineTail = class {
|
|
7092
|
+
constructor(file) {
|
|
7074
7093
|
this.file = file;
|
|
7075
|
-
this.sinceMs = sinceMs;
|
|
7076
7094
|
}
|
|
7077
7095
|
offset = 0;
|
|
7078
7096
|
rest = "";
|
|
7079
|
-
|
|
7080
|
-
|
|
7081
|
-
/** 读新追加的行,返回当前窗内累计;还没有任何窗内轮次返回 undefined。读失败抛给调用方(调用方吞)。 */
|
|
7082
|
-
poll() {
|
|
7097
|
+
/** 读新追加的整行逐行回调;文件被截短时先回调 onReset 再从头读。读失败抛给调用方(调用方吞)。 */
|
|
7098
|
+
read(onLine, onReset) {
|
|
7083
7099
|
const fd = fs8.openSync(this.file, "r");
|
|
7084
7100
|
try {
|
|
7085
7101
|
const size = fs8.fstatSync(fd).size;
|
|
7086
7102
|
if (size < this.offset) {
|
|
7087
7103
|
this.offset = 0;
|
|
7088
7104
|
this.rest = "";
|
|
7089
|
-
|
|
7090
|
-
this.acc.input = this.acc.cacheRead = this.acc.cacheWrite = this.acc.output = 0;
|
|
7105
|
+
onReset?.();
|
|
7091
7106
|
}
|
|
7092
7107
|
if (size > this.offset) {
|
|
7093
7108
|
const buf = Buffer.alloc(size - this.offset);
|
|
@@ -7096,19 +7111,62 @@ var init_codex = __esm({
|
|
|
7096
7111
|
const text7 = this.rest + buf.subarray(0, n).toString("utf8");
|
|
7097
7112
|
const lines = text7.split("\n");
|
|
7098
7113
|
this.rest = lines.pop() ?? "";
|
|
7099
|
-
for (const line2 of lines)
|
|
7100
|
-
const row = readCodexTokenCountLine(line2);
|
|
7101
|
-
if (!row?.turn || !Number.isFinite(row.ts) || row.ts < this.sinceMs) continue;
|
|
7102
|
-
addCodexBuckets(this.acc, row.turn);
|
|
7103
|
-
this.saw = true;
|
|
7104
|
-
}
|
|
7114
|
+
for (const line2 of lines) onLine(line2);
|
|
7105
7115
|
}
|
|
7106
7116
|
} finally {
|
|
7107
7117
|
fs8.closeSync(fd);
|
|
7108
7118
|
}
|
|
7119
|
+
}
|
|
7120
|
+
};
|
|
7121
|
+
CodexRolloutUsageTail = class {
|
|
7122
|
+
constructor(file, sinceMs) {
|
|
7123
|
+
this.file = file;
|
|
7124
|
+
this.sinceMs = sinceMs;
|
|
7125
|
+
this.tail = new CodexRolloutLineTail(file);
|
|
7126
|
+
}
|
|
7127
|
+
tail;
|
|
7128
|
+
acc = { input: 0, cacheRead: 0, cacheWrite: 0, output: 0 };
|
|
7129
|
+
saw = false;
|
|
7130
|
+
lastModel;
|
|
7131
|
+
/** 截至目前读到的**最后一条** `turn_context` 模型(与终值的 {@link parseCodexModel} 同判据)。 */
|
|
7132
|
+
get model() {
|
|
7133
|
+
return this.lastModel;
|
|
7134
|
+
}
|
|
7135
|
+
/** 读新追加的行,返回当前窗内累计;还没有任何窗内轮次返回 undefined。读失败抛给调用方(调用方吞)。 */
|
|
7136
|
+
poll() {
|
|
7137
|
+
this.tail.read((line2) => {
|
|
7138
|
+
const model = readCodexTurnContextModel(line2);
|
|
7139
|
+
if (model) this.lastModel = model;
|
|
7140
|
+
const row = readCodexTokenCountLine(line2);
|
|
7141
|
+
if (!row?.turn || !Number.isFinite(row.ts) || row.ts < this.sinceMs) return;
|
|
7142
|
+
addCodexBuckets(this.acc, row.turn);
|
|
7143
|
+
this.saw = true;
|
|
7144
|
+
}, () => {
|
|
7145
|
+
this.saw = false;
|
|
7146
|
+
this.lastModel = void 0;
|
|
7147
|
+
this.acc.input = this.acc.cacheRead = this.acc.cacheWrite = this.acc.output = 0;
|
|
7148
|
+
});
|
|
7109
7149
|
return this.saw ? usageFromBuckets(this.acc) : void 0;
|
|
7110
7150
|
}
|
|
7111
7151
|
};
|
|
7152
|
+
CodexRolloutModelTail = class {
|
|
7153
|
+
constructor(file) {
|
|
7154
|
+
this.file = file;
|
|
7155
|
+
this.tail = new CodexRolloutLineTail(file);
|
|
7156
|
+
}
|
|
7157
|
+
tail;
|
|
7158
|
+
lastModel;
|
|
7159
|
+
/** 追读新行,返回截至目前**最后一条** `turn_context` 记的模型。读失败抛给调用方(调用方吞)。 */
|
|
7160
|
+
poll() {
|
|
7161
|
+
this.tail.read((line2) => {
|
|
7162
|
+
const model = readCodexTurnContextModel(line2);
|
|
7163
|
+
if (model) this.lastModel = model;
|
|
7164
|
+
}, () => {
|
|
7165
|
+
this.lastModel = void 0;
|
|
7166
|
+
});
|
|
7167
|
+
return this.lastModel;
|
|
7168
|
+
}
|
|
7169
|
+
};
|
|
7112
7170
|
CODEX_APP_SERVER_BLOCKED_FLAGS = /* @__PURE__ */ new Map([
|
|
7113
7171
|
["app-server", "boolean"],
|
|
7114
7172
|
["--stdio", "boolean"],
|
|
@@ -7169,7 +7227,12 @@ var init_codex = __esm({
|
|
|
7169
7227
|
// 只能从 resume 的响应里取(同上实测)。取不到它就读不到实际模型。
|
|
7170
7228
|
rolloutPath: void 0,
|
|
7171
7229
|
// thread/start / thread/resume 响应里 codex 报的线程模型:rollout 读不到时的兜底。
|
|
7172
|
-
threadModel: void 0
|
|
7230
|
+
threadModel: void 0,
|
|
7231
|
+
// 运行中检查点的模型来源(Bug 0227):增量追读会话卷里逐轮记的实际模型。卷路径就位后才建。
|
|
7232
|
+
rolloutModelTail: void 0,
|
|
7233
|
+
rolloutModel: void 0,
|
|
7234
|
+
// 上一次报出去的用量指纹:只在用量真的变了的那一拍才去碰磁盘(runningUsage 每条协议消息都被调到)。
|
|
7235
|
+
lastUsageKey: ""
|
|
7173
7236
|
}),
|
|
7174
7237
|
initialCommand: () => ({
|
|
7175
7238
|
jsonrpc: "2.0",
|
|
@@ -7311,14 +7374,14 @@ ${task}` : task;
|
|
|
7311
7374
|
},
|
|
7312
7375
|
runtimeSessionId: (state) => state.threadId,
|
|
7313
7376
|
exitExtras: (state) => {
|
|
7314
|
-
let
|
|
7377
|
+
let fromRollout;
|
|
7315
7378
|
if (state.rolloutPath) {
|
|
7316
7379
|
try {
|
|
7317
|
-
|
|
7380
|
+
fromRollout = parseCodexModel(fs8.readFileSync(state.rolloutPath, "utf8").split("\n"));
|
|
7318
7381
|
} catch {
|
|
7319
7382
|
}
|
|
7320
7383
|
}
|
|
7321
|
-
model
|
|
7384
|
+
const model = pickCodexModel({ rollout: fromRollout ?? state.rolloutModel, thread: state.threadModel, requested: resolvedModel2 });
|
|
7322
7385
|
const usage = codexRunUsage(state.usageByTurn, state.runTurnIds);
|
|
7323
7386
|
return {
|
|
7324
7387
|
...usage ? { usage } : {},
|
|
@@ -7331,7 +7394,18 @@ ${task}` : task;
|
|
|
7331
7394
|
runningUsage: (state) => {
|
|
7332
7395
|
const usage = codexRunUsage(state.usageByTurn, state.runTurnIds);
|
|
7333
7396
|
if (!usage) return void 0;
|
|
7334
|
-
const
|
|
7397
|
+
const key2 = JSON.stringify(usage);
|
|
7398
|
+
if (key2 !== state.lastUsageKey) {
|
|
7399
|
+
state.lastUsageKey = key2;
|
|
7400
|
+
if (state.rolloutPath) {
|
|
7401
|
+
state.rolloutModelTail ??= new CodexRolloutModelTail(state.rolloutPath);
|
|
7402
|
+
try {
|
|
7403
|
+
state.rolloutModel = state.rolloutModelTail.poll() ?? state.rolloutModel;
|
|
7404
|
+
} catch {
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
7407
|
+
}
|
|
7408
|
+
const model = pickCodexModel({ rollout: state.rolloutModel, thread: state.threadModel, requested: resolvedModel2 });
|
|
7335
7409
|
return { usage, ...model ? { model } : {} };
|
|
7336
7410
|
},
|
|
7337
7411
|
async append(input, state, send) {
|
|
@@ -7563,7 +7637,7 @@ ${task}` : task;
|
|
|
7563
7637
|
const key2 = usage ? JSON.stringify(usage) : "";
|
|
7564
7638
|
if (!usage || key2 === lastUsageKey) return;
|
|
7565
7639
|
lastUsageKey = key2;
|
|
7566
|
-
const model = job.model ?? this.opts.model;
|
|
7640
|
+
const model = pickCodexModel({ rollout: usageTail.model, requested: job.model ?? this.opts.model });
|
|
7567
7641
|
for (const cb of usageCheckpointCbs) cb({ usage, ...model ? { model } : {} });
|
|
7568
7642
|
} catch {
|
|
7569
7643
|
}
|
|
@@ -7615,7 +7689,7 @@ ${task}` : task;
|
|
|
7615
7689
|
clearTimeout(timer);
|
|
7616
7690
|
const resumeSessionId = job.resumeRuntimeSession ? job.runtimeSessionId : void 0;
|
|
7617
7691
|
const telemetry = scanCodexTelemetry(dir, startedAtMs, codexSessionsRoot(), job.runtimeSessionId);
|
|
7618
|
-
const model = telemetry.model
|
|
7692
|
+
const model = pickCodexModel({ rollout: telemetry.model, requested: job.model ?? this.opts.model });
|
|
7619
7693
|
const runtimeSessionId = telemetry.sessionId ?? resumeSessionId;
|
|
7620
7694
|
const sawStreamError = streamErrorMessages.length > 0;
|
|
7621
7695
|
const isError = !killReason && (code2 !== 0 || sawStreamError);
|
|
@@ -136709,7 +136783,7 @@ var init_bus = __esm({
|
|
|
136709
136783
|
notify: (ids2, payload2) => this.io.notify(ids2, payload2)
|
|
136710
136784
|
};
|
|
136711
136785
|
let ok3 = true;
|
|
136712
|
-
let
|
|
136786
|
+
let failureReason2 = null;
|
|
136713
136787
|
for (const h of handlers) {
|
|
136714
136788
|
const effectKey = `${h.name}#effect`;
|
|
136715
136789
|
if (record9.appliedAt && await this.store.transaction((tx) => tx.isHandlerDone(record9.seq, effectKey))) {
|
|
@@ -136732,7 +136806,7 @@ var init_bus = __esm({
|
|
|
136732
136806
|
await this.store.transaction((tx) => tx.deferEvent(record9.seq, "applied", lastErr, this.retryAt()));
|
|
136733
136807
|
return false;
|
|
136734
136808
|
}
|
|
136735
|
-
|
|
136809
|
+
failureReason2 = lastErr;
|
|
136736
136810
|
} else {
|
|
136737
136811
|
await this.store.transaction(async (tx) => {
|
|
136738
136812
|
if (buffered.length) await tx.appendEvents(buffered.map((b2) => ({
|
|
@@ -136751,7 +136825,7 @@ var init_bus = __esm({
|
|
|
136751
136825
|
});
|
|
136752
136826
|
}
|
|
136753
136827
|
}
|
|
136754
|
-
await this.store.transaction((tx) => tx.markEvent(record9.seq, "done",
|
|
136828
|
+
await this.store.transaction((tx) => tx.markEvent(record9.seq, "done", failureReason2, this.clock.now()));
|
|
136755
136829
|
return ok3;
|
|
136756
136830
|
}
|
|
136757
136831
|
/* ═══════════════════════ scan:唯一的调度入口 ═══════════════════════ */
|
|
@@ -142279,7 +142353,10 @@ var init_dispatcher = __esm({
|
|
|
142279
142353
|
* 画板只服务于「产界面」,评审/回复/收口回到能真正读写、能投票的通用运行时。
|
|
142280
142354
|
*/
|
|
142281
142355
|
async resolveBindingFor(spec) {
|
|
142282
|
-
const binding = await this.opts.resolveBinding?.(spec.actor
|
|
142356
|
+
const binding = await this.opts.resolveBinding?.(spec.actor, {
|
|
142357
|
+
...spec.artifactId ? { unitOfWork: spec.artifactId } : {},
|
|
142358
|
+
action: spec.action
|
|
142359
|
+
}) ?? void 0;
|
|
142283
142360
|
if (binding && spec.action !== "produce" && binding.runtimeKind === CANVAS_RUNTIME_KIND) {
|
|
142284
142361
|
return { ...binding, runtimeKind: NON_PRODUCE_FALLBACK_RUNTIME_KIND };
|
|
142285
142362
|
}
|
|
@@ -165111,9 +165188,24 @@ var init_live_chat = __esm({
|
|
|
165111
165188
|
}
|
|
165112
165189
|
if (turn.status === "running") {
|
|
165113
165190
|
turn.cancelCause = "user";
|
|
165114
|
-
|
|
165115
|
-
|
|
165116
|
-
|
|
165191
|
+
const sendKill = turn.handle.sendKill;
|
|
165192
|
+
if (typeof sendKill === "function") {
|
|
165193
|
+
let sent = false;
|
|
165194
|
+
try {
|
|
165195
|
+
sent = sendKill.call(turn.handle);
|
|
165196
|
+
} catch {
|
|
165197
|
+
}
|
|
165198
|
+
if (!sent) {
|
|
165199
|
+
try {
|
|
165200
|
+
turn.handle.cancelLocally?.();
|
|
165201
|
+
} catch {
|
|
165202
|
+
}
|
|
165203
|
+
}
|
|
165204
|
+
} else {
|
|
165205
|
+
try {
|
|
165206
|
+
turn.handle.kill();
|
|
165207
|
+
} catch {
|
|
165208
|
+
}
|
|
165117
165209
|
}
|
|
165118
165210
|
}
|
|
165119
165211
|
return { stopped: true };
|
|
@@ -213390,6 +213482,9 @@ ${composed}`;
|
|
|
213390
213482
|
kill: () => {
|
|
213391
213483
|
void session.kill?.();
|
|
213392
213484
|
},
|
|
213485
|
+
// Bug 0232:把「帧送没送出去 / 就地收尾」这对出口透到注册表,`/stop` 才判得了。
|
|
213486
|
+
// 条件透传不是省事:上层按「方法在不在」分流,本地 adapter 没有这条就该没有。
|
|
213487
|
+
...typeof session.sendKill === "function" ? { sendKill: () => session.sendKill(), cancelLocally: () => session.cancelLocally?.() ?? false } : {},
|
|
213393
213488
|
// ADR-0093 chat 道:把插入能力透到注册表,POST …/append 才有得调。
|
|
213394
213489
|
...typeof session.appendInput === "function" ? { appendInput: (input) => session.appendInput(input) } : {},
|
|
213395
213490
|
get canAppendInput() {
|
|
@@ -215320,6 +215415,7 @@ var init_memory_registry_store = __esm({
|
|
|
215320
215415
|
agentPrefs = /* @__PURE__ */ new Map();
|
|
215321
215416
|
teams = /* @__PURE__ */ new Map();
|
|
215322
215417
|
bindings = /* @__PURE__ */ new Map();
|
|
215418
|
+
runtimeCandidates = /* @__PURE__ */ new Map();
|
|
215323
215419
|
runtimeConfigs = /* @__PURE__ */ new Map();
|
|
215324
215420
|
runtimeModelOverrides = /* @__PURE__ */ new Map();
|
|
215325
215421
|
// key: ${runtimeId}::${modelId}
|
|
@@ -215421,6 +215517,28 @@ var init_memory_registry_store = __esm({
|
|
|
215421
215517
|
async listBindings() {
|
|
215422
215518
|
return [...this.bindings.values()].map((b2) => ({ ...b2 }));
|
|
215423
215519
|
}
|
|
215520
|
+
/* 候选 runtime(ADR 0616):与 PG 实现同一套语义——按 ord 升序读、整份替换、同 (node,kind) 去重。 */
|
|
215521
|
+
async listRuntimeCandidates(actorId) {
|
|
215522
|
+
return (this.runtimeCandidates.get(actorId) ?? []).map((c) => ({ ...c }));
|
|
215523
|
+
}
|
|
215524
|
+
async replaceRuntimeCandidates(actorId, candidates) {
|
|
215525
|
+
const seen = /* @__PURE__ */ new Set();
|
|
215526
|
+
const rows = [];
|
|
215527
|
+
for (const c of candidates) {
|
|
215528
|
+
const key2 = `${c.nodeId}::${c.runtimeKind}`;
|
|
215529
|
+
const model = typeof c.model === "string" && c.model.trim() ? c.model.trim() : null;
|
|
215530
|
+
const existing = rows.findIndex((r) => `${r.nodeId}::${r.runtimeKind}` === key2);
|
|
215531
|
+
if (existing >= 0) {
|
|
215532
|
+
rows[existing] = { ...rows[existing], model };
|
|
215533
|
+
continue;
|
|
215534
|
+
}
|
|
215535
|
+
seen.add(key2);
|
|
215536
|
+
rows.push({ actorId, nodeId: c.nodeId, runtimeKind: c.runtimeKind, model, ord: rows.length });
|
|
215537
|
+
}
|
|
215538
|
+
if (rows.length === 0) this.runtimeCandidates.delete(actorId);
|
|
215539
|
+
else this.runtimeCandidates.set(actorId, rows);
|
|
215540
|
+
return rows.map((c) => ({ ...c }));
|
|
215541
|
+
}
|
|
215424
215542
|
async upsertRuntimeConfig(c) {
|
|
215425
215543
|
this.runtimeConfigs.set(c.runtimeId, { ...c });
|
|
215426
215544
|
}
|
|
@@ -216136,7 +216254,9 @@ function systemHandoffFor(run, checkpoint = null, effects2 = []) {
|
|
|
216136
216254
|
// 与 PG 触发器同口径(2026-08-18):risks / verificationGaps 只装业务语义的东西。会话怎么死的
|
|
216137
216255
|
// 已在 summary 的 reason= 段里,不再冒充成「交付物的风险」;「没有断点」也不是「验证缺口」。
|
|
216138
216256
|
risks: pendingEffects || failedEffects ? [`external effects: pending=${pendingEffects}, failed=${failedEffects}`] : [],
|
|
216139
|
-
nextActions
|
|
216257
|
+
// 与 PG 触发器同口径(2026-09-23):nextActions 是「下游怎么用这份成果」,不拿 pendingSteps
|
|
216258
|
+
//(执行人自己没做完的活)兜底——兜底会让下游交接出现 `[next] 提交并结案` 这种假的「上游建议」。
|
|
216259
|
+
nextActions: [],
|
|
216140
216260
|
verificationGaps: [],
|
|
216141
216261
|
userHandoff: null,
|
|
216142
216262
|
createdAt: run.endedAt ?? run.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -222577,6 +222697,20 @@ var init_company_scoped_registry = __esm({
|
|
|
222577
222697
|
async getBinding(actorId) {
|
|
222578
222698
|
return await this.assertScoped(actorId, "getBinding") ? this.base.getBinding(actorId) : null;
|
|
222579
222699
|
}
|
|
222700
|
+
/* 候选 runtime(ADR 0616):与 binding 同一道租户闸——越界读回空、越界写抛。
|
|
222701
|
+
|
|
222702
|
+
刻意**不**按 `this.base` 有没有这两个方法来决定自己有没有(那样得写成类字段,而类字段的初始化
|
|
222703
|
+
早于构造参数属性赋值,`this.base` 在那一刻还是 undefined)。底座没实现时:读回空、写抛一句
|
|
222704
|
+
指名道姓的错——比静默丢掉一份候选清单强。 */
|
|
222705
|
+
async listRuntimeCandidates(actorId) {
|
|
222706
|
+
if (!this.base.listRuntimeCandidates) return [];
|
|
222707
|
+
return await this.assertScoped(actorId, "listRuntimeCandidates") ? this.base.listRuntimeCandidates(actorId) : [];
|
|
222708
|
+
}
|
|
222709
|
+
async replaceRuntimeCandidates(actorId, candidates) {
|
|
222710
|
+
await this.requireScoped(actorId, "replaceRuntimeCandidates");
|
|
222711
|
+
if (!this.base.replaceRuntimeCandidates) throw new Error("\u5F53\u524D\u767B\u8BB0\u5B58\u50A8\u5B9E\u73B0\u4E0D\u652F\u6301\u5019\u9009\u8FD0\u884C\u65F6\uFF08replaceRuntimeCandidates \u672A\u5B9E\u73B0\uFF09");
|
|
222712
|
+
return this.base.replaceRuntimeCandidates(actorId, candidates);
|
|
222713
|
+
}
|
|
222580
222714
|
async listBindings() {
|
|
222581
222715
|
const [all2, ids2] = await Promise.all([this.base.listBindings(), this.scopeIds()]);
|
|
222582
222716
|
const actors = [];
|
|
@@ -224789,9 +224923,15 @@ function createUsageLedgerResolver(deps) {
|
|
|
224789
224923
|
return chatCid ? { runId: stable, store: await deps.storeFor(chatCid) } : null;
|
|
224790
224924
|
};
|
|
224791
224925
|
}
|
|
224926
|
+
function hasModelName(model) {
|
|
224927
|
+
return typeof model === "string" && model.trim().length > 0;
|
|
224928
|
+
}
|
|
224929
|
+
function frameTokenTotal(usage) {
|
|
224930
|
+
return (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) + (usage.cacheReadTokens ?? 0) + (usage.cacheCreationTokens ?? 0);
|
|
224931
|
+
}
|
|
224792
224932
|
async function backfillRunModel(target, run, model, warn) {
|
|
224793
|
-
if (!model
|
|
224794
|
-
if (run.effectiveModel
|
|
224933
|
+
if (!hasModelName(model)) return;
|
|
224934
|
+
if (hasModelName(run.effectiveModel)) return;
|
|
224795
224935
|
try {
|
|
224796
224936
|
await target.store.updateRun(target.runId, { effectiveModel: model, modelSource: "runtime" });
|
|
224797
224937
|
} catch (err) {
|
|
@@ -224808,6 +224948,13 @@ function createUsageLedgerFrameHandler(deps) {
|
|
|
224808
224948
|
settledDispatch.add(dispatchId);
|
|
224809
224949
|
if (settledDispatch.size > 1e4) settledDispatch.delete(settledDispatch.values().next().value);
|
|
224810
224950
|
};
|
|
224951
|
+
const modelless = /* @__PURE__ */ new Set();
|
|
224952
|
+
const notedModelless = (runId) => {
|
|
224953
|
+
if (modelless.has(runId)) return true;
|
|
224954
|
+
modelless.add(runId);
|
|
224955
|
+
if (modelless.size > 1e4) modelless.delete(modelless.values().next().value);
|
|
224956
|
+
return false;
|
|
224957
|
+
};
|
|
224811
224958
|
return async (msg) => {
|
|
224812
224959
|
if (!RUNTIME_START_FRAME_TYPES.has(msg.type)) return;
|
|
224813
224960
|
const dispatchId = msg.dispatchId;
|
|
@@ -224850,6 +224997,12 @@ function createUsageLedgerFrameHandler(deps) {
|
|
|
224850
224997
|
warn(`[usage-ledger] session_usage dispatch=${dispatchId} usageSeq=${String(msg.usageSeq)} \u975E\u6CD5\uFF0C\u4E22\u5F03`);
|
|
224851
224998
|
return;
|
|
224852
224999
|
}
|
|
225000
|
+
if (!hasModelName(msg.model) && !hasModelName(run.effectiveModel)) {
|
|
225001
|
+
const tokens2 = frameTokenTotal(msg.usage);
|
|
225002
|
+
if (tokens2 > 0 && !notedModelless(target.runId)) {
|
|
225003
|
+
warn(`[usage-ledger] session_usage dispatch=${dispatchId} \u2192 run ${target.runId} \u5E26\u7740 ${tokens2} \u4E2A token \u5374\u6CA1\u6709\u6A21\u578B\u540D\uFF08run \u884C\u4E5F\u662F\u7A7A\u7684\uFF09\uFF0C\u8FD9\u6BB5\u7528\u91CF\u53EA\u80FD\u5F52\u8FDB\u300C\u672A\u77E5\u6A21\u578B\u300D`);
|
|
225004
|
+
}
|
|
225005
|
+
}
|
|
224853
225006
|
await target.store.recordUsagePoint({
|
|
224854
225007
|
runId: target.runId,
|
|
224855
225008
|
seq: msg.usageSeq,
|
|
@@ -225722,6 +225875,8 @@ function wireRecoveredChatTurn(deps) {
|
|
|
225722
225875
|
kill: () => {
|
|
225723
225876
|
void handle.kill();
|
|
225724
225877
|
},
|
|
225878
|
+
// Bug 0232:重挂的轮一样会被人点停止、一样可能撞上节点够不着——同一对出口原样透传。
|
|
225879
|
+
...typeof handle.sendKill === "function" ? { sendKill: () => handle.sendKill(), cancelLocally: () => handle.cancelLocally?.() ?? false } : {},
|
|
225725
225880
|
// ADR-0093 chat 道:重挂的轮同样能被插入——否则「serve 重启过」会变成一条人看不见的能力差异。
|
|
225726
225881
|
// **整份透传**(2026-09-17):此前只转 `text`,插话链路要的消费回执(`requireConsumptionReceipt`)、
|
|
225727
225882
|
// 幂等键(`clientMessageKey`)与附件(`files` / `binaryFiles`)全被剥掉——runtime 照样读到了正文,
|
|
@@ -228318,6 +228473,32 @@ var init_service5 = __esm({
|
|
|
228318
228473
|
listBindings() {
|
|
228319
228474
|
return this.opts.store.listBindings();
|
|
228320
228475
|
}
|
|
228476
|
+
/* ---------- 候选 runtime(ADR 0616) ---------- */
|
|
228477
|
+
/** 按 `ord` 升序。底座没实现(旧存储实现)→ 空清单,等于「只有主绑定」。 */
|
|
228478
|
+
listRuntimeCandidates(actorId) {
|
|
228479
|
+
return this.opts.store.listRuntimeCandidates?.(actorId) ?? Promise.resolve([]);
|
|
228480
|
+
}
|
|
228481
|
+
/**
|
|
228482
|
+
* 整份替换候选清单(加 / 删 / 排序在界面上是同一个动作)。返回写后的整份。
|
|
228483
|
+
*
|
|
228484
|
+
* 校验放在这里而不是路由:候选是「派到哪」的事实源,写进去的 `(nodeId, runtimeKind)`
|
|
228485
|
+
* 必须真的存在于这家公司的名册上——否则分配那一侧只会在几个月后的某次主机故障里,
|
|
228486
|
+
* 报一句「节点不存在」然后把活挂住,而错是在写入那一刻犯的。
|
|
228487
|
+
*/
|
|
228488
|
+
async replaceRuntimeCandidates(actorId, candidates, by) {
|
|
228489
|
+
if (!this.opts.store.replaceRuntimeCandidates) {
|
|
228490
|
+
throw new Error("\u5F53\u524D\u767B\u8BB0\u5B58\u50A8\u5B9E\u73B0\u4E0D\u652F\u6301\u5019\u9009\u8FD0\u884C\u65F6");
|
|
228491
|
+
}
|
|
228492
|
+
const saved = await this.opts.store.replaceRuntimeCandidates(actorId, candidates);
|
|
228493
|
+
await this.audit({
|
|
228494
|
+
kind: "binding_change",
|
|
228495
|
+
actorId,
|
|
228496
|
+
by,
|
|
228497
|
+
at: this.now(),
|
|
228498
|
+
detail: { candidates: saved.map((c) => ({ nodeId: c.nodeId, runtimeKind: c.runtimeKind, model: c.model ?? null })) }
|
|
228499
|
+
});
|
|
228500
|
+
return saved;
|
|
228501
|
+
}
|
|
228321
228502
|
/** 派单接缝的真实现:替换第零步假实现,第三部分代码零改动(决策 0001) */
|
|
228322
228503
|
resolveBinding() {
|
|
228323
228504
|
return async (actorId) => {
|
|
@@ -230767,6 +230948,42 @@ function actorsDomain(opts) {
|
|
|
230767
230948
|
);
|
|
230768
230949
|
return { status: 201, body: cfg };
|
|
230769
230950
|
});
|
|
230951
|
+
router.get("/api/actors/:id/runtime-candidates", async (req) => {
|
|
230952
|
+
const { service } = await resolveCtx(req.auth.companyId);
|
|
230953
|
+
return { status: 200, body: { items: await service.listRuntimeCandidates(req.params.id) } };
|
|
230954
|
+
});
|
|
230955
|
+
router.put("/api/actors/:id/runtime-candidates", async (req) => {
|
|
230956
|
+
const { service } = await resolveCtx(req.auth.companyId);
|
|
230957
|
+
const actorId = req.params.id;
|
|
230958
|
+
if (!await service.getActor(actorId)) throw new ApiError(404, "NOT_FOUND", "\u6CA1\u6709\u8FD9\u4E2A\u5458\u5DE5");
|
|
230959
|
+
const raw = req.body?.candidates;
|
|
230960
|
+
if (!Array.isArray(raw)) throw new ApiError(400, "BAD_REQUEST", "candidates \u5FC5\u987B\u662F\u6570\u7EC4");
|
|
230961
|
+
if (raw.length > 20) throw new ApiError(400, "BAD_REQUEST", "\u5019\u9009\u8FD0\u884C\u65F6\u6700\u591A 20 \u6761");
|
|
230962
|
+
const candidates = [];
|
|
230963
|
+
for (const item of raw) {
|
|
230964
|
+
const c = item;
|
|
230965
|
+
if (typeof c.nodeId !== "string" || !c.nodeId.trim()) throw new ApiError(400, "BAD_REQUEST", "\u6BCF\u6761\u5019\u9009\u90FD\u8981\u6709 nodeId");
|
|
230966
|
+
if (typeof c.runtimeKind !== "string" || !c.runtimeKind.trim()) throw new ApiError(400, "BAD_REQUEST", "\u6BCF\u6761\u5019\u9009\u90FD\u8981\u6709 runtimeKind");
|
|
230967
|
+
if (c.model !== void 0 && c.model !== null && typeof c.model !== "string") {
|
|
230968
|
+
throw new ApiError(400, "BAD_REQUEST", "model \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6216 null\uFF08null = \u8DDF\u968F\u8BE5 runtime \u9ED8\u8BA4\uFF09");
|
|
230969
|
+
}
|
|
230970
|
+
candidates.push({
|
|
230971
|
+
nodeId: c.nodeId.trim(),
|
|
230972
|
+
runtimeKind: c.runtimeKind.trim(),
|
|
230973
|
+
...c.model === void 0 ? {} : { model: typeof c.model === "string" ? c.model : null }
|
|
230974
|
+
});
|
|
230975
|
+
}
|
|
230976
|
+
if (opts.resolveNodeTenancy) {
|
|
230977
|
+
for (const c of candidates) {
|
|
230978
|
+
const facts = await opts.resolveNodeTenancy(c.nodeId).catch(() => null) ?? null;
|
|
230979
|
+
const foreign = foreignNodeBlock(facts, req.auth.companyId);
|
|
230980
|
+
if (foreign) throw new ApiError(400, "NODE_NOT_IN_COMPANY", foreign);
|
|
230981
|
+
if (!facts) throw new ApiError(400, "NODE_NOT_FOUND", `\u627E\u4E0D\u5230\u8282\u70B9 ${c.nodeId}`);
|
|
230982
|
+
}
|
|
230983
|
+
}
|
|
230984
|
+
const items = await service.replaceRuntimeCandidates(actorId, candidates, req.auth.actor);
|
|
230985
|
+
return { status: 200, body: { items } };
|
|
230986
|
+
});
|
|
230770
230987
|
router.get("/api/actors/:id/config/versions", async (req) => {
|
|
230771
230988
|
const { service } = await resolveCtx(req.auth.companyId);
|
|
230772
230989
|
return { status: 200, body: { items: await service.listConfigVersions(req.params.id) } };
|
|
@@ -242816,7 +243033,7 @@ var init_runtime_activity = __esm({
|
|
|
242816
243033
|
|
|
242817
243034
|
// ../server/src/node-health.ts
|
|
242818
243035
|
function deriveNodeHealth(input) {
|
|
242819
|
-
const staleMs = input.staleMs ??
|
|
243036
|
+
const staleMs = input.staleMs ?? NODE_HEARTBEAT_STALE_MS;
|
|
242820
243037
|
const maxFail = input.maxConsecutiveFailures ?? 3;
|
|
242821
243038
|
const cf = input.snapshot?.consecutiveFailures ?? 0;
|
|
242822
243039
|
const heartbeatFresh = input.lastHeartbeatMs !== void 0 && input.now - input.lastHeartbeatMs < staleMs;
|
|
@@ -242875,7 +243092,7 @@ function describeNodeFault(f2, now = Date.now()) {
|
|
|
242875
243092
|
`\u5728\u90A3\u4E4B\u524D\uFF0C\u91CD\u8BD5\u3001\u6362\u4EBA\u3001\u6539\u5185\u5BB9\u90FD\u4E0D\u4F1A\u8BA9\u5B83\u53D8\u597D\u3002`
|
|
242876
243093
|
].join("");
|
|
242877
243094
|
}
|
|
242878
|
-
var NodeHealthTracker;
|
|
243095
|
+
var NodeHealthTracker, NODE_HEARTBEAT_STALE_MS;
|
|
242879
243096
|
var init_node_health = __esm({
|
|
242880
243097
|
"../server/src/node-health.ts"() {
|
|
242881
243098
|
"use strict";
|
|
@@ -242917,6 +243134,7 @@ var init_node_health = __esm({
|
|
|
242917
243134
|
this.onChange?.();
|
|
242918
243135
|
}
|
|
242919
243136
|
};
|
|
243137
|
+
NODE_HEARTBEAT_STALE_MS = 9e4;
|
|
242920
243138
|
}
|
|
242921
243139
|
});
|
|
242922
243140
|
|
|
@@ -242952,6 +243170,15 @@ async function resolveRuntimeModels(deps, runtimeId) {
|
|
|
242952
243170
|
}
|
|
242953
243171
|
function nodesDomain(deps) {
|
|
242954
243172
|
const quotaByNode = /* @__PURE__ */ new Map();
|
|
243173
|
+
void deps.nodeStore.listRuntimeQuotas().then((rows) => {
|
|
243174
|
+
for (const row of rows) {
|
|
243175
|
+
if (row.runtimeKind !== "codex" && row.runtimeKind !== "claude-code") continue;
|
|
243176
|
+
const list2 = quotaByNode.get(row.nodeId) ?? [];
|
|
243177
|
+
list2.push({ kind: row.runtimeKind, status: row.status, checkedAt: row.checkedAt, windows: row.windows, ...row.retryAt ? { retryAt: row.retryAt } : {} });
|
|
243178
|
+
quotaByNode.set(row.nodeId, list2);
|
|
243179
|
+
}
|
|
243180
|
+
if (rows.length > 0) console.log(`[nodes] \u989D\u5EA6\u6C34\u5408\uFF1A\u4ECE\u5E93\u91CC\u8BFB\u56DE ${rows.length} \u6761\u8FD0\u884C\u65F6\u989D\u5EA6`);
|
|
243181
|
+
}).catch((err) => console.warn(`[nodes] \u989D\u5EA6\u6C34\u5408\u5931\u8D25\uFF08\u7167\u65E7\u7B49\u4E0B\u4E00\u6B21\u91C7\u96C6\uFF09\uFF1A${err instanceof Error ? err.message : String(err)}`));
|
|
242955
243182
|
const UPDATE_TIMEOUT_MS = 15e4;
|
|
242956
243183
|
const nodeUpdates = /* @__PURE__ */ new Map();
|
|
242957
243184
|
function runtimeInfoOf(r) {
|
|
@@ -243052,7 +243279,9 @@ function nodesDomain(deps) {
|
|
|
243052
243279
|
if (msg.type !== "runtime_quotas") return;
|
|
243053
243280
|
const daemon = hub.connectedDaemons().find((d) => d.daemonId === daemonId);
|
|
243054
243281
|
if (!daemon) return;
|
|
243055
|
-
|
|
243282
|
+
const quotas = sanitizeRuntimeQuotas(msg.quotas).filter((q2) => daemon.meta.adapters.includes(q2.kind));
|
|
243283
|
+
quotaByNode.set(daemonId, quotas);
|
|
243284
|
+
void nodeStore.saveRuntimeQuotas(daemonId, quotas.map((q2) => ({ ...q2, nodeId: daemonId, runtimeKind: q2.kind }))).catch((err) => console.warn(`[nodes] \u989D\u5EA6\u843D\u5E93\u5931\u8D25(node=${daemonId})\uFF1A${err instanceof Error ? err.message : String(err)}`));
|
|
243056
243285
|
});
|
|
243057
243286
|
hub.addMessageListener(async (daemonId, msg) => {
|
|
243058
243287
|
if (msg.type !== "hello") return;
|
|
@@ -243204,6 +243433,10 @@ function nodesDomain(deps) {
|
|
|
243204
243433
|
...sourceIpById.get(n.id) ? { sourceIp: sourceIpById.get(n.id) } : {},
|
|
243205
243434
|
...n.name ? { name: n.name } : {},
|
|
243206
243435
|
...updateStatus ? { updateStatus } : {},
|
|
243436
|
+
/* 并发上限与当前在跑数(ADR 0616 D2):`maxConcurrent` 只在真设过时带这一格——
|
|
243437
|
+
缺省就是「未设置 = 不限制」,带一个 null 进去会让前端不得不再判一次。 */
|
|
243438
|
+
...n.maxConcurrent != null ? { maxConcurrent: n.maxConcurrent } : {},
|
|
243439
|
+
activeRunCount,
|
|
243207
243440
|
...lastDisconnectedAt || lastReconnectedAt ? {
|
|
243208
243441
|
connection: {
|
|
243209
243442
|
...lastDisconnectedAt ? { lastDisconnectedAt } : {},
|
|
@@ -243403,6 +243636,24 @@ function nodesDomain(deps) {
|
|
|
243403
243636
|
deps.onStatusChange?.();
|
|
243404
243637
|
return { status: 200, body: { ok: true, name } };
|
|
243405
243638
|
});
|
|
243639
|
+
router.put("/api/nodes/:nodeId/max-concurrent", async (req) => {
|
|
243640
|
+
const nodeId = req.params["nodeId"];
|
|
243641
|
+
if (!nodeId) return { status: 400, body: { error: "missing nodeId" } };
|
|
243642
|
+
const forbidden = await forbidUnlessOrgManager(req, "\u8BBE\u7F6E\u8282\u70B9\u5E76\u53D1\u4E0A\u9650");
|
|
243643
|
+
if (forbidden) return forbidden;
|
|
243644
|
+
const scope2 = req.auth.companyId !== void 0 ? { companyId: req.auth.companyId } : void 0;
|
|
243645
|
+
const node2 = (await deps.nodeStore.listNodes(scope2)).find((n) => n.id === nodeId);
|
|
243646
|
+
if (!node2) return { status: 404, body: { error: "NODE_NOT_FOUND", message: "\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u7EC4\u7EC7" } };
|
|
243647
|
+
const raw = req.body ?? {};
|
|
243648
|
+
const value2 = raw.maxConcurrent;
|
|
243649
|
+
if (value2 !== null && value2 !== void 0 && !(Number.isSafeInteger(value2) && value2 >= 0 && value2 <= 1e3)) {
|
|
243650
|
+
return { status: 400, body: { error: "BAD_REQUEST", message: "maxConcurrent \u5FC5\u987B\u662F 0\u20131000 \u7684\u6574\u6570\uFF0C\u6216 null\uFF08\u4E0D\u9650\u5236\uFF09" } };
|
|
243651
|
+
}
|
|
243652
|
+
const limit = value2 === void 0 || value2 === null ? null : value2;
|
|
243653
|
+
await deps.nodeStore.setNodeMaxConcurrent(nodeId, limit);
|
|
243654
|
+
deps.onStatusChange?.();
|
|
243655
|
+
return { status: 200, body: { ok: true, maxConcurrent: limit } };
|
|
243656
|
+
});
|
|
243406
243657
|
router.delete("/api/nodes/:nodeId", async (req) => {
|
|
243407
243658
|
const nodeId = req.params["nodeId"];
|
|
243408
243659
|
if (!nodeId) return { status: 400, body: { error: "missing nodeId" } };
|
|
@@ -243624,6 +243875,353 @@ var init_runtime_drift = __esm({
|
|
|
243624
243875
|
}
|
|
243625
243876
|
});
|
|
243626
243877
|
|
|
243878
|
+
// ../server/src/domains/runtime-allocation/allocate.ts
|
|
243879
|
+
function runtimeTargetKey(target) {
|
|
243880
|
+
return `${target.nodeId}::${target.runtimeKind}`;
|
|
243881
|
+
}
|
|
243882
|
+
function idlenessOf(node2) {
|
|
243883
|
+
const limit = node2.maxConcurrent;
|
|
243884
|
+
if (limit === null || limit === void 0) return 1;
|
|
243885
|
+
if (limit <= 0) return 0;
|
|
243886
|
+
return Math.max(0, 1 - node2.activeRunCount / limit);
|
|
243887
|
+
}
|
|
243888
|
+
function atCapacity(node2) {
|
|
243889
|
+
const limit = node2.maxConcurrent;
|
|
243890
|
+
if (limit === null || limit === void 0) return false;
|
|
243891
|
+
return node2.activeRunCount >= limit;
|
|
243892
|
+
}
|
|
243893
|
+
function quotaDerateFactor(quota) {
|
|
243894
|
+
if (!quota || quota.status !== "ok" || quota.windows.length === 0) return { factor: 1 };
|
|
243895
|
+
const worst = Math.max(...quota.windows.map((w2) => w2.usedPercent));
|
|
243896
|
+
if (worst <= RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT) return { factor: 1, worstUsedPercent: worst };
|
|
243897
|
+
const remaining = Math.max(0, 100 - worst);
|
|
243898
|
+
return { factor: remaining / (100 - RUNTIME_QUOTA_DERATE_THRESHOLD_PERCENT), worstUsedPercent: worst };
|
|
243899
|
+
}
|
|
243900
|
+
function faultFromQuota(target, quota, now) {
|
|
243901
|
+
if (!quota) return null;
|
|
243902
|
+
const base = { nodeId: target.nodeId, runtimeKind: target.runtimeKind, markedAt: quota.checkedAt, attempts: 0 };
|
|
243903
|
+
if (quota.status === "unauthenticated") {
|
|
243904
|
+
return { ...base, reason: "unauthenticated", awaitingManual: true, detail: "\u8FD0\u884C\u65F6\u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u9700\u8981\u5728\u8BE5\u673A\u5668\u4E0A\u91CD\u65B0\u767B\u5F55" };
|
|
243905
|
+
}
|
|
243906
|
+
if (quota.status === "rate_limited") {
|
|
243907
|
+
return {
|
|
243908
|
+
...base,
|
|
243909
|
+
reason: "quota-exhausted",
|
|
243910
|
+
awaitingManual: false,
|
|
243911
|
+
...quota.retryAt ? { nextProbeAt: quota.retryAt } : {},
|
|
243912
|
+
detail: "provider \u62A5\u9650\u6D41/\u989D\u5EA6\u8017\u5C3D"
|
|
243913
|
+
};
|
|
243914
|
+
}
|
|
243915
|
+
if (quota.status !== "ok") return null;
|
|
243916
|
+
const exhausted = quota.windows.filter((w2) => w2.usedPercent >= 100);
|
|
243917
|
+
if (exhausted.length === 0) return null;
|
|
243918
|
+
const resets = exhausted.map((w2) => w2.resetsAt).filter((v2) => Boolean(v2)).sort();
|
|
243919
|
+
const latest = resets.at(-1);
|
|
243920
|
+
return {
|
|
243921
|
+
...base,
|
|
243922
|
+
reason: "quota-exhausted",
|
|
243923
|
+
awaitingManual: false,
|
|
243924
|
+
...latest && Date.parse(latest) > now ? { nextProbeAt: latest } : {},
|
|
243925
|
+
detail: `\u989D\u5EA6\u5DF2\u7528\u6EE1\uFF08${exhausted.map((w2) => `${w2.bucket} ${Math.round(w2.usedPercent)}%`).join("\u3001")}\uFF09`
|
|
243926
|
+
};
|
|
243927
|
+
}
|
|
243928
|
+
function faultIsBlocking(fault, now) {
|
|
243929
|
+
if (fault.awaitingManual) return true;
|
|
243930
|
+
if (!fault.nextProbeAt) return true;
|
|
243931
|
+
return Date.parse(fault.nextProbeAt) > now;
|
|
243932
|
+
}
|
|
243933
|
+
function failureReason(rejections) {
|
|
243934
|
+
if (rejections.length === 0) return "no-candidates";
|
|
243935
|
+
const causes = new Set(rejections.map((r) => r.cause));
|
|
243936
|
+
if (causes.size === 1) {
|
|
243937
|
+
const only = [...causes][0];
|
|
243938
|
+
if (only === "at-capacity") return "all-at-capacity";
|
|
243939
|
+
if (only === "already-tried") return "all-tried";
|
|
243940
|
+
return "all-faulted";
|
|
243941
|
+
}
|
|
243942
|
+
const faultLike = /* @__PURE__ */ new Set(["node-unknown", "node-offline", "runtime-missing", "faulted"]);
|
|
243943
|
+
if ([...causes].every((c) => faultLike.has(c))) return "all-faulted";
|
|
243944
|
+
return "mixed";
|
|
243945
|
+
}
|
|
243946
|
+
function allocateRuntime(input, now = Date.now()) {
|
|
243947
|
+
const rejections = [];
|
|
243948
|
+
const eligible = [];
|
|
243949
|
+
for (const candidate of input.candidates) {
|
|
243950
|
+
const key2 = runtimeTargetKey(candidate);
|
|
243951
|
+
const node2 = input.nodes.get(candidate.nodeId);
|
|
243952
|
+
const nodeName = node2?.displayName?.trim() || candidate.nodeId;
|
|
243953
|
+
const reject = (cause, detail) => rejections.push({ nodeId: candidate.nodeId, runtimeKind: candidate.runtimeKind, nodeName, cause, ...detail ? { detail } : {} });
|
|
243954
|
+
if (input.alreadyTried?.has(key2)) {
|
|
243955
|
+
reject("already-tried", input.triedDetail?.get(key2) ?? "\u672C\u6B21\u5DF2\u8BD5\u8FC7\u5E76\u5931\u8D25");
|
|
243956
|
+
continue;
|
|
243957
|
+
}
|
|
243958
|
+
if (!node2) {
|
|
243959
|
+
reject("node-unknown", "\u8282\u70B9\u4E0D\u5B58\u5728\u3001\u5DF2\u5220\u9664\uFF0C\u6216\u4E0D\u5C5E\u4E8E\u5F53\u524D\u516C\u53F8");
|
|
243960
|
+
continue;
|
|
243961
|
+
}
|
|
243962
|
+
if (!node2.online) {
|
|
243963
|
+
reject("node-offline", "\u8282\u70B9\u5F53\u524D\u79BB\u7EBF");
|
|
243964
|
+
continue;
|
|
243965
|
+
}
|
|
243966
|
+
if (!node2.runtimeKinds.includes(candidate.runtimeKind)) {
|
|
243967
|
+
reject("runtime-missing", "\u8BE5\u673A\u5668\u4E0A\u6CA1\u6709\u8FD9\u4E2A\u8FD0\u884C\u65F6\uFF08\u672A\u88C5\u597D\u3001\u5DF2\u5378\u8F7D\u6216\u672A\u4E0A\u62A5\uFF09");
|
|
243968
|
+
continue;
|
|
243969
|
+
}
|
|
243970
|
+
const fault = input.faults?.get(key2) ?? faultFromQuota(candidate, input.quotas?.get(key2), now);
|
|
243971
|
+
if (fault && faultIsBlocking(fault, now)) {
|
|
243972
|
+
reject("faulted", `${fault.reason}${fault.detail ? `\uFF1A${fault.detail}` : ""}${fault.awaitingManual ? "\uFF08\u7B49\u4EBA\u5DE5\u6062\u590D\uFF09" : ""}`);
|
|
243973
|
+
continue;
|
|
243974
|
+
}
|
|
243975
|
+
if (atCapacity(node2)) {
|
|
243976
|
+
reject("at-capacity", `\u5728\u8DD1 ${node2.activeRunCount} / \u4E0A\u9650 ${node2.maxConcurrent}`);
|
|
243977
|
+
continue;
|
|
243978
|
+
}
|
|
243979
|
+
eligible.push(candidate);
|
|
243980
|
+
}
|
|
243981
|
+
if (eligible.length === 0) return { ok: false, reason: failureReason(rejections), rejections };
|
|
243982
|
+
const primary = eligible.find((c) => c.primary);
|
|
243983
|
+
const rank = (candidate) => {
|
|
243984
|
+
const node2 = input.nodes.get(candidate.nodeId);
|
|
243985
|
+
const idleness = idlenessOf(node2);
|
|
243986
|
+
const { factor, worstUsedPercent } = quotaDerateFactor(input.quotas?.get(runtimeTargetKey(candidate)));
|
|
243987
|
+
return {
|
|
243988
|
+
candidate,
|
|
243989
|
+
nodeName: node2.displayName?.trim() || candidate.nodeId,
|
|
243990
|
+
idleness,
|
|
243991
|
+
quotaFactor: factor,
|
|
243992
|
+
weight: idleness * factor,
|
|
243993
|
+
...worstUsedPercent !== void 0 ? { worstQuotaUsedPercent: worstUsedPercent } : {}
|
|
243994
|
+
};
|
|
243995
|
+
};
|
|
243996
|
+
if (primary) {
|
|
243997
|
+
const ranked2 = [rank(primary)];
|
|
243998
|
+
return { ok: true, chosen: primary, nodeName: ranked2[0].nodeName, source: "primary", ranked: ranked2, rejections };
|
|
243999
|
+
}
|
|
244000
|
+
const ranked = eligible.map(rank).sort((a, b2) => b2.weight - a.weight || a.candidate.ord - b2.candidate.ord || a.candidate.nodeId.localeCompare(b2.candidate.nodeId) || a.candidate.runtimeKind.localeCompare(b2.candidate.runtimeKind));
|
|
244001
|
+
const top = ranked[0];
|
|
244002
|
+
return { ok: true, chosen: top.candidate, nodeName: top.nodeName, source: "candidate", ranked, rejections };
|
|
244003
|
+
}
|
|
244004
|
+
function canRetryAllocation(attempt) {
|
|
244005
|
+
return attempt < RUNTIME_ALLOCATION_MAX_ATTEMPTS;
|
|
244006
|
+
}
|
|
244007
|
+
function describeAllocationChoice(result) {
|
|
244008
|
+
const head = result.source === "primary" ? `\u9009\u4E2D\u4E3B\u8FD0\u884C\u65F6 ${result.nodeName} \xB7 ${result.chosen.runtimeKind}` : `\u4E3B\u8FD0\u884C\u65F6\u4E0D\u53EF\u7528\uFF0C\u6309\u6743\u91CD\u9009\u4E2D ${result.nodeName} \xB7 ${result.chosen.runtimeKind}`;
|
|
244009
|
+
const detail = result.ranked.map((r) => {
|
|
244010
|
+
const quota = r.worstQuotaUsedPercent === void 0 ? "\u65E0\u989D\u5EA6\u6570\u636E" : `\u989D\u5EA6\u5DF2\u7528 ${Math.round(r.worstQuotaUsedPercent)}%`;
|
|
244011
|
+
return `${r.nodeName}\xB7${r.candidate.runtimeKind} \u6743\u91CD ${r.weight.toFixed(2)}\uFF08\u7A7A\u95F2\u5EA6 ${r.idleness.toFixed(2)} \xD7 \u989D\u5EA6\u7CFB\u6570 ${r.quotaFactor.toFixed(2)}\uFF0C${quota}\uFF09`;
|
|
244012
|
+
}).join("\uFF1B");
|
|
244013
|
+
const skipped = result.rejections.length > 0 ? `\uFF1B\u6392\u9664 ${result.rejections.map((r) => `${r.nodeName}\xB7${r.runtimeKind}(${r.cause})`).join("\u3001")}` : "";
|
|
244014
|
+
return `${head}\u3002${detail}${skipped}`;
|
|
244015
|
+
}
|
|
244016
|
+
var init_allocate = __esm({
|
|
244017
|
+
"../server/src/domains/runtime-allocation/allocate.ts"() {
|
|
244018
|
+
"use strict";
|
|
244019
|
+
init_src();
|
|
244020
|
+
}
|
|
244021
|
+
});
|
|
244022
|
+
|
|
244023
|
+
// ../server/src/domains/runtime-allocation/errors.ts
|
|
244024
|
+
function describeRejections(rejections) {
|
|
244025
|
+
return rejections.map((r) => `${r.nodeName} \xB7 ${r.runtimeKind}\uFF08${CAUSE_TEXT[r.cause]}${r.detail ? `\uFF1A${r.detail}` : ""}\uFF09`).join("\uFF1B");
|
|
244026
|
+
}
|
|
244027
|
+
function requireAllocation(result, who) {
|
|
244028
|
+
if (result.ok) return result;
|
|
244029
|
+
const subject = who?.actorName?.trim() ? `\u300C${who.actorName.trim()}\u300D` : "\u8FD9\u4F4D Agent";
|
|
244030
|
+
const what = who?.what?.trim() ? `${who.what.trim()}\uFF1A` : "";
|
|
244031
|
+
const detail = describeRejections(result.rejections);
|
|
244032
|
+
if (result.reason === "no-candidates") {
|
|
244033
|
+
throw new RuntimeAllocationError(
|
|
244034
|
+
"RUNTIME_ALLOCATION_NO_CANDIDATES",
|
|
244035
|
+
`${what}${subject}\u6CA1\u6709\u53EF\u7528\u7684\u8FD0\u884C\u65F6\u76EE\u6807\uFF1A\u65E2\u6CA1\u6709\u4E3B\u8FD0\u884C\u65F6\u7ED1\u5B9A\uFF0C\u4E5F\u6CA1\u6709\u914D\u7F6E\u5019\u9009\u8FD0\u884C\u65F6\u3002\u8BF7\u5728\u5458\u5DE5\u8BE6\u60C5\u9875\u7684\u300C\u9ED8\u8BA4\u8FD0\u884C\u914D\u7F6E\u300D\u91CC\u7ED1\u5B9A\u4E00\u4E2A\u8FD0\u884C\u65F6\uFF0C\u6216\u52A0\u4E00\u6761\u5019\u9009\u8FD0\u884C\u65F6\u3002`,
|
|
244036
|
+
result.rejections
|
|
244037
|
+
);
|
|
244038
|
+
}
|
|
244039
|
+
if (result.reason === "all-at-capacity") {
|
|
244040
|
+
throw new RuntimeAllocationError(
|
|
244041
|
+
"RUNTIME_ALLOCATION_ALL_AT_CAPACITY",
|
|
244042
|
+
`${what}${subject}\u7684\u8FD0\u884C\u65F6\u5168\u90E8\u5DF2\u5230\u5E76\u53D1\u4E0A\u9650\uFF0C\u672C\u6B21\u6CA1\u6709\u6D3E\u51FA\uFF1A${detail}\u3002\u8BF7\u7B49\u5F53\u524D\u4F1A\u8BDD\u8DD1\u5B8C\uFF0C\u6216\u5728\u8FD0\u884C\u65F6\u7BA1\u7406\u9875\u628A\u8FD9\u4E9B\u8282\u70B9\u7684\u5E76\u53D1\u4E0A\u9650\u8C03\u9AD8\u3002`,
|
|
244043
|
+
result.rejections,
|
|
244044
|
+
503
|
|
244045
|
+
);
|
|
244046
|
+
}
|
|
244047
|
+
throw new RuntimeAllocationError(
|
|
244048
|
+
"RUNTIME_ALLOCATION_ALL_UNAVAILABLE",
|
|
244049
|
+
`${what}${subject}\u7684\u8FD0\u884C\u65F6\u76EE\u524D\u90FD\u4E0D\u53EF\u7528\uFF0C\u672C\u6B21\u6CA1\u6709\u6D3E\u51FA\uFF1A${detail}\u3002\u8BF7\u786E\u8BA4\u90A3\u4E9B\u673A\u5668\u5F00\u7740\u3001\u8282\u70B9\u7A0B\u5E8F\u5728\u8FD0\u884C\u3001\u8FD0\u884C\u65F6\u5DF2\u767B\u5F55\u4E14\u989D\u5EA6\u672A\u8017\u5C3D\uFF1B\u6216\u5728\u5458\u5DE5\u8BE6\u60C5\u9875\u52A0\u4E00\u6761\u522B\u7684\u5019\u9009\u8FD0\u884C\u65F6\u3002`,
|
|
244050
|
+
result.rejections,
|
|
244051
|
+
result.reason === "all-tried" ? 409 : 503
|
|
244052
|
+
);
|
|
244053
|
+
}
|
|
244054
|
+
var RuntimeAllocationError, CAUSE_TEXT;
|
|
244055
|
+
var init_errors6 = __esm({
|
|
244056
|
+
"../server/src/domains/runtime-allocation/errors.ts"() {
|
|
244057
|
+
"use strict";
|
|
244058
|
+
RuntimeAllocationError = class extends Error {
|
|
244059
|
+
constructor(code2, message, rejections, status = 409) {
|
|
244060
|
+
super(message);
|
|
244061
|
+
this.code = code2;
|
|
244062
|
+
this.rejections = rejections;
|
|
244063
|
+
this.status = status;
|
|
244064
|
+
this.name = "RuntimeAllocationError";
|
|
244065
|
+
}
|
|
244066
|
+
};
|
|
244067
|
+
CAUSE_TEXT = {
|
|
244068
|
+
"node-unknown": "\u8282\u70B9\u4E0D\u53EF\u7528",
|
|
244069
|
+
"node-offline": "\u8282\u70B9\u79BB\u7EBF",
|
|
244070
|
+
"runtime-missing": "\u673A\u5668\u4E0A\u6CA1\u6709\u8FD9\u4E2A\u8FD0\u884C\u65F6",
|
|
244071
|
+
faulted: "\u6807\u8BB0\u4E3A\u6545\u969C",
|
|
244072
|
+
"at-capacity": "\u5DF2\u5230\u5E76\u53D1\u4E0A\u9650",
|
|
244073
|
+
"already-tried": "\u672C\u8F6E\u5DF2\u8BD5\u8FC7\u5E76\u5931\u8D25"
|
|
244074
|
+
};
|
|
244075
|
+
}
|
|
244076
|
+
});
|
|
244077
|
+
|
|
244078
|
+
// ../server/src/domains/runtime-allocation/service.ts
|
|
244079
|
+
function isRuntimeClassFailure(reason, errorMessage2) {
|
|
244080
|
+
if (reason === "server-unreachable" || reason === "rate-limit" || reason === "auth-error") return true;
|
|
244081
|
+
if (reason === "error" && errorMessage2?.startsWith("\u8FD9\u4E00\u8F6E\u6CA1\u80FD\u5728\u8282\u70B9\u4E0A\u542F\u52A8\uFF1A")) return true;
|
|
244082
|
+
return false;
|
|
244083
|
+
}
|
|
244084
|
+
function createRuntimeAllocator(ports) {
|
|
244085
|
+
const units = /* @__PURE__ */ new Map();
|
|
244086
|
+
const log3 = ports.log ?? ((line2) => console.log(line2));
|
|
244087
|
+
const sweep = (nowMs) => {
|
|
244088
|
+
for (const [key2, state] of units) if (nowMs - state.updatedAtMs > UNIT_STATE_TTL_MS) units.delete(key2);
|
|
244089
|
+
};
|
|
244090
|
+
const stateOf = (unitOfWork, nowMs) => {
|
|
244091
|
+
sweep(nowMs);
|
|
244092
|
+
const existing = units.get(unitOfWork);
|
|
244093
|
+
if (existing) return existing;
|
|
244094
|
+
const fresh = { tried: /* @__PURE__ */ new Map(), updatedAtMs: nowMs };
|
|
244095
|
+
units.set(unitOfWork, fresh);
|
|
244096
|
+
return fresh;
|
|
244097
|
+
};
|
|
244098
|
+
const flatten = (binding, candidates, primary) => {
|
|
244099
|
+
const out = [];
|
|
244100
|
+
const seen = /* @__PURE__ */ new Set();
|
|
244101
|
+
const head = primary ?? (binding?.nodeId && binding.runtimeKind && binding.status === "active" ? { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind, model: null } : null);
|
|
244102
|
+
if (head) {
|
|
244103
|
+
out.push({ nodeId: head.nodeId, runtimeKind: head.runtimeKind, primary: true, ord: -1, model: head.model ?? null });
|
|
244104
|
+
seen.add(runtimeTargetKey(head));
|
|
244105
|
+
}
|
|
244106
|
+
for (const c of candidates) {
|
|
244107
|
+
const key2 = runtimeTargetKey(c);
|
|
244108
|
+
if (seen.has(key2)) continue;
|
|
244109
|
+
seen.add(key2);
|
|
244110
|
+
out.push({ nodeId: c.nodeId, runtimeKind: c.runtimeKind, primary: false, ord: c.ord, model: c.model ?? null });
|
|
244111
|
+
}
|
|
244112
|
+
return out;
|
|
244113
|
+
};
|
|
244114
|
+
const gather = async (actorId, primary) => {
|
|
244115
|
+
const [binding, candidateRows, nodeStates, faultRows, quotaRows] = await Promise.all([
|
|
244116
|
+
ports.primaryBinding(actorId).catch(() => null),
|
|
244117
|
+
ports.candidates(actorId).catch(() => []),
|
|
244118
|
+
ports.nodeStates(),
|
|
244119
|
+
ports.faults().catch(() => []),
|
|
244120
|
+
ports.quotas().catch(() => [])
|
|
244121
|
+
]);
|
|
244122
|
+
return {
|
|
244123
|
+
candidates: flatten(binding, candidateRows, primary),
|
|
244124
|
+
nodes: new Map(nodeStates.map((n) => [n.nodeId, n])),
|
|
244125
|
+
faults: new Map(faultRows.map((f2) => [runtimeTargetKey(f2), f2])),
|
|
244126
|
+
quotas: new Map(quotaRows.map((q2) => [runtimeTargetKey(q2), q2]))
|
|
244127
|
+
};
|
|
244128
|
+
};
|
|
244129
|
+
return {
|
|
244130
|
+
/** 跑一次分配但**不抛错**(给需要自己决定怎么呈现失败的调用方,比如要落一条失败卡的那条路)。 */
|
|
244131
|
+
async tryAllocate(input) {
|
|
244132
|
+
const now = input.now ?? Date.now();
|
|
244133
|
+
const state = stateOf(input.unitOfWork, now);
|
|
244134
|
+
const gathered = await gather(input.actorId, input.primary);
|
|
244135
|
+
return allocateRuntime({
|
|
244136
|
+
candidates: gathered.candidates,
|
|
244137
|
+
nodes: gathered.nodes,
|
|
244138
|
+
faults: gathered.faults,
|
|
244139
|
+
quotas: gathered.quotas,
|
|
244140
|
+
alreadyTried: new Set(state.tried.keys()),
|
|
244141
|
+
triedDetail: state.tried
|
|
244142
|
+
}, now);
|
|
244143
|
+
},
|
|
244144
|
+
/**
|
|
244145
|
+
* 分配,选不出就抛 {@link RuntimeAllocationError}(经 `requireAllocation` 那一处——日后接排队改那里)。
|
|
244146
|
+
* 成功时记下「这件活这次派到哪」,供 {@link noteFailure} 回报。
|
|
244147
|
+
*/
|
|
244148
|
+
async allocateFor(input) {
|
|
244149
|
+
const now = input.now ?? Date.now();
|
|
244150
|
+
const state = stateOf(input.unitOfWork, now);
|
|
244151
|
+
const actorName = await ports.actorName?.(input.actorId).catch(() => null) ?? null;
|
|
244152
|
+
if (!canRetryAllocation(state.tried.size)) {
|
|
244153
|
+
const tried = [...state.tried.entries()].map(([key2, detail]) => {
|
|
244154
|
+
const [nodeId, runtimeKind] = key2.split("::");
|
|
244155
|
+
return { nodeId, runtimeKind, nodeName: nodeId, cause: "already-tried", detail };
|
|
244156
|
+
});
|
|
244157
|
+
throw new RuntimeAllocationError(
|
|
244158
|
+
"RUNTIME_ALLOCATION_ALL_UNAVAILABLE",
|
|
244159
|
+
`${input.what ? `${input.what}\uFF1A` : ""}${actorName ? `\u300C${actorName}\u300D` : "\u8FD9\u4F4D Agent"}\u7684\u8FD0\u884C\u65F6\u5DF2\u8FDE\u7EED\u6362\u4E86 ${RUNTIME_ALLOCATION_MAX_ATTEMPTS} \u6B21\u90FD\u6CA1\u8DD1\u8D77\u6765\uFF0C\u4E0D\u518D\u81EA\u52A8\u91CD\u8BD5\uFF1A${tried.map((t) => `${t.nodeName} \xB7 ${t.runtimeKind}\uFF08${t.detail}\uFF09`).join("\uFF1B")}\u3002\u8BF7\u5148\u4FEE\u597D\u5176\u4E2D\u4E00\u53F0\u673A\u5668\uFF0C\u6216\u5728\u5458\u5DE5\u8BE6\u60C5\u9875\u52A0\u4E00\u6761\u522B\u7684\u5019\u9009\u8FD0\u884C\u65F6\u540E\u91CD\u65B0\u53D1\u8D77\u3002`,
|
|
244160
|
+
tried
|
|
244161
|
+
);
|
|
244162
|
+
}
|
|
244163
|
+
const result = requireAllocation(
|
|
244164
|
+
await this.tryAllocate({ actorId: input.actorId, unitOfWork: input.unitOfWork, ...input.primary !== void 0 ? { primary: input.primary } : {}, now }),
|
|
244165
|
+
{ actorName, ...input.what ? { what: input.what } : {} }
|
|
244166
|
+
);
|
|
244167
|
+
const attempt = state.tried.size + 1;
|
|
244168
|
+
state.lastAssigned = { nodeId: result.chosen.nodeId, runtimeKind: result.chosen.runtimeKind };
|
|
244169
|
+
state.updatedAtMs = now;
|
|
244170
|
+
const explanation = describeAllocationChoice(result);
|
|
244171
|
+
log3(`[runtime-alloc] ${input.unitOfWork} \u7B2C ${attempt} \u6B21\uFF1A${explanation}`);
|
|
244172
|
+
return {
|
|
244173
|
+
binding: { actorId: input.actorId, nodeId: result.chosen.nodeId, runtimeKind: result.chosen.runtimeKind, status: "active" },
|
|
244174
|
+
model: result.chosen.model ?? null,
|
|
244175
|
+
source: result.source,
|
|
244176
|
+
attempt,
|
|
244177
|
+
explanation
|
|
244178
|
+
};
|
|
244179
|
+
},
|
|
244180
|
+
/**
|
|
244181
|
+
* 这件活在上次分给它的那个目标上**因 runtime/节点原因**失败了。下一次 `allocateFor` 会排除它。
|
|
244182
|
+
*
|
|
244183
|
+
* 返回 false = 这条失败不算 runtime/节点的错(agent 自己干砸了),什么都没记。
|
|
244184
|
+
*/
|
|
244185
|
+
noteFailure(input) {
|
|
244186
|
+
if (!isRuntimeClassFailure(input.reason, input.errorMessage)) return false;
|
|
244187
|
+
const now = input.now ?? Date.now();
|
|
244188
|
+
const state = units.get(input.unitOfWork);
|
|
244189
|
+
const target = input.target ?? state?.lastAssigned;
|
|
244190
|
+
if (!state || !target) return false;
|
|
244191
|
+
const detail = input.errorMessage?.trim() || String(input.reason ?? "runtime \u5931\u8D25");
|
|
244192
|
+
state.tried.set(runtimeTargetKey(target), detail.slice(0, 200));
|
|
244193
|
+
state.updatedAtMs = now;
|
|
244194
|
+
log3(`[runtime-alloc] ${input.unitOfWork} \u5728 ${target.nodeId} \xB7 ${target.runtimeKind} \u4E0A\u5931\u8D25\uFF08${detail.slice(0, 80)}\uFF09\u2192 \u4E0B\u4E00\u8F6E\u6362\u5019\u9009`);
|
|
244195
|
+
return true;
|
|
244196
|
+
},
|
|
244197
|
+
/**
|
|
244198
|
+
* 这件活这一轮**不是因为 runtime/节点**结束的(跑成了、agent 自己干砸了、被停了)→ 丢掉整条失败链。
|
|
244199
|
+
*
|
|
244200
|
+
* 这是重试上限只对「连着换机器都没跑起来」生效的机制:正常派发一次就清一次,
|
|
244201
|
+
* 所以一个长命的工单节点不会因为派过很多次而撞上上限。
|
|
244202
|
+
*/
|
|
244203
|
+
noteSettled(unitOfWork) {
|
|
244204
|
+
units.delete(unitOfWork);
|
|
244205
|
+
},
|
|
244206
|
+
/** 只给测试与诊断用:这件活当前失败链的账。 */
|
|
244207
|
+
inspect(unitOfWork) {
|
|
244208
|
+
const s2 = units.get(unitOfWork);
|
|
244209
|
+
if (!s2) return null;
|
|
244210
|
+
return { failures: s2.tried.size, tried: [...s2.tried.keys()], ...s2.lastAssigned ? { lastAssigned: s2.lastAssigned } : {} };
|
|
244211
|
+
}
|
|
244212
|
+
};
|
|
244213
|
+
}
|
|
244214
|
+
var UNIT_STATE_TTL_MS;
|
|
244215
|
+
var init_service7 = __esm({
|
|
244216
|
+
"../server/src/domains/runtime-allocation/service.ts"() {
|
|
244217
|
+
"use strict";
|
|
244218
|
+
init_src();
|
|
244219
|
+
init_allocate();
|
|
244220
|
+
init_errors6();
|
|
244221
|
+
UNIT_STATE_TTL_MS = 2 * 60 * 6e4;
|
|
244222
|
+
}
|
|
244223
|
+
});
|
|
244224
|
+
|
|
243627
244225
|
// ../server/src/domains/types/index.ts
|
|
243628
244226
|
function typesDomain(deps) {
|
|
243629
244227
|
const adminOf = async (companyId) => {
|
|
@@ -243747,6 +244345,10 @@ var init_node_store = __esm({
|
|
|
243747
244345
|
MemoryNodeStore = class {
|
|
243748
244346
|
nodes = /* @__PURE__ */ new Map();
|
|
243749
244347
|
runtimes = /* @__PURE__ */ new Map();
|
|
244348
|
+
/** 故障标记,键 = `${nodeId}::${runtimeKind}`(与 `runtimeTargetKey` 同一个键形状)。 */
|
|
244349
|
+
faults = /* @__PURE__ */ new Map();
|
|
244350
|
+
/** 落库额度,键同上。 */
|
|
244351
|
+
quotas = /* @__PURE__ */ new Map();
|
|
243750
244352
|
async upsertNode(node2) {
|
|
243751
244353
|
this.nodes.set(node2.id, { ...this.nodes.get(node2.id), ...node2 });
|
|
243752
244354
|
}
|
|
@@ -243859,6 +244461,27 @@ var init_node_store = __esm({
|
|
|
243859
244461
|
}
|
|
243860
244462
|
return { nodeDeleted: false };
|
|
243861
244463
|
}
|
|
244464
|
+
/* ---------- 运行时分配(ADR 0616) ---------- */
|
|
244465
|
+
async setNodeMaxConcurrent(nodeId, limit) {
|
|
244466
|
+
const n = this.nodes.get(nodeId);
|
|
244467
|
+
if (n) n.maxConcurrent = limit;
|
|
244468
|
+
}
|
|
244469
|
+
async upsertRuntimeFault(fault) {
|
|
244470
|
+
this.faults.set(`${fault.nodeId}::${fault.runtimeKind}`, { ...fault });
|
|
244471
|
+
}
|
|
244472
|
+
async clearRuntimeFault(nodeId, runtimeKind) {
|
|
244473
|
+
return this.faults.delete(`${nodeId}::${runtimeKind}`);
|
|
244474
|
+
}
|
|
244475
|
+
async listRuntimeFaults(filter) {
|
|
244476
|
+
return [...this.faults.values()].filter((f2) => filter?.nodeId ? f2.nodeId === filter.nodeId : true).filter((f2) => filter?.companyId ? this.nodes.get(f2.nodeId)?.companyId === filter.companyId : true).map((f2) => ({ ...f2 }));
|
|
244477
|
+
}
|
|
244478
|
+
async saveRuntimeQuotas(nodeId, quotas) {
|
|
244479
|
+
for (const key2 of [...this.quotas.keys()]) if (key2.startsWith(`${nodeId}::`)) this.quotas.delete(key2);
|
|
244480
|
+
for (const q2 of quotas) this.quotas.set(`${q2.nodeId}::${q2.runtimeKind}`, { ...q2 });
|
|
244481
|
+
}
|
|
244482
|
+
async listRuntimeQuotas(filter) {
|
|
244483
|
+
return [...this.quotas.values()].filter((q2) => filter?.nodeId ? q2.nodeId === filter.nodeId : true).filter((q2) => filter?.companyId ? this.nodes.get(q2.nodeId)?.companyId === filter.companyId : true).map((q2) => ({ ...q2 }));
|
|
244484
|
+
}
|
|
243862
244485
|
};
|
|
243863
244486
|
FileNodeStore = class _FileNodeStore extends MemoryNodeStore {
|
|
243864
244487
|
constructor(file) {
|
|
@@ -243871,15 +244494,19 @@ var init_node_store = __esm({
|
|
|
243871
244494
|
const snap = JSON.parse(fs26.readFileSync(file, "utf8"));
|
|
243872
244495
|
for (const n of snap.nodes ?? []) s2.nodes.set(n.id, n);
|
|
243873
244496
|
for (const r of snap.runtimes ?? []) s2.runtimes.set(r.id, r);
|
|
244497
|
+
for (const f2 of snap.faults ?? []) s2.faults.set(`${f2.nodeId}::${f2.runtimeKind}`, f2);
|
|
244498
|
+
for (const q2 of snap.quotas ?? []) s2.quotas.set(`${q2.nodeId}::${q2.runtimeKind}`, q2);
|
|
243874
244499
|
} catch {
|
|
243875
|
-
fs26.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [] }, null, 2));
|
|
244500
|
+
fs26.writeFileSync(file, JSON.stringify({ nodes: [], runtimes: [], faults: [], quotas: [] }, null, 2));
|
|
243876
244501
|
}
|
|
243877
244502
|
return s2;
|
|
243878
244503
|
}
|
|
243879
244504
|
flush() {
|
|
243880
244505
|
fs26.writeFileSync(this.file, JSON.stringify({
|
|
243881
244506
|
nodes: [...this.nodes.values()],
|
|
243882
|
-
runtimes: [...this.runtimes.values()]
|
|
244507
|
+
runtimes: [...this.runtimes.values()],
|
|
244508
|
+
faults: [...this.faults.values()],
|
|
244509
|
+
quotas: [...this.quotas.values()]
|
|
243883
244510
|
}, null, 2));
|
|
243884
244511
|
}
|
|
243885
244512
|
async upsertNode(node2) {
|
|
@@ -243943,6 +244570,25 @@ var init_node_store = __esm({
|
|
|
243943
244570
|
this.flush();
|
|
243944
244571
|
return r;
|
|
243945
244572
|
}
|
|
244573
|
+
// ⚠ 同上:并发上限、故障标记、额度三类写入漏了 override 就只活在内存,重启回到「没上限、没故障、
|
|
244574
|
+
// 没额度数据」——而那三件事恰恰都是「重启不能丢」才有意义。
|
|
244575
|
+
async setNodeMaxConcurrent(nodeId, limit) {
|
|
244576
|
+
await super.setNodeMaxConcurrent(nodeId, limit);
|
|
244577
|
+
this.flush();
|
|
244578
|
+
}
|
|
244579
|
+
async upsertRuntimeFault(fault) {
|
|
244580
|
+
await super.upsertRuntimeFault(fault);
|
|
244581
|
+
this.flush();
|
|
244582
|
+
}
|
|
244583
|
+
async clearRuntimeFault(nodeId, runtimeKind) {
|
|
244584
|
+
const r = await super.clearRuntimeFault(nodeId, runtimeKind);
|
|
244585
|
+
if (r) this.flush();
|
|
244586
|
+
return r;
|
|
244587
|
+
}
|
|
244588
|
+
async saveRuntimeQuotas(nodeId, quotas) {
|
|
244589
|
+
await super.saveRuntimeQuotas(nodeId, quotas);
|
|
244590
|
+
this.flush();
|
|
244591
|
+
}
|
|
243946
244592
|
};
|
|
243947
244593
|
}
|
|
243948
244594
|
});
|
|
@@ -245216,7 +245862,7 @@ function compactModelChunks(rows) {
|
|
|
245216
245862
|
return out;
|
|
245217
245863
|
}
|
|
245218
245864
|
var EVENT_PAGE_LIMIT, MODEL_CHUNK_COMPACT_WINDOW_MS, MODEL_CHUNK_RAW_MULTIPLIER, TraceService;
|
|
245219
|
-
var
|
|
245865
|
+
var init_service8 = __esm({
|
|
245220
245866
|
"../server/src/domains/trace/service.ts"() {
|
|
245221
245867
|
"use strict";
|
|
245222
245868
|
init_sink();
|
|
@@ -245992,9 +246638,9 @@ function createTraceDomain(opts) {
|
|
|
245992
246638
|
var init_trace2 = __esm({
|
|
245993
246639
|
"../server/src/domains/trace/index.ts"() {
|
|
245994
246640
|
"use strict";
|
|
245995
|
-
|
|
246641
|
+
init_service8();
|
|
245996
246642
|
init_routes9();
|
|
245997
|
-
|
|
246643
|
+
init_service8();
|
|
245998
246644
|
init_routes9();
|
|
245999
246645
|
init_read_scope();
|
|
246000
246646
|
init_sink();
|
|
@@ -246223,7 +246869,7 @@ function renderResumePack(pack) {
|
|
|
246223
246869
|
if (cp) lines.push(`- Recoverability: ${cp.recoverability}`);
|
|
246224
246870
|
if (done.length > 0) lines.push(`- \u4E0A\u4E00\u8D9F\u5DF2\u5B8C\u6210\u7684\u6B65\u9AA4\uFF1A${done.join("\u3001")}`);
|
|
246225
246871
|
for (const ref2 of durable) {
|
|
246226
|
-
lines.push(` - durable: ${ref2
|
|
246872
|
+
lines.push(` - durable: ${renderRefId(ref2)}${ref2.version ? `@${ref2.version}` : ""}${ref2.label ? ` \u2014 ${ref2.label}` : ""}`);
|
|
246227
246873
|
}
|
|
246228
246874
|
if (cp && cp.pendingSteps.length > 0) {
|
|
246229
246875
|
for (const step of cp.pendingSteps) lines.push(` - pending: ${step}`);
|
|
@@ -246268,6 +246914,11 @@ function parseResumePackSnapshot(content3) {
|
|
|
246268
246914
|
return null;
|
|
246269
246915
|
}
|
|
246270
246916
|
}
|
|
246917
|
+
function renderRefId(ref2) {
|
|
246918
|
+
const kind = ref2.kind.trim();
|
|
246919
|
+
const id = ref2.id.trim();
|
|
246920
|
+
return kind && !id.startsWith(`${kind}:`) ? `${kind}:${id}` : id;
|
|
246921
|
+
}
|
|
246271
246922
|
function renderWorkVersion(work) {
|
|
246272
246923
|
const current = work.currentWorkId ?? "none";
|
|
246273
246924
|
const delivered = work.acceptedWorkId ?? "none";
|
|
@@ -246365,20 +247016,29 @@ function renderCoordinatorView(view, taskBackground) {
|
|
|
246365
247016
|
`- Actor: ${node2.actorId ?? "unassigned"}; role: ${node2.role ?? "unassigned"}`
|
|
246366
247017
|
);
|
|
246367
247018
|
if (node2.dependsOn.length > 0) lines.push(`- Depends on: ${node2.dependsOn.join(", ")}`);
|
|
246368
|
-
if (node2.conclusion?.trim())
|
|
247019
|
+
if (node2.conclusion?.trim()) {
|
|
247020
|
+
lines.push(node2.work.acceptedWorkId ? `- Conclusion (delivered ${node2.work.acceptedWorkId}): ${node2.conclusion.trim()}` : `- Conclusion: ${node2.conclusion.trim()}`);
|
|
247021
|
+
}
|
|
246369
247022
|
if (node2.recoverySource) {
|
|
246370
247023
|
const source = node2.recoverySource;
|
|
246371
247024
|
lines.push(
|
|
246372
247025
|
`- Recovery source: Attempt ${source.recoveredFromAttempt.attemptNo ?? "?"} \u2192 Attempt ${source.currentAttempt.attemptNo ?? "?"}`
|
|
246373
247026
|
);
|
|
246374
247027
|
if (source.recoveryCheckpoint) {
|
|
246375
|
-
lines.push(
|
|
247028
|
+
lines.push(
|
|
247029
|
+
` - Recovery checkpoint: #${source.recoveryCheckpoint.seq} (${source.recoveryCheckpoint.recoverability}) from Attempt ${source.recoveredFromAttempt.attemptNo ?? "?"}`
|
|
247030
|
+
);
|
|
246376
247031
|
for (const step of source.recoveryCheckpoint.pendingSteps) lines.push(` - Recovery pending: ${step}`);
|
|
246377
247032
|
}
|
|
246378
|
-
if (source.recoveryHandoff
|
|
246379
|
-
|
|
246380
|
-
|
|
246381
|
-
|
|
247033
|
+
if (source.recoveryHandoff) {
|
|
247034
|
+
const handoffFrom = source.recoveryHandoff.attemptId === source.recoveredFromAttempt.id ? `Attempt ${source.recoveredFromAttempt.attemptNo ?? "?"}` : `attempt ${source.recoveryHandoff.attemptId}`;
|
|
247035
|
+
if (source.recoveryHandoff.producedBy === "agent") {
|
|
247036
|
+
lines.push(` - Recovery Agent handoff (${handoffFrom}): ${source.recoveryHandoff.summary}`);
|
|
247037
|
+
} else if (source.recoveryHandoff.producedBy === "system_reconstructed") {
|
|
247038
|
+
lines.push(
|
|
247039
|
+
` - Recovery system reconstruction (${handoffFrom}, not an Agent conclusion): ${source.recoveryHandoff.summary}`
|
|
247040
|
+
);
|
|
247041
|
+
}
|
|
246382
247042
|
}
|
|
246383
247043
|
for (const risk of source.recoveryHandoff?.risks ?? []) lines.push(` - Recovery risk: ${risk}`);
|
|
246384
247044
|
}
|
|
@@ -246388,11 +247048,12 @@ function renderCoordinatorView(view, taskBackground) {
|
|
|
246388
247048
|
}
|
|
246389
247049
|
if (node2.handoffProducer === "agent" && node2.summary.trim() && !sameCoordinatorText(node2.summary, node2.conclusion)) {
|
|
246390
247050
|
lines.push(`- Technical notes: ${node2.summary}`);
|
|
246391
|
-
} else if (node2.handoffProducer === "system_reconstructed" && node2.summary.startsWith("[system_reconstructed]")) {
|
|
247051
|
+
} else if (node2.handoffProducer === "system_reconstructed" && node2.summary.startsWith("[system_reconstructed]") && node2.status !== "succeeded") {
|
|
246392
247052
|
lines.push(`- Execution diagnostic: ${node2.summary}`);
|
|
246393
247053
|
}
|
|
246394
247054
|
for (const output of node2.changedOutputs) {
|
|
246395
|
-
|
|
247055
|
+
if (!output.version) continue;
|
|
247056
|
+
lines.push(` - Durable output: ${renderRefId(output)}@${output.version}`);
|
|
246396
247057
|
}
|
|
246397
247058
|
const openItems = [
|
|
246398
247059
|
...node2.unresolvedIssues.map((value2) => `[unresolved] ${value2}`),
|
|
@@ -246512,7 +247173,7 @@ function stateFromAttempt(attempt, handoff, checkpoint, now, stalledAfterMs, ope
|
|
|
246512
247173
|
return "failed";
|
|
246513
247174
|
}
|
|
246514
247175
|
var import_node_crypto64, BLOCKING_STATES, HANDOFF_OUTCOMES2, RESUME_EXTERNAL_EFFECT_LIMIT, RESUME_SNAPSHOT_PREFIX, RESUME_SNAPSHOT_SUFFIX, ExecutionContinuityService;
|
|
246515
|
-
var
|
|
247176
|
+
var init_service9 = __esm({
|
|
246516
247177
|
"../server/src/domains/execution-continuity/service.ts"() {
|
|
246517
247178
|
"use strict";
|
|
246518
247179
|
init_src();
|
|
@@ -247171,7 +247832,7 @@ function nodeCoordinatorFactsFromSnapshot(snapshot) {
|
|
|
247171
247832
|
state: currentWorkId === null ? "not_started" : currentWork ? workState(currentWork, hasOutput(currentWork, artifactCountByWork.get(currentWork.id) ?? 0)) : "unavailable"
|
|
247172
247833
|
};
|
|
247173
247834
|
const accepted = node2.latestAcceptId ? workById.get(node2.latestAcceptId) : void 0;
|
|
247174
|
-
const conclusion = accepted?.conclusion?.trim()
|
|
247835
|
+
const conclusion = accepted?.conclusion?.trim();
|
|
247175
247836
|
const sealed = node2.cancelledAt ? { reason: node2.sealReason ?? "cancelled", at: node2.cancelledAt } : null;
|
|
247176
247837
|
out[node2.id] = {
|
|
247177
247838
|
work,
|
|
@@ -247332,8 +247993,8 @@ var init_execution_continuity2 = __esm({
|
|
|
247332
247993
|
"../server/src/domains/execution-continuity/index.ts"() {
|
|
247333
247994
|
"use strict";
|
|
247334
247995
|
init_routes10();
|
|
247335
|
-
|
|
247336
|
-
|
|
247996
|
+
init_service9();
|
|
247997
|
+
init_service9();
|
|
247337
247998
|
init_routes10();
|
|
247338
247999
|
init_handoff_work_bridge();
|
|
247339
248000
|
init_upstream_handoff_input();
|
|
@@ -250420,7 +251081,7 @@ function appendSourceNote(brief, automation, source, atIso) {
|
|
|
250420
251081
|
\u672C\u5DE5\u5355\u7531\u81EA\u52A8\u5316\u300C${automation.name}\u300D\u4E8E ${atIso} \u89E6\u53D1\uFF08\u6765\u6E90\uFF1A${source}\uFF09\u3002\u5F00\u5DE5\u540E\u8BF7\u628A\u5DE5\u5355\u6807\u9898\u6539\u6210\u51C6\u786E\u53CD\u6620\u5B9E\u9645\u5DE5\u4F5C\u7684\u63CF\u8FF0\u3002`;
|
|
250421
251082
|
}
|
|
250422
251083
|
var import_node_crypto66, DATE_TOKEN, AutomationsService, EVENT_RETRY_BASE_MS, EVENT_RETRY_MAX_MS, EVENT_RETRY_MAX_ATTEMPTS, eventAttempts, eventLastAttemptAt, eventLastError, CHAT_REPLY_MAX;
|
|
250423
|
-
var
|
|
251084
|
+
var init_service10 = __esm({
|
|
250424
251085
|
"../server/src/domains/automations/service.ts"() {
|
|
250425
251086
|
"use strict";
|
|
250426
251087
|
import_node_crypto66 = require("node:crypto");
|
|
@@ -252155,7 +252816,7 @@ var init_routes11 = __esm({
|
|
|
252155
252816
|
init_src();
|
|
252156
252817
|
init_templates();
|
|
252157
252818
|
init_router();
|
|
252158
|
-
|
|
252819
|
+
init_service10();
|
|
252159
252820
|
init_projection2();
|
|
252160
252821
|
init_scheduler();
|
|
252161
252822
|
init_registry3();
|
|
@@ -252270,10 +252931,10 @@ function createAutomationsDomain(opts) {
|
|
|
252270
252931
|
var init_automations = __esm({
|
|
252271
252932
|
"../server/src/domains/automations/index.ts"() {
|
|
252272
252933
|
"use strict";
|
|
252273
|
-
|
|
252934
|
+
init_service10();
|
|
252274
252935
|
init_webhook();
|
|
252275
252936
|
init_routes11();
|
|
252276
|
-
|
|
252937
|
+
init_service10();
|
|
252277
252938
|
init_routes11();
|
|
252278
252939
|
init_projection2();
|
|
252279
252940
|
init_webhook();
|
|
@@ -253089,6 +253750,32 @@ var init_daemon_adapter = __esm({
|
|
|
253089
253750
|
}
|
|
253090
253751
|
}
|
|
253091
253752
|
}
|
|
253753
|
+
/**
|
|
253754
|
+
* Bug 0232:**就地**收尾一次派发——用户点了停止、而 kill 帧送不出去(节点 socket 非 OPEN)时用。
|
|
253755
|
+
*
|
|
253756
|
+
* 走的是和「服务端自判死」完全同一个 {@link settle},所以三件必须做的事一次做齐:
|
|
253757
|
+
* 兑现在途的插话等待者、触发 exitCbs(本轮收口、释放 `chat_session_turns` 的槽、输入框解冻)、
|
|
253758
|
+
* 把 dispatchId 记进 `settledDispatchIds`。
|
|
253759
|
+
*
|
|
253760
|
+
* 为什么这样不会造出两个 agent 一起跑(防双跑的三道防线都是现成的):
|
|
253761
|
+
* 1. 节点上的会话进程自己有看门狗——连不上服务端超过 `SESSION_LIMITS.suicideAfterUnreachableMs`
|
|
253762
|
+
* (5 分钟)就自尽(`packages/cli/src/daemon/session-process.ts`);
|
|
253763
|
+
* 2. 契约上钉着不变式 `suicideAfterUnreachableMs ≤ reapGraceMs`,服务端判死必然发生在它自尽之后;
|
|
253764
|
+
* 3. 它要是在这之前连回来了,hub 的三级认领(`daemon-hub.ts` 的 `judgeSession`)第一级就命中
|
|
253765
|
+
* `settledDispatchIds` → 判 `settled` → 下发 `evict` → 会话 `selfTerminate("evicted")`。
|
|
253766
|
+
* 所以僵尸的存活时间是 `min(网络恢复时间, 5 分钟)`,这个窗口自己会关。
|
|
253767
|
+
*
|
|
253768
|
+
* 归因不在这里写:`cancelCause` 在点停止那一刻就由 `LiveChatRegistry.stop` 写成了 `user`,
|
|
253769
|
+
* 收尾时读出来,页面照旧显示「用户停止了输出」。
|
|
253770
|
+
*
|
|
253771
|
+
* @returns true = 确由这次调用收的尾;false = 这次派发不在在途表里(已收尾 / 不归本 adapter 管)。
|
|
253772
|
+
*/
|
|
253773
|
+
cancelLocally(dispatchId, errorMessage2 = "\u7528\u6237\u505C\u6B62\uFF08\u8282\u70B9\u79BB\u7EBF\uFF0C\u672A\u7B49\u5230\u9000\u51FA\u5E27\uFF09") {
|
|
253774
|
+
const entry = this.pending.get(dispatchId);
|
|
253775
|
+
if (!entry || entry.exited) return false;
|
|
253776
|
+
this.settle(dispatchId, { code: null, reason: "cancelled", errorMessage: errorMessage2 });
|
|
253777
|
+
return true;
|
|
253778
|
+
}
|
|
253092
253779
|
/** 当前在途(未退出)会话数;传 runtimeKind 时收窄到单个 runtime 实例。 */
|
|
253093
253780
|
activeRunCount(nodeId, runtimeKind) {
|
|
253094
253781
|
let n = 0;
|
|
@@ -253338,14 +254025,18 @@ var init_daemon_adapter = __esm({
|
|
|
253338
254025
|
const appendTimeoutMs = this.appendTimeoutMs;
|
|
253339
254026
|
const sessionReadyWaitMs = this.sessionReadyWaitMs;
|
|
253340
254027
|
const confirm = (key2) => this.confirmInputReceipt(nodeId, dispatchId, key2);
|
|
254028
|
+
const sendKill = () => routeToSession(hub, nodeId, dispatchId, { type: "kill", dispatchId });
|
|
254029
|
+
const cancelLocally = () => this.cancelLocally(dispatchId);
|
|
253341
254030
|
return {
|
|
253342
254031
|
id: dispatchId,
|
|
253343
254032
|
get supportsLiveProtocol() {
|
|
253344
254033
|
return entry.supportsLiveProtocol;
|
|
253345
254034
|
},
|
|
253346
254035
|
async kill() {
|
|
253347
|
-
|
|
254036
|
+
sendKill();
|
|
253348
254037
|
},
|
|
254038
|
+
sendKill,
|
|
254039
|
+
cancelLocally,
|
|
253349
254040
|
onExit(cb) {
|
|
253350
254041
|
if (entry.exited) cb(entry.exited);
|
|
253351
254042
|
else entry.exitCbs.push(cb);
|
|
@@ -253430,6 +254121,8 @@ var init_daemon_adapter = __esm({
|
|
|
253430
254121
|
const appendTimeoutMs = this.appendTimeoutMs;
|
|
253431
254122
|
const sessionReadyWaitMs = this.sessionReadyWaitMs;
|
|
253432
254123
|
const confirm = (key2) => this.confirmInputReceipt(nodeId, dispatchId, key2);
|
|
254124
|
+
const sendKill = () => routeToSession(hub, nodeId, dispatchId, { type: "kill", dispatchId });
|
|
254125
|
+
const cancelLocally = () => this.cancelLocally(dispatchId);
|
|
253433
254126
|
return {
|
|
253434
254127
|
id: dispatchId,
|
|
253435
254128
|
stashDroppedFrames: stashed.droppedFrames ?? 0,
|
|
@@ -253437,8 +254130,10 @@ var init_daemon_adapter = __esm({
|
|
|
253437
254130
|
return entry.supportsLiveProtocol;
|
|
253438
254131
|
},
|
|
253439
254132
|
async kill() {
|
|
253440
|
-
|
|
254133
|
+
sendKill();
|
|
253441
254134
|
},
|
|
254135
|
+
sendKill,
|
|
254136
|
+
cancelLocally,
|
|
253442
254137
|
onExit(cb) {
|
|
253443
254138
|
if (entry.exited) cb(entry.exited);
|
|
253444
254139
|
else entry.exitCbs.push(cb);
|
|
@@ -255608,6 +256303,18 @@ var init_postgres_registry = __esm({
|
|
|
255608
256303
|
status text NOT NULL -- active | paused
|
|
255609
256304
|
)`);
|
|
255610
256305
|
await pool.query(`
|
|
256306
|
+
CREATE TABLE IF NOT EXISTS "${s2}".actor_runtime_candidates (
|
|
256307
|
+
actor_id text NOT NULL,
|
|
256308
|
+
node_id text NOT NULL,
|
|
256309
|
+
runtime_kind text NOT NULL,
|
|
256310
|
+
model text,
|
|
256311
|
+
ord integer NOT NULL,
|
|
256312
|
+
updated_at timestamptz NOT NULL,
|
|
256313
|
+
updated_by text NOT NULL,
|
|
256314
|
+
PRIMARY KEY (actor_id, node_id, runtime_kind)
|
|
256315
|
+
)`);
|
|
256316
|
+
await pool.query(`CREATE INDEX IF NOT EXISTS actor_runtime_candidates_actor_ord_idx ON "${s2}".actor_runtime_candidates (actor_id, ord)`);
|
|
256317
|
+
await pool.query(`
|
|
255611
256318
|
CREATE TABLE IF NOT EXISTS "${s2}".connectors (
|
|
255612
256319
|
id text PRIMARY KEY,
|
|
255613
256320
|
name text NOT NULL,
|
|
@@ -255965,6 +256672,49 @@ var init_postgres_registry = __esm({
|
|
|
255965
256672
|
const r = await this.pool.query(`SELECT * FROM ${this.s}.actor_bindings ORDER BY actor_id`);
|
|
255966
256673
|
return r.rows.map((row) => ({ actorId: row.actor_id, nodeId: row.node_id, runtimeKind: row.runtime_kind, status: row.status }));
|
|
255967
256674
|
}
|
|
256675
|
+
/* ---------- 候选 runtime(ADR 0616) ---------- */
|
|
256676
|
+
async listRuntimeCandidates(actorId) {
|
|
256677
|
+
const r = await this.pool.query(
|
|
256678
|
+
// 第二、三排序键让同 ord 的存量脏数据也有**确定**次序——分配算法的定序是全序的,
|
|
256679
|
+
// 读侧若随机返回,那句「为什么选了它」下一次就解释不了同一件事。
|
|
256680
|
+
`SELECT actor_id, node_id, runtime_kind, model, ord FROM ${this.s}.actor_runtime_candidates
|
|
256681
|
+
WHERE actor_id = $1 ORDER BY ord, node_id, runtime_kind`,
|
|
256682
|
+
[actorId]
|
|
256683
|
+
);
|
|
256684
|
+
return r.rows.map((row) => ({
|
|
256685
|
+
actorId: row.actor_id,
|
|
256686
|
+
nodeId: row.node_id,
|
|
256687
|
+
runtimeKind: row.runtime_kind,
|
|
256688
|
+
model: row.model ?? null,
|
|
256689
|
+
ord: Number(row.ord)
|
|
256690
|
+
}));
|
|
256691
|
+
}
|
|
256692
|
+
async replaceRuntimeCandidates(actorId, candidates, by) {
|
|
256693
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
256694
|
+
const updatedBy = by ?? "actor:system:registry";
|
|
256695
|
+
const client = await this.pool.connect();
|
|
256696
|
+
try {
|
|
256697
|
+
await client.query("BEGIN");
|
|
256698
|
+
await client.query(`DELETE FROM ${this.s}.actor_runtime_candidates WHERE actor_id = $1`, [actorId]);
|
|
256699
|
+
for (const [i, c] of candidates.entries()) {
|
|
256700
|
+
const model = typeof c.model === "string" && c.model.trim() ? c.model.trim() : null;
|
|
256701
|
+
await client.query(
|
|
256702
|
+
`INSERT INTO ${this.s}.actor_runtime_candidates (actor_id, node_id, runtime_kind, model, ord, updated_at, updated_by)
|
|
256703
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7)
|
|
256704
|
+
ON CONFLICT (actor_id, node_id, runtime_kind) DO UPDATE SET model=$4, ord=$5, updated_at=$6, updated_by=$7`,
|
|
256705
|
+
[actorId, c.nodeId, c.runtimeKind, model, i, now, updatedBy]
|
|
256706
|
+
);
|
|
256707
|
+
}
|
|
256708
|
+
await client.query("COMMIT");
|
|
256709
|
+
} catch (err) {
|
|
256710
|
+
await client.query("ROLLBACK").catch(() => {
|
|
256711
|
+
});
|
|
256712
|
+
throw err;
|
|
256713
|
+
} finally {
|
|
256714
|
+
client.release();
|
|
256715
|
+
}
|
|
256716
|
+
return this.listRuntimeCandidates(actorId);
|
|
256717
|
+
}
|
|
255968
256718
|
/* ---------- Runtime 默认配置 ---------- */
|
|
255969
256719
|
async upsertRuntimeConfig(c) {
|
|
255970
256720
|
await this.pool.query(
|
|
@@ -259317,7 +260067,18 @@ var init_postgres_trace = __esm({
|
|
|
259317
260067
|
(generated_handoff_id, NEW.id, COALESCE(NEW.job_key, NEW.trigger_ref, 'attempt:' || NEW.id), NEW.part,
|
|
259318
260068
|
'system_reconstructed', mapped_outcome,
|
|
259319
260069
|
reconstructed_summary, NEW.output_refs, '[]'::jsonb, reconstructed_risks,
|
|
259320
|
-
|
|
260070
|
+
-- \u2605 2026-09-23\uFF1Anext_actions \u4E0D\u518D\u7528 checkpoint \u7684 pendingSteps \u515C\u5E95\u3002
|
|
260071
|
+
--
|
|
260072
|
+
-- \u8FD9\u4E00\u680F\u7684\u8BED\u4E49\u662F\u300C\u4E0B\u6E38\u600E\u4E48\u7528\u8FD9\u4EFD\u6210\u679C\u300D\uFF08TASK.md \u539F\u6587\uFF1A--next\uFF1A\u4E0B\u6E38\u5982\u4F55\u4F7F\u7528\u6210\u679C\uFF0C\u4E0D\u662F\u4F60\u5269\u4F59
|
|
260073
|
+
-- \u6CA1\u505A\u7684\u5DE5\u4F5C\uFF09\uFF0C\u800C pendingSteps \u6070\u6070\u662F\u6267\u884C\u4EBA\u81EA\u5DF1\u6CA1\u505A\u5B8C\u7684\u6D3B\u3002\u515C\u5E95\u7684\u540E\u679C\uFF1A\u7EBF\u4E0A 114 \u4EFD\u4EA4\u4ED8\u8BB0\u5F55
|
|
260074
|
+
-- \u91CC 106 \u4EFD\u5E26 next\uFF0C\u5176\u4E2D 59 \u4EFD\u662F conclude / \u63D0\u4EA4\u5E76\u7ED3\u6848 \u8FD9\u7C7B\u81EA\u7559\u5F85\u529E\uFF0C\u8FD8\u88AB\u4E0B\u6E38\u4EA4\u63A5\u7684\u7ED3\u5C3E\u53E5
|
|
260075
|
+
-- \u58F0\u660E\u6210\u300C\u4EE5\u4E0A\u5F00\u653E\u9879\u662F\u4E0A\u6E38\u6267\u884C\u4EBA\u4EA4\u4F5C\u4E1A\u65F6\u81EA\u5DF1\u5199\u7684\u300D\u2014\u2014\u4E00\u53E5\u5047\u8BDD\uFF08\u5B9E\u8BC1\uFF1Aws:wo-2481b4cb \u7684\u4E0B\u6E38
|
|
260076
|
+
-- \u300C\u8C03\u7814\u62A5\u544A\u300D\u6536\u5230\u7684 _HANDOFF.md \u53EA\u6709\u4E00\u884C - [next] \u63D0\u4EA4\u5E76\u7ED3\u6848\uFF09\u3002\u6539\u6210\u7A7A\u4E4B\u540E\uFF0C\u8FD9\u4E00\u680F\u8981\u4E48\u662F
|
|
260077
|
+
-- \u6267\u884C\u4EBA propose --next \u4EB2\u624B\u5199\u7684\uFF0C\u8981\u4E48\u6CA1\u6709\u3002
|
|
260078
|
+
--
|
|
260079
|
+
-- pendingSteps \u4E0D\u4F1A\u56E0\u6B64\u4E22\u5931\uFF1A\u4E2D\u65AD\u7EED\u8DD1\u8BFB\u7684\u662F checkpoint \u672C\u8EAB\uFF08RESUME.md\uFF09\uFF0C\u534F\u8C03\u8005\u89C6\u56FE\u5BF9
|
|
260080
|
+
-- running/stalled/blocked/failed \u8282\u70B9\u5355\u72EC\u6E32\u67D3 Pending \u884C\uFF08progress \u6BB5\uFF09\uFF0C\u4E24\u5904\u90FD\u4E0D\u8BFB\u8FD9\u4E00\u680F\u3002
|
|
260081
|
+
'[]'::jsonb, reconstructed_gaps, NULL,
|
|
259321
260082
|
COALESCE(NEW.ended_at, NEW.updated_at, NEW.started_at))
|
|
259322
260083
|
-- \u2605 2026-08-17\uFF1A\u7EC8\u6001\u91CD\u5EFA\u4ECE\u300C\u6574\u884C\u8986\u76D6\u300D\u6539\u6210\u300C**\u53EA\u586B\u7A7A**\u300D\u3002
|
|
259323
260084
|
--
|
|
@@ -265097,7 +265858,7 @@ var init_postgres_delegations = __esm({
|
|
|
265097
265858
|
});
|
|
265098
265859
|
|
|
265099
265860
|
// ../storage/src/postgres-nodes.ts
|
|
265100
|
-
var import_node_crypto75, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToRuntime;
|
|
265861
|
+
var import_node_crypto75, ident13, PostgresNodeStore, PostgresNodeTokenStore, rowToNode, rowToFault, rowToQuota, rowToRuntime;
|
|
265101
265862
|
var init_postgres_nodes = __esm({
|
|
265102
265863
|
"../storage/src/postgres-nodes.ts"() {
|
|
265103
265864
|
"use strict";
|
|
@@ -265147,6 +265908,33 @@ var init_postgres_nodes = __esm({
|
|
|
265147
265908
|
await pool.query(`CREATE UNIQUE INDEX IF NOT EXISTS node_runtimes_company_id_uq ON "${s2}".node_runtimes (company_id, id)`);
|
|
265148
265909
|
await pool.query(`CREATE INDEX IF NOT EXISTS node_runtimes_company_node_idx ON "${s2}".node_runtimes (company_id, node_id)`);
|
|
265149
265910
|
await pool.query(`CREATE INDEX IF NOT EXISTS node_runtimes_company_status_idx ON "${s2}".node_runtimes (company_id, status)`);
|
|
265911
|
+
await pool.query(`ALTER TABLE "${s2}".nodes ADD COLUMN IF NOT EXISTS max_concurrent integer`);
|
|
265912
|
+
await pool.query(`
|
|
265913
|
+
CREATE TABLE IF NOT EXISTS "${s2}".node_runtime_faults (
|
|
265914
|
+
node_id text NOT NULL REFERENCES "${s2}".nodes(id) ON DELETE CASCADE,
|
|
265915
|
+
runtime_kind text NOT NULL,
|
|
265916
|
+
company_id text,
|
|
265917
|
+
reason text NOT NULL,
|
|
265918
|
+
detail text,
|
|
265919
|
+
marked_at timestamptz NOT NULL,
|
|
265920
|
+
attempts integer NOT NULL DEFAULT 0,
|
|
265921
|
+
next_probe_at timestamptz,
|
|
265922
|
+
awaiting_manual boolean NOT NULL DEFAULT false,
|
|
265923
|
+
PRIMARY KEY (node_id, runtime_kind)
|
|
265924
|
+
)`);
|
|
265925
|
+
await pool.query(`CREATE INDEX IF NOT EXISTS node_runtime_faults_company_idx ON "${s2}".node_runtime_faults (company_id)`);
|
|
265926
|
+
await pool.query(`
|
|
265927
|
+
CREATE TABLE IF NOT EXISTS "${s2}".node_runtime_quotas (
|
|
265928
|
+
node_id text NOT NULL REFERENCES "${s2}".nodes(id) ON DELETE CASCADE,
|
|
265929
|
+
runtime_kind text NOT NULL,
|
|
265930
|
+
company_id text,
|
|
265931
|
+
status text NOT NULL,
|
|
265932
|
+
checked_at timestamptz NOT NULL,
|
|
265933
|
+
retry_at timestamptz,
|
|
265934
|
+
windows jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
265935
|
+
PRIMARY KEY (node_id, runtime_kind)
|
|
265936
|
+
)`);
|
|
265937
|
+
await pool.query(`CREATE INDEX IF NOT EXISTS node_runtime_quotas_company_idx ON "${s2}".node_runtime_quotas (company_id)`);
|
|
265150
265938
|
return new _PostgresNodeStore(pool, schema);
|
|
265151
265939
|
}
|
|
265152
265940
|
async upsertNode(n) {
|
|
@@ -265300,6 +266088,95 @@ var init_postgres_nodes = __esm({
|
|
|
265300
266088
|
}
|
|
265301
266089
|
return { nodeDeleted: false };
|
|
265302
266090
|
}
|
|
266091
|
+
/* ---------- 运行时分配(ADR 0616):并发上限 / 故障标记 / 落库额度 ---------- */
|
|
266092
|
+
async setNodeMaxConcurrent(nodeId, limit) {
|
|
266093
|
+
await this.pool.query(`UPDATE ${this.s}.nodes SET max_concurrent=$2 WHERE id=$1`, [nodeId, limit]);
|
|
266094
|
+
}
|
|
266095
|
+
async upsertRuntimeFault(fault) {
|
|
266096
|
+
await this.pool.query(
|
|
266097
|
+
// company_id 从 node 行继承(node 是唯一事实源,同 upsertRuntime 的兜底),任何调用方都绕不过去。
|
|
266098
|
+
`INSERT INTO ${this.s}.node_runtime_faults
|
|
266099
|
+
(node_id, runtime_kind, company_id, reason, detail, marked_at, attempts, next_probe_at, awaiting_manual)
|
|
266100
|
+
VALUES ($1,$2,(SELECT company_id FROM ${this.s}.nodes WHERE id=$1),$3,$4,$5,$6,$7,$8)
|
|
266101
|
+
ON CONFLICT (node_id, runtime_kind) DO UPDATE SET
|
|
266102
|
+
company_id = COALESCE(EXCLUDED.company_id, ${this.s}.node_runtime_faults.company_id),
|
|
266103
|
+
reason=EXCLUDED.reason, detail=EXCLUDED.detail, marked_at=EXCLUDED.marked_at,
|
|
266104
|
+
attempts=EXCLUDED.attempts, next_probe_at=EXCLUDED.next_probe_at, awaiting_manual=EXCLUDED.awaiting_manual`,
|
|
266105
|
+
[
|
|
266106
|
+
fault.nodeId,
|
|
266107
|
+
fault.runtimeKind,
|
|
266108
|
+
fault.reason,
|
|
266109
|
+
fault.detail ?? null,
|
|
266110
|
+
fault.markedAt,
|
|
266111
|
+
fault.attempts,
|
|
266112
|
+
fault.nextProbeAt ?? null,
|
|
266113
|
+
fault.awaitingManual
|
|
266114
|
+
]
|
|
266115
|
+
);
|
|
266116
|
+
}
|
|
266117
|
+
async clearRuntimeFault(nodeId, runtimeKind) {
|
|
266118
|
+
const r = await this.pool.query(
|
|
266119
|
+
`DELETE FROM ${this.s}.node_runtime_faults WHERE node_id=$1 AND runtime_kind=$2`,
|
|
266120
|
+
[nodeId, runtimeKind]
|
|
266121
|
+
);
|
|
266122
|
+
return (r.rowCount ?? 0) > 0;
|
|
266123
|
+
}
|
|
266124
|
+
async listRuntimeFaults(filter) {
|
|
266125
|
+
const where = [];
|
|
266126
|
+
const args = [];
|
|
266127
|
+
if (filter?.nodeId) {
|
|
266128
|
+
args.push(filter.nodeId);
|
|
266129
|
+
where.push(`node_id = $${args.length}`);
|
|
266130
|
+
}
|
|
266131
|
+
if (filter?.companyId) {
|
|
266132
|
+
args.push(filter.companyId);
|
|
266133
|
+
where.push(`company_id = $${args.length}`);
|
|
266134
|
+
}
|
|
266135
|
+
const r = await this.pool.query(
|
|
266136
|
+
`SELECT * FROM ${this.s}.node_runtime_faults${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY node_id, runtime_kind`,
|
|
266137
|
+
args
|
|
266138
|
+
);
|
|
266139
|
+
return r.rows.map(rowToFault);
|
|
266140
|
+
}
|
|
266141
|
+
async saveRuntimeQuotas(nodeId, quotas) {
|
|
266142
|
+
const client = await this.pool.connect();
|
|
266143
|
+
try {
|
|
266144
|
+
await client.query("BEGIN");
|
|
266145
|
+
await client.query(`DELETE FROM ${this.s}.node_runtime_quotas WHERE node_id=$1`, [nodeId]);
|
|
266146
|
+
for (const q2 of quotas) {
|
|
266147
|
+
await client.query(
|
|
266148
|
+
`INSERT INTO ${this.s}.node_runtime_quotas
|
|
266149
|
+
(node_id, runtime_kind, company_id, status, checked_at, retry_at, windows)
|
|
266150
|
+
VALUES ($1,$2,(SELECT company_id FROM ${this.s}.nodes WHERE id=$1),$3,$4,$5,$6::jsonb)`,
|
|
266151
|
+
[nodeId, q2.runtimeKind, q2.status, q2.checkedAt, q2.retryAt ?? null, JSON.stringify(q2.windows ?? [])]
|
|
266152
|
+
);
|
|
266153
|
+
}
|
|
266154
|
+
await client.query("COMMIT");
|
|
266155
|
+
} catch (err) {
|
|
266156
|
+
await client.query("ROLLBACK").catch(() => {
|
|
266157
|
+
});
|
|
266158
|
+
throw err;
|
|
266159
|
+
} finally {
|
|
266160
|
+
client.release();
|
|
266161
|
+
}
|
|
266162
|
+
}
|
|
266163
|
+
async listRuntimeQuotas(filter) {
|
|
266164
|
+
const where = [];
|
|
266165
|
+
const args = [];
|
|
266166
|
+
if (filter?.nodeId) {
|
|
266167
|
+
args.push(filter.nodeId);
|
|
266168
|
+
where.push(`node_id = $${args.length}`);
|
|
266169
|
+
}
|
|
266170
|
+
if (filter?.companyId) {
|
|
266171
|
+
args.push(filter.companyId);
|
|
266172
|
+
where.push(`company_id = $${args.length}`);
|
|
266173
|
+
}
|
|
266174
|
+
const r = await this.pool.query(
|
|
266175
|
+
`SELECT * FROM ${this.s}.node_runtime_quotas${where.length ? ` WHERE ${where.join(" AND ")}` : ""} ORDER BY node_id, runtime_kind`,
|
|
266176
|
+
args
|
|
266177
|
+
);
|
|
266178
|
+
return r.rows.map(rowToQuota);
|
|
266179
|
+
}
|
|
265303
266180
|
};
|
|
265304
266181
|
PostgresNodeTokenStore = class _PostgresNodeTokenStore {
|
|
265305
266182
|
constructor(pool, schema) {
|
|
@@ -265342,7 +266219,28 @@ var init_postgres_nodes = __esm({
|
|
|
265342
266219
|
nodeVersion: row.node_version,
|
|
265343
266220
|
status: row.status,
|
|
265344
266221
|
lastSeenAt: new Date(row.last_seen_at).toISOString(),
|
|
265345
|
-
enrolledAt: new Date(row.enrolled_at).toISOString()
|
|
266222
|
+
enrolledAt: new Date(row.enrolled_at).toISOString(),
|
|
266223
|
+
// 只在库里真有值时带上这一格:`undefined` 与 `null` 在 `exactOptionalPropertyTypes` 下不等价,
|
|
266224
|
+
// 而判据只认「有没有上限」——带一个 null 进去会让下游不得不再判一次。
|
|
266225
|
+
...row.max_concurrent != null ? { maxConcurrent: Number(row.max_concurrent) } : {}
|
|
266226
|
+
});
|
|
266227
|
+
rowToFault = (row) => ({
|
|
266228
|
+
nodeId: row.node_id,
|
|
266229
|
+
runtimeKind: row.runtime_kind,
|
|
266230
|
+
reason: row.reason,
|
|
266231
|
+
...row.detail ? { detail: row.detail } : {},
|
|
266232
|
+
markedAt: new Date(row.marked_at).toISOString(),
|
|
266233
|
+
attempts: Number(row.attempts ?? 0),
|
|
266234
|
+
...row.next_probe_at ? { nextProbeAt: new Date(row.next_probe_at).toISOString() } : {},
|
|
266235
|
+
awaitingManual: Boolean(row.awaiting_manual)
|
|
266236
|
+
});
|
|
266237
|
+
rowToQuota = (row) => ({
|
|
266238
|
+
nodeId: row.node_id,
|
|
266239
|
+
runtimeKind: row.runtime_kind,
|
|
266240
|
+
status: row.status,
|
|
266241
|
+
checkedAt: new Date(row.checked_at).toISOString(),
|
|
266242
|
+
...row.retry_at ? { retryAt: new Date(row.retry_at).toISOString() } : {},
|
|
266243
|
+
windows: Array.isArray(row.windows) ? row.windows : []
|
|
265346
266244
|
});
|
|
265347
266245
|
rowToRuntime = (row) => ({
|
|
265348
266246
|
id: row.id,
|
|
@@ -268544,6 +269442,16 @@ var init_cutover_aware_registry = __esm({
|
|
|
268544
269442
|
async listBindings() {
|
|
268545
269443
|
return (await this.orgStore()).listBindings();
|
|
268546
269444
|
}
|
|
269445
|
+
// 候选 runtime(ADR 0616):与 binding 同一侧(组织面);底座没实现就退化成「没有候选」。
|
|
269446
|
+
async listRuntimeCandidates(actorId) {
|
|
269447
|
+
const store = await this.orgStore();
|
|
269448
|
+
return store.listRuntimeCandidates ? store.listRuntimeCandidates(actorId) : [];
|
|
269449
|
+
}
|
|
269450
|
+
async replaceRuntimeCandidates(actorId, candidates) {
|
|
269451
|
+
const store = await this.orgStoreForWrite();
|
|
269452
|
+
if (!store.replaceRuntimeCandidates) throw new Error("\u5F53\u524D\u767B\u8BB0\u5B58\u50A8\u5B9E\u73B0\u4E0D\u652F\u6301\u5019\u9009\u8FD0\u884C\u65F6\uFF08replaceRuntimeCandidates \u672A\u5B9E\u73B0\uFF09");
|
|
269453
|
+
return store.replaceRuntimeCandidates(actorId, candidates);
|
|
269454
|
+
}
|
|
268547
269455
|
async upsertRuntimeConfig(...args) {
|
|
268548
269456
|
return (await this.orgStoreForWrite()).upsertRuntimeConfig(...args);
|
|
268549
269457
|
}
|
|
@@ -274267,6 +275175,7 @@ __export(src_exports2, {
|
|
|
274267
275175
|
MemoryWriteConflictError: () => MemoryWriteConflictError,
|
|
274268
275176
|
MirrorEngine: () => MirrorEngine,
|
|
274269
275177
|
MissingCompanyContextError: () => MissingCompanyContextError,
|
|
275178
|
+
NODE_HEARTBEAT_STALE_MS: () => NODE_HEARTBEAT_STALE_MS,
|
|
274270
275179
|
NPM_PROBE_TIMEOUT_MS: () => NPM_PROBE_TIMEOUT_MS,
|
|
274271
275180
|
NdjsonOplogStore: () => NdjsonOplogStore,
|
|
274272
275181
|
NodeHealthTracker: () => NodeHealthTracker,
|
|
@@ -274296,6 +275205,7 @@ __export(src_exports2, {
|
|
|
274296
275205
|
RolesAdminService: () => RolesAdminService,
|
|
274297
275206
|
Router: () => Router,
|
|
274298
275207
|
RunSettlementFunnel: () => RunSettlementFunnel,
|
|
275208
|
+
RuntimeAllocationError: () => RuntimeAllocationError,
|
|
274299
275209
|
RuntimeStartMarker: () => RuntimeStartMarker,
|
|
274300
275210
|
SERVER_PING: () => SERVER_PING,
|
|
274301
275211
|
SHARED_GRAPH_BODY: () => SHARED_GRAPH_BODY,
|
|
@@ -274319,6 +275229,7 @@ __export(src_exports2, {
|
|
|
274319
275229
|
actorStats: () => actorStats,
|
|
274320
275230
|
actorsDomain: () => actorsDomain,
|
|
274321
275231
|
agentDefaultRuntimeForNewSession: () => agentDefaultRuntimeForNewSession,
|
|
275232
|
+
allocateRuntime: () => allocateRuntime,
|
|
274322
275233
|
answerFallbackReason: () => answerFallbackReason,
|
|
274323
275234
|
appendChatMessageOnce: () => appendChatMessageOnce,
|
|
274324
275235
|
applyBuiltinSkillVariants: () => applyBuiltinSkillVariants,
|
|
@@ -274329,6 +275240,7 @@ __export(src_exports2, {
|
|
|
274329
275240
|
assertCreatableVariable: () => assertCreatableVariable,
|
|
274330
275241
|
assistantRowRecovery: () => assistantRowRecovery,
|
|
274331
275242
|
assistantsDomain: () => assistantsDomain,
|
|
275243
|
+
atCapacity: () => atCapacity,
|
|
274332
275244
|
automationsDomain: () => automationsDomain,
|
|
274333
275245
|
backfillOrgModel: () => backfillOrgModel,
|
|
274334
275246
|
backfillOrgModelOnStartup: () => backfillOrgModelOnStartup,
|
|
@@ -274361,6 +275273,7 @@ __export(src_exports2, {
|
|
|
274361
275273
|
buildWorkorderSummaries: () => buildWorkorderSummaries,
|
|
274362
275274
|
builtinSkillToFiles: () => builtinSkillToFiles,
|
|
274363
275275
|
canAnnounceRuntimeSwitch: () => canAnnounceRuntimeSwitch,
|
|
275276
|
+
canRetryAllocation: () => canRetryAllocation,
|
|
274364
275277
|
chatBroadcastEnabledFromEnv: () => chatBroadcastEnabledFromEnv,
|
|
274365
275278
|
chatDelegationEnabledFromEnv: () => chatDelegationEnabledFromEnv,
|
|
274366
275279
|
chatDispatchScope: () => chatDispatchScope,
|
|
@@ -274428,6 +275341,7 @@ __export(src_exports2, {
|
|
|
274428
275341
|
createRegistryAuditSink: () => createRegistryAuditSink,
|
|
274429
275342
|
createReworkHandoffInputResolver: () => createReworkHandoffInputResolver,
|
|
274430
275343
|
createRunSettlementWiring: () => createRunSettlementWiring,
|
|
275344
|
+
createRuntimeAllocator: () => createRuntimeAllocator,
|
|
274431
275345
|
createSkillMarketClientFromEnv: () => createSkillMarketClientFromEnv,
|
|
274432
275346
|
createSpawnMutex: () => createSpawnMutex,
|
|
274433
275347
|
createTokenIssuer: () => createTokenIssuer,
|
|
@@ -274452,8 +275366,10 @@ __export(src_exports2, {
|
|
|
274452
275366
|
deliverDelegationReturn: () => deliverDelegationReturn,
|
|
274453
275367
|
deliverWorkorderBroadcast: () => deliverWorkorderBroadcast,
|
|
274454
275368
|
deriveNodeHealth: () => deriveNodeHealth,
|
|
275369
|
+
describeAllocationChoice: () => describeAllocationChoice,
|
|
274455
275370
|
describeAttachmentsForAppend: () => describeAttachmentsForAppend,
|
|
274456
275371
|
describeNodeFault: () => describeNodeFault,
|
|
275372
|
+
describeRejections: () => describeRejections,
|
|
274457
275373
|
deterministicKnowledgePlan: () => deterministicKnowledgePlan,
|
|
274458
275374
|
deterministicUuid: () => deterministicUuid,
|
|
274459
275375
|
digestOf: () => digestOf2,
|
|
@@ -274480,6 +275396,8 @@ __export(src_exports2, {
|
|
|
274480
275396
|
fallbackTurnId: () => fallbackTurnId,
|
|
274481
275397
|
fastPathFallbackReason: () => fastPathFallbackReason,
|
|
274482
275398
|
fastPathQualityReason: () => fastPathQualityReason,
|
|
275399
|
+
faultFromQuota: () => faultFromQuota,
|
|
275400
|
+
faultIsBlocking: () => faultIsBlocking,
|
|
274483
275401
|
fenceDispatch: () => fenceDispatch,
|
|
274484
275402
|
fetchLatestNpmVersion: () => fetchLatestNpmVersion,
|
|
274485
275403
|
fetchSkillFromSource: () => fetchSkillFromSource,
|
|
@@ -274495,6 +275413,7 @@ __export(src_exports2, {
|
|
|
274495
275413
|
hashPassword: () => hashPassword,
|
|
274496
275414
|
hideDeleted: () => hideDeleted,
|
|
274497
275415
|
humanSubmitSourceRunId: () => humanSubmitSourceRunId,
|
|
275416
|
+
idlenessOf: () => idlenessOf,
|
|
274498
275417
|
isActiveCoordinatorAgent: () => isActiveCoordinatorAgent,
|
|
274499
275418
|
isCompanyCrossTenantPath: () => isCompanyCrossTenantPath,
|
|
274500
275419
|
isCoordinatorAgentName: () => isCoordinatorAgentName,
|
|
@@ -274513,6 +275432,7 @@ __export(src_exports2, {
|
|
|
274513
275432
|
isNodeHealthy: () => isNodeHealthy,
|
|
274514
275433
|
isOperatorRequest: () => isOperatorRequest,
|
|
274515
275434
|
isRunTerminalStatus: () => isRunTerminalStatus,
|
|
275435
|
+
isRuntimeClassFailure: () => isRuntimeClassFailure,
|
|
274516
275436
|
isRuntimeDrifted: () => isRuntimeDrifted,
|
|
274517
275437
|
isRuntimeRunReader: () => isRuntimeRunReader,
|
|
274518
275438
|
isSealedKnowledgeSnapshotContent: () => isSealedKnowledgeSnapshotContent,
|
|
@@ -274609,6 +275529,7 @@ __export(src_exports2, {
|
|
|
274609
275529
|
providerKnowledgeHash: () => providerKnowledgeHash,
|
|
274610
275530
|
providerRoundTripKnowledgeHash: () => providerRoundTripKnowledgeHash,
|
|
274611
275531
|
publishMarker: () => publishMarker,
|
|
275532
|
+
quotaDerateFactor: () => quotaDerateFactor,
|
|
274612
275533
|
readCredentialAuditLog: () => readCredentialAuditLog,
|
|
274613
275534
|
readLocalPack: () => readLocalPack,
|
|
274614
275535
|
readRegisteredToolVersions: () => readRegisteredToolVersions,
|
|
@@ -274646,6 +275567,7 @@ __export(src_exports2, {
|
|
|
274646
275567
|
repairSubmissionDelivery: () => repairSubmissionDelivery,
|
|
274647
275568
|
repairSubmissionDeliveryAcrossCompanies: () => repairSubmissionDeliveryAcrossCompanies,
|
|
274648
275569
|
replaceModelPriceCacheForTest: () => replaceModelPriceCacheForTest,
|
|
275570
|
+
requireAllocation: () => requireAllocation,
|
|
274649
275571
|
requiresKnowledgePlanner: () => requiresKnowledgePlanner,
|
|
274650
275572
|
resendSender: () => resendSender,
|
|
274651
275573
|
resetBuildInfoCacheForTest: () => resetBuildInfoCacheForTest,
|
|
@@ -274669,6 +275591,7 @@ __export(src_exports2, {
|
|
|
274669
275591
|
runtimeConnectionStatus: () => runtimeConnectionStatus,
|
|
274670
275592
|
runtimeDriftBlock: () => runtimeDriftBlock,
|
|
274671
275593
|
runtimeSwitchedNotice: () => runtimeSwitchedNotice,
|
|
275594
|
+
runtimeTargetKey: () => runtimeTargetKey,
|
|
274672
275595
|
safeAttachmentName: () => safeAttachmentName,
|
|
274673
275596
|
sanitizeSkillDir: () => sanitizeSkillDir,
|
|
274674
275597
|
scanFrontmatterEntries: () => scanFrontmatterEntries,
|
|
@@ -274804,6 +275727,9 @@ var init_src11 = __esm({
|
|
|
274804
275727
|
init_coordinator_runs();
|
|
274805
275728
|
init_usage_read();
|
|
274806
275729
|
init_runtime_drift();
|
|
275730
|
+
init_allocate();
|
|
275731
|
+
init_errors6();
|
|
275732
|
+
init_service7();
|
|
274807
275733
|
init_runtime_status();
|
|
274808
275734
|
init_runtime_tenancy();
|
|
274809
275735
|
init_types4();
|
|
@@ -275179,6 +276105,7 @@ var import_node_crypto91 = require("node:crypto");
|
|
|
275179
276105
|
|
|
275180
276106
|
// ../cli/src/serve.ts
|
|
275181
276107
|
init_src11();
|
|
276108
|
+
init_src11();
|
|
275182
276109
|
init_chat_cancel_attribution();
|
|
275183
276110
|
init_src();
|
|
275184
276111
|
init_src11();
|
|
@@ -280163,6 +281090,59 @@ async function startServe(opts) {
|
|
|
280163
281090
|
return (await ctx.service.latestConfig(actorId))?.model ?? null;
|
|
280164
281091
|
};
|
|
280165
281092
|
agentDefaultRuntimeResolvers = { resolveBinding: resolveCompanyBinding, resolveActorModel: resolveAgentConfiguredModel };
|
|
281093
|
+
const runtimeAllocators = /* @__PURE__ */ new Map();
|
|
281094
|
+
const runtimeAllocatorFor = (companyId) => {
|
|
281095
|
+
const hit = runtimeAllocators.get(companyId);
|
|
281096
|
+
if (hit) return hit;
|
|
281097
|
+
const allocator = createRuntimeAllocator({
|
|
281098
|
+
// 主绑定与候选清单都**按公司解析**:全局 registry 对已切 `actor-config` 的公司是一份冻结旧镜像
|
|
281099
|
+
//(理由与 `resolveCompanyBinding` 文件头同源)。
|
|
281100
|
+
primaryBinding: (actorId) => resolveCompanyBinding(companyId, actorId),
|
|
281101
|
+
candidates: async (actorId) => {
|
|
281102
|
+
const registry2 = (await getEngine(companyId)).registry;
|
|
281103
|
+
return registry2.listRuntimeCandidates ? registry2.listRuntimeCandidates(actorId) : [];
|
|
281104
|
+
},
|
|
281105
|
+
nodeStates: async () => {
|
|
281106
|
+
const [nodes, runtimes] = await Promise.all([
|
|
281107
|
+
nodeStore.listNodes({ companyId }),
|
|
281108
|
+
nodeStore.listRuntimes(void 0, { companyId })
|
|
281109
|
+
]);
|
|
281110
|
+
return nodes.filter((n) => n.status !== "deleted").map((n) => ({
|
|
281111
|
+
nodeId: n.id,
|
|
281112
|
+
// 报名称不报 id(ADR 0612):节点名 → 主机名 → 万不得已才是 id。
|
|
281113
|
+
displayName: n.name?.trim() || n.hostname || n.id,
|
|
281114
|
+
/**
|
|
281115
|
+
* 「在线」只取**可达性**:长连接在 + 心跳新鲜。刻意**不用** `healthOfNode(...).healthy`
|
|
281116
|
+
* ——那个判据还叠了「连败 ≥ 3 次」这一档软不健康。
|
|
281117
|
+
*
|
|
281118
|
+
* 为什么不叠:委派子会话此前走的是 `bindingRuntimeState`(在线 + 上报了这个 adapter),
|
|
281119
|
+
* 连败次数根本不参与。把它叠进来,一台**还连着、还能跑**但最近连败过三次的机器会被分配
|
|
281120
|
+
* 排除掉,而这个员工要是没配候选,那一轮就从「照旧派出去」变成当场 503 —— 拿一个新功能
|
|
281121
|
+
* 去掐掉一条本来能走的路。
|
|
281122
|
+
*
|
|
281123
|
+
* 连败这类「机器看着在、但活总是挂」的判断归**故障标记**(ADR 0616 D8,第 2 批):
|
|
281124
|
+
* 它会写进 `node_runtime_faults`,那时分配读的是故障行,粒度是 (节点, runtime) 而不是整台机器,
|
|
281125
|
+
* 也带得上「下次什么时候再试」。工单派发那条路的 `checkDispatchable` 仍按原样拦连败节点,
|
|
281126
|
+
* 与改动前逐字相同。
|
|
281127
|
+
*/
|
|
281128
|
+
online: (hub?.connectedDaemons() ?? []).some((d) => d.daemonId === n.id) && (() => {
|
|
281129
|
+
const seen = hub?.lastSeenAt(n.id);
|
|
281130
|
+
return seen !== void 0 && Date.now() - seen < NODE_HEARTBEAT_STALE_MS;
|
|
281131
|
+
})(),
|
|
281132
|
+
/* 机器上有什么 runtime:漂移行(登记表有、机器探测不到)不算有——判据同 `runtimeDriftBlock`。
|
|
281133
|
+
`presentOnMachine` 缺省(还没对账过的旧行)一律当作正常,与那道闸逐字一致。 */
|
|
281134
|
+
runtimeKinds: runtimes.filter((r) => r.nodeId === n.id && r.presentOnMachine !== false).map((r) => r.kind),
|
|
281135
|
+
maxConcurrent: n.maxConcurrent ?? null,
|
|
281136
|
+
activeRunCount: activeRunCountOf(n.id)
|
|
281137
|
+
}));
|
|
281138
|
+
},
|
|
281139
|
+
faults: () => nodeStore.listRuntimeFaults({ companyId }),
|
|
281140
|
+
quotas: () => nodeStore.listRuntimeQuotas({ companyId }),
|
|
281141
|
+
actorName: async (actorId) => (await (await actors.resolveCtx(companyId)).service.getActor(actorId).catch(() => null))?.name ?? null
|
|
281142
|
+
});
|
|
281143
|
+
runtimeAllocators.set(companyId, allocator);
|
|
281144
|
+
return allocator;
|
|
281145
|
+
};
|
|
280166
281146
|
if (pgPool) {
|
|
280167
281147
|
const pinPool = pgPool;
|
|
280168
281148
|
void (async () => {
|
|
@@ -281412,6 +282392,7 @@ async function startServe(opts) {
|
|
|
281412
282392
|
const priorChatSession = chatSessionId && turnChatStore ? await turnChatStore.getSession(chatSessionId) : null;
|
|
281413
282393
|
let binding;
|
|
281414
282394
|
let runtimeTargetSource = "actor_binding";
|
|
282395
|
+
let allocatedModelOverride;
|
|
281415
282396
|
if (stableDispatch) {
|
|
281416
282397
|
binding = { actorId, ...stableDispatch.target, status: "active" };
|
|
281417
282398
|
} else {
|
|
@@ -281438,14 +282419,42 @@ async function startServe(opts) {
|
|
|
281438
282419
|
}
|
|
281439
282420
|
});
|
|
281440
282421
|
} catch (e) {
|
|
281441
|
-
if (e instanceof SessionRuntimeOverrideError)
|
|
281442
|
-
|
|
282422
|
+
if (e instanceof SessionRuntimeOverrideError) {
|
|
282423
|
+
if (!delegatedChildTurn) throw new ApiError(e.status, e.code, e.message);
|
|
282424
|
+
console.log(`[runtime-alloc] \u59D4\u6D3E\u5B50\u4F1A\u8BDD\u9489\u4F4F\u7684\u76EE\u6807\u4E0D\u53EF\u7528\uFF08${e.code}\uFF09\uFF0C\u4EA4\u7ED9\u5206\u914D\u6311\u5019\u9009`);
|
|
282425
|
+
resolved = null;
|
|
282426
|
+
} else throw e;
|
|
281443
282427
|
}
|
|
281444
|
-
if (!resolved) {
|
|
282428
|
+
if (!resolved && !delegatedChildTurn) {
|
|
281445
282429
|
throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u8FD9\u4F4D Agent \u8FD8\u6CA1\u6709\u7ED1\u5B9A\u8FD0\u884C\u65F6\uFF0C\u8BF7${chatRebindHint("\u7ED1\u5B9A\u4E00\u4E2A\u53EF\u7528\u7684\u8FD0\u884C\u65F6")}\u540E\u518D\u53D1`);
|
|
281446
282430
|
}
|
|
281447
|
-
binding = resolved
|
|
281448
|
-
runtimeTargetSource = resolved
|
|
282431
|
+
binding = resolved?.binding ?? { actorId, nodeId: "", runtimeKind: "", status: "active" };
|
|
282432
|
+
runtimeTargetSource = resolved?.source ?? "actor_binding";
|
|
282433
|
+
if (delegatedChildTurn) {
|
|
282434
|
+
const pinned = sessionRuntimeOverride(priorChatSession);
|
|
282435
|
+
const primary = resolved ? { nodeId: resolved.binding.nodeId, runtimeKind: resolved.binding.runtimeKind } : pinned ? { nodeId: pinned.nodeId, runtimeKind: pinned.runtimeKind } : null;
|
|
282436
|
+
try {
|
|
282437
|
+
const allocated = await runtimeAllocatorFor(runCompanyId).allocateFor({
|
|
282438
|
+
actorId,
|
|
282439
|
+
// 「同一件活」= 这条子会话。重试上限算在它头上,不与同一位专家的别条委派互相扣次数。
|
|
282440
|
+
unitOfWork: `chat-session:${chatSessionId ?? artifactId}`,
|
|
282441
|
+
what: "\u59D4\u6D3E\u5B50\u4F1A\u8BDD",
|
|
282442
|
+
primary
|
|
282443
|
+
});
|
|
282444
|
+
if (allocated.binding.nodeId !== binding.nodeId || allocated.binding.runtimeKind !== binding.runtimeKind) {
|
|
282445
|
+
binding = allocated.binding;
|
|
282446
|
+
runtimeTargetSource = "session_override";
|
|
282447
|
+
allocatedModelOverride = allocated.model ?? void 0;
|
|
282448
|
+
}
|
|
282449
|
+
} catch (e) {
|
|
282450
|
+
if (e instanceof RuntimeAllocationError) throw new ApiError(e.status === 503 ? 503 : 409, e.code, e.message);
|
|
282451
|
+
console.warn(`[runtime-alloc] \u59D4\u6D3E\u5B50\u4F1A\u8BDD\u5206\u914D\u5931\u8D25\uFF1A${String(e)}`);
|
|
282452
|
+
if (!resolved) throw e;
|
|
282453
|
+
}
|
|
282454
|
+
}
|
|
282455
|
+
if (!binding.nodeId || !binding.runtimeKind) {
|
|
282456
|
+
throw new ApiError(409, "ACTOR_BINDING_MISSING", `\u8FD9\u4F4D Agent \u8FD8\u6CA1\u6709\u53EF\u7528\u7684\u8FD0\u884C\u65F6\uFF0C\u8BF7${chatRebindHint("\u7ED1\u5B9A\u4E00\u4E2A\u53EF\u7528\u7684\u8FD0\u884C\u65F6")}\u540E\u518D\u53D1`);
|
|
282457
|
+
}
|
|
281449
282458
|
}
|
|
281450
282459
|
const requireDispatchNode = async (target) => {
|
|
281451
282460
|
const targetNode = await nodeStore.getNode(target.nodeId);
|
|
@@ -281669,7 +282678,7 @@ async function startServe(opts) {
|
|
|
281669
282678
|
};
|
|
281670
282679
|
const provision = isolatedKnowledgeTurn ? { env: {}, wrapperPaths: [], requiredTools: [], requiredToolVersions: {}, connectorCreds: [], cleanup: async () => void 0 } : await buildActorProvision(actorService, actorId, chatDispatchScope(priorChatSession));
|
|
281671
282680
|
const actorCtx = await buildActorContext(actorService, actorId).catch(() => null);
|
|
281672
|
-
const sessionModel = configuredModel(sessionModelOverride);
|
|
282681
|
+
const sessionModel = configuredModel(allocatedModelOverride !== void 0 ? allocatedModelOverride : sessionModelOverride);
|
|
281673
282682
|
const actorConfiguredModel = isolatedBackground ? configuredModel(actorCtx?.config?.model) : void 0;
|
|
281674
282683
|
const chatResolvedModel = sessionModel ?? actorConfiguredModel;
|
|
281675
282684
|
const chatModel = chatResolvedModel ?? await registryStore.getRuntimeConfig(`runtime:${binding.nodeId}:${binding.runtimeKind}`).then((c) => configuredModel(c?.model)).catch(() => void 0);
|
|
@@ -281874,6 +282883,24 @@ async function startServe(opts) {
|
|
|
281874
282883
|
const done = new Promise((resolve11, reject) => {
|
|
281875
282884
|
handle.onExit((info) => {
|
|
281876
282885
|
chatLiveSessions.delete(chatJobKey);
|
|
282886
|
+
if (delegatedChildTurn) {
|
|
282887
|
+
try {
|
|
282888
|
+
const unitOfWork = `chat-session:${chatSessionId ?? artifactId}`;
|
|
282889
|
+
const allocator = runtimeAllocatorFor(runCompanyId);
|
|
282890
|
+
if (isRuntimeClassFailure(info.reason, info.errorMessage)) {
|
|
282891
|
+
allocator.noteFailure({
|
|
282892
|
+
unitOfWork,
|
|
282893
|
+
target: { nodeId: binding.nodeId, runtimeKind: binding.runtimeKind },
|
|
282894
|
+
...info.reason ? { reason: info.reason } : {},
|
|
282895
|
+
...info.errorMessage ? { errorMessage: info.errorMessage } : {}
|
|
282896
|
+
});
|
|
282897
|
+
} else {
|
|
282898
|
+
allocator.noteSettled(unitOfWork);
|
|
282899
|
+
}
|
|
282900
|
+
} catch (err) {
|
|
282901
|
+
console.warn(`[runtime-alloc] \u59D4\u6D3E\u5B50\u4F1A\u8BDD\u5931\u8D25\u56DE\u62A5\u51FA\u9519\uFF08\u4E0D\u5F71\u54CD\u6536\u5C3E\uFF09\uFF1A${String(err)}`);
|
|
282902
|
+
}
|
|
282903
|
+
}
|
|
281877
282904
|
setTimeout(() => {
|
|
281878
282905
|
chatRunByDispatch.delete(handle.id);
|
|
281879
282906
|
}, 6e4).unref?.();
|
|
@@ -281944,6 +282971,9 @@ async function startServe(opts) {
|
|
|
281944
282971
|
},
|
|
281945
282972
|
done,
|
|
281946
282973
|
kill: () => handle.kill(),
|
|
282974
|
+
// Bug 0232:`kill()` 报不了帧的去向(契约钉死 `Promise<void>`),这一对能。
|
|
282975
|
+
// 条件透传:本地 adapter 的句柄没有这条,上层按「方法在不在」分流回老行为。
|
|
282976
|
+
...typeof handle.sendKill === "function" ? { sendKill: () => handle.sendKill(), cancelLocally: () => handle.cancelLocally?.() ?? false } : {},
|
|
281947
282977
|
// ADR-0093 对话追加(chat 道):透传底层句柄的 appendInput。runtime 不原生支持时 handle 上
|
|
281948
282978
|
// 压根没这个方法,这里也就不给——server 据此让插入入口置灰,而不是让人按下去才发现不行。
|
|
281949
282979
|
// 不带 interrupt:人插一句话走回合边界排队,不作废在飞回合(那是 produce 道 ADR-0094 的语义)。
|
|
@@ -282317,6 +283347,18 @@ async function startServe(opts) {
|
|
|
282317
283347
|
const mapping = dispatchToWork.get(msg.dispatchId);
|
|
282318
283348
|
const exitBus = mapping ? busForCompany(mapping.companyId) ?? busForWorkorder(mapping.workorderId) : null;
|
|
282319
283349
|
if (mapping && exitBus) {
|
|
283350
|
+
if (mapping.artifactId && mapping.companyId) {
|
|
283351
|
+
const allocator = runtimeAllocatorFor(mapping.companyId);
|
|
283352
|
+
if (isRuntimeClassFailure(msg.info.reason, msg.info.errorMessage)) {
|
|
283353
|
+
allocator.noteFailure({
|
|
283354
|
+
unitOfWork: mapping.artifactId,
|
|
283355
|
+
...msg.info.reason ? { reason: msg.info.reason } : {},
|
|
283356
|
+
...msg.info.errorMessage ? { errorMessage: msg.info.errorMessage } : {}
|
|
283357
|
+
});
|
|
283358
|
+
} else {
|
|
283359
|
+
allocator.noteSettled(mapping.artifactId);
|
|
283360
|
+
}
|
|
283361
|
+
}
|
|
282320
283362
|
await exitBus.submit({
|
|
282321
283363
|
companyId: "",
|
|
282322
283364
|
workorderId: mapping.workorderId,
|
|
@@ -282776,7 +283818,8 @@ async function startServe(opts) {
|
|
|
282776
283818
|
try {
|
|
282777
283819
|
result = replyIssueId ? await bridge.replyWork(rev.artifactId, rev.author, replyIssueId, ledgerId) : await bridge.produce(rev.artifactId, rev.author, workId, ledgerId, opts2?.resumeEligible, opts2?.protocolRecovery?.round);
|
|
282778
283820
|
} catch (err) {
|
|
282779
|
-
|
|
283821
|
+
const reason = err instanceof RuntimeAllocationError ? err.message : `dispatch error: ${err instanceof Error ? err.message : String(err)}`;
|
|
283822
|
+
void dispatchLedgerFor(companyId).then((l) => l.close(ledgerId, { outcome: "failed", reason })).catch(ledgerWarn("close(error)"));
|
|
282780
283823
|
console.log(`[new-engine] dispatchWork ${workId} \u6D3E\u53D1\u5F02\u5E38\uFF1A${String(err)} \u2192 \u7F6E work failed`);
|
|
282781
283824
|
const failBus = busForCompany(companyId);
|
|
282782
283825
|
if (failBus) {
|
|
@@ -282791,7 +283834,7 @@ async function startServe(opts) {
|
|
|
282791
283834
|
semanticsVersion: 3,
|
|
282792
283835
|
dispatchId: ledgerId,
|
|
282793
283836
|
outcome: "failed",
|
|
282794
|
-
detail: { reason
|
|
283837
|
+
detail: { reason }
|
|
282795
283838
|
}
|
|
282796
283839
|
});
|
|
282797
283840
|
}
|
|
@@ -282802,6 +283845,9 @@ async function startServe(opts) {
|
|
|
282802
283845
|
dispatchToWork.set(result.dispatchId, {
|
|
282803
283846
|
workId,
|
|
282804
283847
|
workorderId: wid,
|
|
283848
|
+
// ★ ADR 0616:运行时分配按「同一件活」记失败链,工单这条路的单位就是这个节点(artifactId)。
|
|
283849
|
+
// 退出帧上只有 dispatchId,所以必须在这里记下来——否则失败时不知道该把哪条链标上。
|
|
283850
|
+
artifactId: rev.artifactId,
|
|
282805
283851
|
// ★ 记下归属公司:session_started / session_exited / 收口漏斗都据它把事件投回**这一家**的 bus。
|
|
282806
283852
|
companyId,
|
|
282807
283853
|
...delivery ? { nodeId: delivery.nodeId } : {}
|
|
@@ -283075,7 +284121,27 @@ async function startServe(opts) {
|
|
|
283075
284121
|
}),
|
|
283076
284122
|
serverUrl: localUrl,
|
|
283077
284123
|
serverUrlFor: (ctx) => serverUrlForBinding(ctx.binding),
|
|
283078
|
-
|
|
284124
|
+
/**
|
|
284125
|
+
* ★ ADR 0616:工单节点派活(produce / review / integrate / reply)的目标由**运行时分配**决定,
|
|
284126
|
+
* 不再直接读那条主绑定。两个触发时机都落在这里:
|
|
284127
|
+
*
|
|
284128
|
+
* ① 开一个新会话时 —— 每次 spawn 前跑一次分配(排除故障与满并发、主可用就选主)。
|
|
284129
|
+
* ② 上一轮因 runtime/节点原因失败时 —— `session_exited` 那边已经把失败目标记进这件活的
|
|
284130
|
+
* 失败链,这一次分配会自动换到另一个候选;连着换满 `RUNTIME_ALLOCATION_MAX_ATTEMPTS` 次
|
|
284131
|
+
* 仍起不来就抛错(由 `dispatchWork` 的 catch 落成 work failed,原文带机器名与原因)。
|
|
284132
|
+
*
|
|
284133
|
+
* 没带 `ctx.unitOfWork` 的调用(`executorSignature` 只要一个指纹)退回旧路:那条路不该
|
|
284134
|
+
* 消耗一次分配尝试,更不该因为「所有候选都试过了」而抛错——指纹算不出来只会退化,不该阻断。
|
|
284135
|
+
*/
|
|
284136
|
+
resolveBinding: async (actorId, ctx) => {
|
|
284137
|
+
if (!ctx?.unitOfWork) return (await runActors()).service.resolveBinding()(actorId);
|
|
284138
|
+
const resolved = await runtimeAllocatorFor(companyId).allocateFor({
|
|
284139
|
+
actorId,
|
|
284140
|
+
unitOfWork: ctx.unitOfWork,
|
|
284141
|
+
what: "\u5DE5\u5355\u8282\u70B9\u6D3E\u6D3B"
|
|
284142
|
+
});
|
|
284143
|
+
return resolved.binding;
|
|
284144
|
+
},
|
|
283079
284145
|
prepareReadonlyInputs: async ({ actor, artifactType, binding, codeRepo, pinnedRevisions }) => {
|
|
283080
284146
|
let prepared = null;
|
|
283081
284147
|
try {
|
|
@@ -284481,7 +285547,7 @@ ${nodeFault}` : "");
|
|
|
284481
285547
|
const zbEnabled = opts.zeroOutputBreaker?.enabled ?? !zbDisabledByEnv;
|
|
284482
285548
|
const zbIntervalMs = opts.zeroOutputBreaker?.intervalMs ?? 3e4;
|
|
284483
285549
|
const zbWindowMs = opts.zeroOutputBreaker?.windowMs ?? (envInt("OASIS_ZERO_OUTPUT_BREAKER_WINDOW_MIN") ?? 30) * 6e4;
|
|
284484
|
-
const zbThreshold = opts.zeroOutputBreaker?.threshold ?? envInt("OASIS_ZERO_OUTPUT_BREAKER_N") ??
|
|
285550
|
+
const zbThreshold = opts.zeroOutputBreaker?.threshold ?? envInt("OASIS_ZERO_OUTPUT_BREAKER_N") ?? 20;
|
|
284485
285551
|
const zbCostThresholdMicros = opts.zeroOutputBreaker?.costThresholdMicros ?? (envInt("OASIS_ZERO_OUTPUT_BREAKER_COST_USD") ?? 0) * 1e6;
|
|
284486
285552
|
if (zbEnabled && zbIntervalMs > 0 && (zbThreshold > 0 || zbCostThresholdMicros > 0)) {
|
|
284487
285553
|
const zbActions = /* @__PURE__ */ new Set(["produce", "review"]);
|
|
@@ -289687,7 +290753,7 @@ async function waitForCommandAuthorization(base, headers, draftId, fallback) {
|
|
|
289687
290753
|
}
|
|
289688
290754
|
}
|
|
289689
290755
|
}
|
|
289690
|
-
var CLI_VERSION = "2.2.
|
|
290756
|
+
var CLI_VERSION = "2.2.73" ? "2.2.73" : "dev";
|
|
289691
290757
|
function apiHeaders(token, company) {
|
|
289692
290758
|
const headers = {
|
|
289693
290759
|
authorization: `Bearer ${token}`,
|
|
@@ -293493,7 +294559,7 @@ function shimScript() {
|
|
|
293493
294559
|
}
|
|
293494
294560
|
|
|
293495
294561
|
// src/index.ts
|
|
293496
|
-
var PKG_VERSION = true ? "2.2.
|
|
294562
|
+
var PKG_VERSION = true ? "2.2.73" : "dev";
|
|
293497
294563
|
var LOCAL_BIN = localBin();
|
|
293498
294564
|
var NPM_PREFIX = npmPrefix();
|
|
293499
294565
|
var INSTANCE = DEFAULT_INSTANCE;
|