oasis_test 0.1.102 → 0.1.104
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 +710 -123
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -387,7 +387,9 @@ function emptyReadModel() {
|
|
|
387
387
|
gaps: /* @__PURE__ */ new Map(),
|
|
388
388
|
escalations: /* @__PURE__ */ new Map(),
|
|
389
389
|
pendingReviews: /* @__PURE__ */ new Map(),
|
|
390
|
-
pausedWorkorders: /* @__PURE__ */ new Set()
|
|
390
|
+
pausedWorkorders: /* @__PURE__ */ new Set(),
|
|
391
|
+
acceptBlockers: /* @__PURE__ */ new Map()
|
|
392
|
+
// 默认空 = 无挡板 = 历史行为;只有 bridge 从引擎快照刷新时才填
|
|
391
393
|
};
|
|
392
394
|
}
|
|
393
395
|
function indexAdd(index, key, value) {
|
|
@@ -1513,6 +1515,15 @@ function stuckDiagnosis(model, id, opts) {
|
|
|
1513
1515
|
handler: "none"
|
|
1514
1516
|
});
|
|
1515
1517
|
}
|
|
1518
|
+
const blocker = model.acceptBlockers.get(id);
|
|
1519
|
+
if (blocker) {
|
|
1520
|
+
causes.push({
|
|
1521
|
+
category: "accept-barrier",
|
|
1522
|
+
source: "oplog",
|
|
1523
|
+
detail: `\u4EA7\u51FA\u5DF2\u5C31\u7EEA\u4F46\u6536\u53E3\u88AB\u62E6\uFF08${blocker.code}\uFF09\uFF1A${blocker.detail}${blocker.overridable ? "\u2014\u2014\u4EBA\u53EF force-conclude \u7B7E\u5B57\u7A7F\u900F\uFF08\u7559 override \u75D5\u8FF9\uFF09" : "\u2014\u2014\u4E0D\u53EF\u7A7F\u900F\uFF0C\u6309\u4E0A\u9762\u7684\u539F\u56E0\u5148\u4FEE\u6B63\u76EE\u6807"}`,
|
|
1524
|
+
handler: "human"
|
|
1525
|
+
});
|
|
1526
|
+
}
|
|
1516
1527
|
const rt = opts.runtime;
|
|
1517
1528
|
if (rt.queued) {
|
|
1518
1529
|
causes.push({
|
|
@@ -4335,6 +4346,93 @@ var init_exit_classify = __esm({
|
|
|
4335
4346
|
}
|
|
4336
4347
|
});
|
|
4337
4348
|
|
|
4349
|
+
// ../core/src/produce-resume-fallback.ts
|
|
4350
|
+
function isSessionNotFound(info) {
|
|
4351
|
+
return info.reason === "session-not-found";
|
|
4352
|
+
}
|
|
4353
|
+
function makeProduceResumeFallback(first, spawnFallback) {
|
|
4354
|
+
const stableId2 = first.id;
|
|
4355
|
+
const exitCbs = [];
|
|
4356
|
+
const outCbs = [];
|
|
4357
|
+
const telCbs = [];
|
|
4358
|
+
const outBuf = [];
|
|
4359
|
+
const telBuf = [];
|
|
4360
|
+
let sawOutput = false;
|
|
4361
|
+
let active = first;
|
|
4362
|
+
let fellBack = false;
|
|
4363
|
+
let terminal;
|
|
4364
|
+
const fanOut = (chunk) => {
|
|
4365
|
+
if (outCbs.length) for (const cb of outCbs) cb(chunk);
|
|
4366
|
+
else outBuf.push(chunk);
|
|
4367
|
+
};
|
|
4368
|
+
const fanTel = (event) => {
|
|
4369
|
+
if (telCbs.length) for (const cb of telCbs) cb(event);
|
|
4370
|
+
else telBuf.push(event);
|
|
4371
|
+
};
|
|
4372
|
+
const wire = (s2) => {
|
|
4373
|
+
s2.onOutput?.((c) => {
|
|
4374
|
+
sawOutput = true;
|
|
4375
|
+
fanOut(c);
|
|
4376
|
+
});
|
|
4377
|
+
s2.onTelemetry?.((e) => {
|
|
4378
|
+
sawOutput = true;
|
|
4379
|
+
fanTel(e);
|
|
4380
|
+
});
|
|
4381
|
+
};
|
|
4382
|
+
const fireTerminal = (info) => {
|
|
4383
|
+
if (terminal) return;
|
|
4384
|
+
terminal = info;
|
|
4385
|
+
for (const cb of exitCbs.splice(0)) cb(info);
|
|
4386
|
+
};
|
|
4387
|
+
wire(first);
|
|
4388
|
+
first.onExit((info) => {
|
|
4389
|
+
if (fellBack || sawOutput || !isSessionNotFound(info)) {
|
|
4390
|
+
fireTerminal(info);
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
fellBack = true;
|
|
4394
|
+
void spawnFallback().then(
|
|
4395
|
+
(second) => {
|
|
4396
|
+
active = second;
|
|
4397
|
+
wire(second);
|
|
4398
|
+
second.onExit((info2) => fireTerminal(info2));
|
|
4399
|
+
},
|
|
4400
|
+
// 回落连 spawn 都起不来(节点掉线等)→ 把原始 session-not-found 如实上报,交常规重试兜底。
|
|
4401
|
+
() => fireTerminal(info)
|
|
4402
|
+
);
|
|
4403
|
+
});
|
|
4404
|
+
return {
|
|
4405
|
+
get id() {
|
|
4406
|
+
return stableId2;
|
|
4407
|
+
},
|
|
4408
|
+
kill: () => active.kill(),
|
|
4409
|
+
onExit(cb) {
|
|
4410
|
+
if (terminal) {
|
|
4411
|
+
cb(terminal);
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
exitCbs.push(cb);
|
|
4415
|
+
},
|
|
4416
|
+
onOutput(cb) {
|
|
4417
|
+
for (const c of outBuf.splice(0)) cb(c);
|
|
4418
|
+
outCbs.push(cb);
|
|
4419
|
+
},
|
|
4420
|
+
onTelemetry(cb) {
|
|
4421
|
+
for (const e of telBuf.splice(0)) cb(e);
|
|
4422
|
+
telCbs.push(cb);
|
|
4423
|
+
},
|
|
4424
|
+
appendInput: (input) => active.appendInput?.(input) ?? Promise.resolve({ accepted: false, reason: "unsupported" }),
|
|
4425
|
+
get canAppendInput() {
|
|
4426
|
+
return active.canAppendInput !== false && typeof active.appendInput === "function";
|
|
4427
|
+
}
|
|
4428
|
+
};
|
|
4429
|
+
}
|
|
4430
|
+
var init_produce_resume_fallback = __esm({
|
|
4431
|
+
"../core/src/produce-resume-fallback.ts"() {
|
|
4432
|
+
"use strict";
|
|
4433
|
+
}
|
|
4434
|
+
});
|
|
4435
|
+
|
|
4338
4436
|
// ../timeout-policy/src/policy.ts
|
|
4339
4437
|
function classify(actorId) {
|
|
4340
4438
|
if (!actorId) return "unknown";
|
|
@@ -4801,6 +4899,7 @@ var init_dispatcher = __esm({
|
|
|
4801
4899
|
"../core/src/dispatcher.ts"() {
|
|
4802
4900
|
"use strict";
|
|
4803
4901
|
import_node_crypto2 = require("node:crypto");
|
|
4902
|
+
init_produce_resume_fallback();
|
|
4804
4903
|
init_read_model();
|
|
4805
4904
|
init_assembler();
|
|
4806
4905
|
init_content_type_handler();
|
|
@@ -5574,7 +5673,10 @@ var init_dispatcher = __esm({
|
|
|
5574
5673
|
action: "produce",
|
|
5575
5674
|
...args.part !== void 0 ? { part: args.part } : {},
|
|
5576
5675
|
...args.engineWorkId !== void 0 ? { engineWorkId: args.engineWorkId } : {},
|
|
5577
|
-
...args.engineCompanyId !== void 0 ? { engineCompanyId: args.engineCompanyId } : {}
|
|
5676
|
+
...args.engineCompanyId !== void 0 ? { engineCompanyId: args.engineCompanyId } : {},
|
|
5677
|
+
// 工单 e4a43a42:只有短时重试(同一未收口任务)才接续旧会话;变更请求/gap/级联触发的返工
|
|
5678
|
+
// 一律新会话(`resumeEligible: false`)——否则接续会把过时上下文和旧任务书一起拖进来。缺省=可续。
|
|
5679
|
+
...args.resumeEligible !== void 0 ? { resumeEligible: args.resumeEligible } : {}
|
|
5578
5680
|
}, args.bypassScheduling === true, args.dispatchId);
|
|
5579
5681
|
}
|
|
5580
5682
|
/** 新引擎回信 work(设计-最终 §回信轮)的 effect 调到此——派发收容回信会话(唯一写动作 `oasis reply`)。
|
|
@@ -5820,7 +5922,8 @@ var init_dispatcher = __esm({
|
|
|
5820
5922
|
const priorConsistent = prior !== void 0 && prior.nodeId === binding?.nodeId && prior.runtimeKind === binding?.runtimeKind;
|
|
5821
5923
|
const retainedPartSession = spec.action === "produce" && spec.part !== void 0 && priorConsistent && !prior.sessionMissing ? prior.rtId : void 0;
|
|
5822
5924
|
const retainedWholeSession = spec.action === "produce" && spec.part === void 0 && priorConsistent && !prior.sessionMissing ? this.resumableWholeSession(spec.artifactId, seqNow, prior.rtId) : void 0;
|
|
5823
|
-
const
|
|
5925
|
+
const resumeEligible = spec.resumeEligible !== false;
|
|
5926
|
+
const retainedSession = resumeEligible ? retainedPartSession ?? retainedWholeSession : void 0;
|
|
5824
5927
|
const runtimeSessionId = retainedSession ?? (isProduceLike ? (0, import_node_crypto2.randomUUID)() : void 0);
|
|
5825
5928
|
const resumeSession = retainedSession !== void 0;
|
|
5826
5929
|
const retry = this.strikes.get(jobKey);
|
|
@@ -5938,7 +6041,8 @@ var init_dispatcher = __esm({
|
|
|
5938
6041
|
// `handle.id` = `agent_runs.id` = token 的 dispatchId claim,三处同值。
|
|
5939
6042
|
dispatchId: attempt
|
|
5940
6043
|
};
|
|
5941
|
-
const
|
|
6044
|
+
const first = await this.opts.adapter.spawn(job);
|
|
6045
|
+
const session = resumeSession ? makeProduceResumeFallback(first, () => this.opts.adapter.spawn({ ...job, resumeRuntimeSession: false })) : first;
|
|
5942
6046
|
if (!this.isSpawnAttemptCurrent(jobKey, attempt)) {
|
|
5943
6047
|
try {
|
|
5944
6048
|
await session.kill();
|
|
@@ -6995,7 +7099,7 @@ function scan(state, ctx) {
|
|
|
6995
7099
|
if (wstate === "success" && node.latestAcceptId !== lw.id) {
|
|
6996
7100
|
const reqs = state.requirementsOf(node.id);
|
|
6997
7101
|
if (reqs.length === 0) {
|
|
6998
|
-
if (
|
|
7102
|
+
if (acceptBlock(state, lw.id) !== null) continue;
|
|
6999
7103
|
events.push({
|
|
7000
7104
|
kind: "work.accept",
|
|
7001
7105
|
workorderId: state.workorder.id,
|
|
@@ -7036,7 +7140,11 @@ function tryToRun(state, nodeId, ctx, events) {
|
|
|
7036
7140
|
nodeId,
|
|
7037
7141
|
workId,
|
|
7038
7142
|
reasons: [],
|
|
7039
|
-
continuesWorkId: null
|
|
7143
|
+
continuesWorkId: null,
|
|
7144
|
+
// 工单 e4a43a42(接续只对短时重试成立):**已收口过的节点被重新激活 = 返工**(变更请求/驳回/上游
|
|
7145
|
+
// 变更把已 accept 的节点重开)→ 起全新会话,别接续那场过时的旧对话;**从未收口的节点** = 首跑或
|
|
7146
|
+
// 失败后重试,接续 WIP 是对的。判据 = 有没有 latest_accept。派发层据此决定 resumeSession。
|
|
7147
|
+
resumeEligible: node.latestAcceptId == null
|
|
7040
7148
|
});
|
|
7041
7149
|
}
|
|
7042
7150
|
function inputsMatch(state, node, latestWork2) {
|
|
@@ -7164,13 +7272,15 @@ function processCommentReplies(state, nodeId, ctx, events) {
|
|
|
7164
7272
|
if (latest) {
|
|
7165
7273
|
const s2 = workState(latest, hasOutput(latest, 0));
|
|
7166
7274
|
if (s2 === "success" && latest.acceptanceState !== "accepted") {
|
|
7167
|
-
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
|
|
7171
|
-
|
|
7172
|
-
|
|
7173
|
-
|
|
7275
|
+
if (acceptBlock(state, latest.id) === null) {
|
|
7276
|
+
events.push({
|
|
7277
|
+
kind: "work.accept",
|
|
7278
|
+
workorderId: state.workorder.id,
|
|
7279
|
+
workId: latest.id,
|
|
7280
|
+
by: "actor:system:engine",
|
|
7281
|
+
override: null
|
|
7282
|
+
});
|
|
7283
|
+
}
|
|
7174
7284
|
return;
|
|
7175
7285
|
}
|
|
7176
7286
|
}
|
|
@@ -7254,9 +7364,9 @@ function checkTerminated(state) {
|
|
|
7254
7364
|
if (!isBusinessDependencyEdge(state, edge)) continue;
|
|
7255
7365
|
if (isEdgeInputUnobtainable(state, edge)) continue;
|
|
7256
7366
|
const upstream = state.node(edge.fromNodeId);
|
|
7257
|
-
if (!upstream?.
|
|
7367
|
+
if (!upstream?.latestAcceptId || !businessHandoffBarrierSatisfied(state, upstream.id, upstream.latestAcceptId)) return false;
|
|
7258
7368
|
}
|
|
7259
|
-
return live.every((n) => n.
|
|
7369
|
+
return live.every((n) => n.latestAcceptId !== null && (n.latestAcceptId === n.latestWorkId || n.latestAcceptId === n.latestSuccessWorkId));
|
|
7260
7370
|
}
|
|
7261
7371
|
function isEdgeInputUnobtainable(state, edge) {
|
|
7262
7372
|
const upstream = state.node(edge.fromNodeId);
|
|
@@ -7267,12 +7377,61 @@ function isBusinessDependencyEdge(state, edge) {
|
|
|
7267
7377
|
return edge.kind === "data" && edge.required && downstream?.fields?.[BUSINESS_HANDOFF_POLICY_FIELD] !== BUSINESS_HANDOFF_POLICY_EXEMPT;
|
|
7268
7378
|
}
|
|
7269
7379
|
function businessHandoffBarrierSatisfied(state, nodeId, workId) {
|
|
7380
|
+
return handoffBlock(state, nodeId, workId) === null;
|
|
7381
|
+
}
|
|
7382
|
+
function acceptBlock(state, workId) {
|
|
7383
|
+
const w2 = state.work(workId);
|
|
7384
|
+
if (!w2) return { code: "work-missing", detail: `work \u4E0D\u5728\u518C\uFF1A${workId}`, overridable: false };
|
|
7385
|
+
if (w2.deadAt) {
|
|
7386
|
+
return {
|
|
7387
|
+
code: "work-dead",
|
|
7388
|
+
detail: `\u8FD9\u4E00\u7248\u5DF2\u4F5C\u5E9F\uFF08${w2.deadAt}${w2.rejectedReason ? `\uFF1A${w2.rejectedReason}` : ""}\uFF09\u2014\u2014\u4F5C\u5E9F\u7248\u4E0D\u80FD\u6536\u53E3\uFF0C\u6539\u5BF9\u6700\u65B0\u7248\u64CD\u4F5C\u6216\u5148\u91CD\u8DD1\u672C\u8282\u70B9`,
|
|
7389
|
+
overridable: false
|
|
7390
|
+
};
|
|
7391
|
+
}
|
|
7392
|
+
if (w2.acceptanceState === "accepted") {
|
|
7393
|
+
return { code: "already-accepted", detail: `\u5DF2\u662F\u9A8C\u6536\u6001\uFF08${w2.acceptedAt ?? "?"}\uFF09\u2014\u2014\u65E0\u9700\u91CD\u590D\u6536\u53E3`, overridable: false };
|
|
7394
|
+
}
|
|
7395
|
+
const node = state.node(w2.nodeId);
|
|
7396
|
+
if (!node) return { code: "node-missing", detail: `\u8282\u70B9\u4E0D\u5728\u518C\uFF1A${w2.nodeId}`, overridable: false };
|
|
7397
|
+
if (!hasOutput(w2, state.artifactsOf(w2.id).length)) {
|
|
7398
|
+
if (!w2.endedAt) {
|
|
7399
|
+
return {
|
|
7400
|
+
code: "work-in-flight",
|
|
7401
|
+
detail: `\u8FD9\u4E00\u7248\u8FD8\u5728\u8DD1\u3001\u4E14\u8FD8\u6CA1\u6709\u4EFB\u4F55\u4EA7\u51FA\u2014\u2014\u7B49\u5B83\u4EA4\u7A3F\u540E\u518D\u6536\u53E3\uFF0C\u6216\u5148\u505C\u6389\uFF08hold\uFF09/ \u91CD\u8DD1`,
|
|
7402
|
+
overridable: false
|
|
7403
|
+
};
|
|
7404
|
+
}
|
|
7405
|
+
return {
|
|
7406
|
+
code: "no-output",
|
|
7407
|
+
detail: `\u8FD9\u4E00\u7248\u5DF2\u7ED3\u675F\u4F46\u6CA1\u6709\u4EFB\u4F55\u4EA7\u51FA\uFF08\u7A7A\u58F3\uFF09\u2014\u2014\u4E0D\u80FD\u628A\u6CA1\u6709\u5185\u5BB9\u7684\u7248\u672C\u6536\u53E3\u6210\u4EA4\u4ED8\u7248\u672C\uFF1B\u6539\u5BF9\u6700\u8FD1\u4E00\u7248\u6709\u4EA7\u51FA\u7684\u7248\u672C\u64CD\u4F5C\uFF0C\u6216\u91CD\u8DD1\u672C\u8282\u70B9`,
|
|
7408
|
+
overridable: false
|
|
7409
|
+
};
|
|
7410
|
+
}
|
|
7411
|
+
return handoffBlock(state, w2.nodeId, workId);
|
|
7412
|
+
}
|
|
7413
|
+
function handoffBlock(state, nodeId, workId) {
|
|
7270
7414
|
const work = state.work(workId);
|
|
7271
|
-
if (!requiresBusinessHandoff(state, nodeId, work)) return
|
|
7415
|
+
if (!requiresBusinessHandoff(state, nodeId, work)) return null;
|
|
7416
|
+
if (work?.acceptanceState === "accepted" && work.overrideBy) return null;
|
|
7272
7417
|
const node = state.node(nodeId);
|
|
7273
|
-
|
|
7418
|
+
if (workId !== node?.latestWorkId && workId !== node?.latestSuccessWorkId) {
|
|
7419
|
+
return {
|
|
7420
|
+
code: "not-latest-work",
|
|
7421
|
+
detail: `\u4E0D\u662F\u672C\u8282\u70B9\u6700\u65B0\u7248\uFF08\u6700\u65B0\u5C1D\u8BD5\uFF1A${node?.latestWorkId ?? "\u65E0"}\uFF1B\u6700\u65B0\u6709\u6548\u4EA7\u51FA\uFF1A${node?.latestSuccessWorkId ?? "\u65E0"}\uFF09\u2014\u2014\u6536\u53E3\u53EA\u8BA4\u6700\u65B0\u4E00\u7248\u6216\u6700\u65B0\u6709\u6548\u4EA7\u51FA`,
|
|
7422
|
+
overridable: false
|
|
7423
|
+
};
|
|
7424
|
+
}
|
|
7425
|
+
const delivered = work?.businessHandoffStatus === "delivered" || work?.status === "success" && work.agentHandoffAttemptId === null && work.businessHandoffStatus === null;
|
|
7426
|
+
if (delivered) return null;
|
|
7427
|
+
return {
|
|
7428
|
+
code: "handoff-undeclared",
|
|
7429
|
+
detail: `\u4E1A\u52A1\u4EA4\u63A5\u672A\u5B8C\u6210\uFF1Awork.status=${work?.status ?? "?"}\u3001handoff=${work?.businessHandoffStatus ?? "\u672A\u58F0\u660E"}${work?.agentHandoffAttemptId ? `\uFF08attempt ${work.agentHandoffAttemptId}\uFF09` : ""}\u2014\u2014\u7B49\u6267\u884C\u4EBA\u8865\u4EA4\u63A5\uFF0C\u6216\u7531\u4EBA force-conclude \u7B7E\u5B57\u7A7F\u900F`,
|
|
7430
|
+
overridable: true
|
|
7431
|
+
};
|
|
7274
7432
|
}
|
|
7275
7433
|
function requiresBusinessHandoff(state, nodeId, work) {
|
|
7434
|
+
if (work?.replyToIssueId) return false;
|
|
7276
7435
|
if (!state.edgesFrom(nodeId).some((edge) => isBusinessDependencyEdge(state, edge))) return false;
|
|
7277
7436
|
if (typeof work?.assigneeActorId === "string" && work.assigneeActorId.startsWith("actor:human:")) return false;
|
|
7278
7437
|
return true;
|
|
@@ -7352,6 +7511,7 @@ CREATE TABLE IF NOT EXISTS ${schema}.workflow_nodes (
|
|
|
7352
7511
|
assignee_actor_id text,
|
|
7353
7512
|
assignee_role text,
|
|
7354
7513
|
latest_work_id text,
|
|
7514
|
+
latest_success_work_id text,
|
|
7355
7515
|
latest_accept_id text,
|
|
7356
7516
|
is_final_output boolean NOT NULL DEFAULT false,
|
|
7357
7517
|
fields jsonb,
|
|
@@ -7465,6 +7625,9 @@ CREATE TABLE IF NOT EXISTS ${schema}.issues (
|
|
|
7465
7625
|
author_actor_id text NOT NULL,
|
|
7466
7626
|
hand_actor_id text,
|
|
7467
7627
|
raised_by_node_id text,
|
|
7628
|
+
-- \u2605 escalation \u66FF\u54EA\u6761 gap \u53D1\u8D77\uFF08\u88AB\u5F15\u7528 gap \u7684 issue id\uFF09\uFF1B\u4F9B issue.resolve \u7CBE\u786E\u8FDE\u5E26\u5173\u95ED
|
|
7629
|
+
-- \uFF08ADR-0030 D1\uFF1Bwo-05dfbd6e \u8865\u9F50\u6865\u63A5\u8FC1\u79FB\u9057\u6F0F\uFF09\u3002\u975E escalation / \u672A\u5E26 --gap \u65F6\u4E3A NULL\u3002
|
|
7630
|
+
gap_id text,
|
|
7468
7631
|
-- \u2605 ADR 0144\uFF1Ablocking / state \u5217\u5DF2\u5220\u9664\u2014\u2014\u5B58\u6D3B\u5224\u636E\u552F\u4E00\u5316\u4E3A resolved_at \u5224\u7A7A\uFF1B
|
|
7469
7632
|
-- resolution \u627F\u63A5\u65E7\u4E09\u503C state \u7684 closed\uFF08=wontfix\uFF09\u8BED\u4E49\u3002\u7EA6\u675F\u7531\u8FC1\u79FB\u6BB5\u7EDF\u4E00\u52A0\uFF08\u540D\u5B57\u5316\u3001\u5E42\u7B49\uFF09\u3002
|
|
7470
7633
|
proposed_resolution text,
|
|
@@ -7589,6 +7752,8 @@ ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
|
|
|
7589
7752
|
-- \u968F\u540E state / blocking \u5217\u5220\u9664\uFF08\u4F9D\u8D56 state \u7684 CHECK \u4E0E\u4E09\u4E2A\u65E7\u90E8\u5206\u7D22\u5F15\u968F\u5217\u81EA\u52A8\u5220\u9664\uFF09\u3002
|
|
7590
7753
|
-- \u56DE\u586B\u5FC5\u987B\u5728\u5220\u5217\u524D\u3001\u4E14\u53EA\u5728 state \u5217\u8FD8\u5B58\u5728\u65F6\u6267\u884C\uFF08DO \u5757\u5224 information_schema\uFF0C\u91CD\u590D\u542F\u52A8\u5E42\u7B49\uFF09\u3002
|
|
7591
7754
|
ALTER TABLE ${schema}.issues ADD COLUMN IF NOT EXISTS resolution text;
|
|
7755
|
+
-- escalation\u2192gap \u7CBE\u786E\u5173\u8054\uFF08wo-05dfbd6e\uFF09\uFF1A\u4F9B issue.resolve \u8FDE\u5E26\u5173\u95ED\u5339\u914D escalation\uFF08ADR-0030 D1\uFF09\u3002
|
|
7756
|
+
ALTER TABLE ${schema}.issues ADD COLUMN IF NOT EXISTS gap_id text;
|
|
7592
7757
|
DO $do$ BEGIN
|
|
7593
7758
|
IF EXISTS (SELECT 1 FROM information_schema.columns
|
|
7594
7759
|
WHERE table_schema = '${schema}' AND table_name = 'issues' AND column_name = 'state') THEN
|
|
@@ -7608,6 +7773,9 @@ CREATE INDEX IF NOT EXISTS issues_workorder_alive_idx ON ${schema}.issues (worko
|
|
|
7608
7773
|
CREATE INDEX IF NOT EXISTS issues_about_alive_idx ON ${schema}.issues (about_node_id) WHERE resolved_at IS NULL;
|
|
7609
7774
|
CREATE INDEX IF NOT EXISTS issues_raised_alive_idx ON ${schema}.issues (raised_by_node_id) WHERE resolved_at IS NULL;
|
|
7610
7775
|
ALTER TABLE ${schema}.workflow_nodes ADD COLUMN IF NOT EXISTS fields jsonb;
|
|
7776
|
+
-- \u2605 \u6709\u6548\u4EA7\u51FA\u6307\u9488\uFF08ADR 0148\uFF09\uFF1Anode \u4E0A\u300C\u6700\u8FD1\u4E00\u6B21\u5C1D\u8BD5\u300D\uFF08latest_work_id\uFF09\u4E4B\u5916\u518D\u5B58\u300C\u6700\u8FD1\u4E00\u6B21\u6709\u6548\u4EA7\u51FA\u300D\uFF0C
|
|
7777
|
+
-- \u5931\u8D25\u7A7A\u6D3E\u53D1\u4E0D\u518D\u9876\u6389\u6210\u54C1\u3002\u56DE\u586B\u5728\u4E0B\u65B9 works.status \u56DE\u586B\u4E4B\u540E\u6267\u884C\uFF08\u4F9D\u8D56 status \u5DF2\u843D\u5E93\uFF09\u3002
|
|
7778
|
+
ALTER TABLE ${schema}.workflow_nodes ADD COLUMN IF NOT EXISTS latest_success_work_id text;
|
|
7611
7779
|
-- \u2605 \u5148\u5220\u89C6\u56FE\u518D\u5220\u5217\uFF1Awork_view/node_view/workorder_view \u7528 SELECT *\uFF0C\u5BF9\u57FA\u8868\u5217\u6709\u4F9D\u8D56\u2014\u2014\u5B58\u91CF\u5E93\u4E0A
|
|
7612
7780
|
-- DROP COLUMN \u4F1A\u62A5 "cannot drop column ... because other objects depend on it"\uFF0C\u4E14\u672C\u6BB5\u662F\u5355\u6761
|
|
7613
7781
|
-- \u591A\u8BED\u53E5 Query\uFF08\u4E00\u4E2A\u9690\u5F0F\u4E8B\u52A1\uFF09\uFF0C\u4E00\u5904\u5931\u8D25\u5168\u6BB5\u56DE\u6EDA\u3001migrate \u629B\u9519 \u2192 serve \u8D77\u4E0D\u6765\u3002\u89C6\u56FE\u968F\u540E\u7531
|
|
@@ -7663,6 +7831,16 @@ UPDATE ${schema}.reviews SET status = CASE
|
|
|
7663
7831
|
WHEN ended_at IS NOT NULL THEN 'failed'
|
|
7664
7832
|
ELSE 'running' END
|
|
7665
7833
|
WHERE status IS NULL;
|
|
7834
|
+
-- \u2605 \u6709\u6548\u4EA7\u51FA\u6307\u9488\u56DE\u586B\uFF08ADR 0148\uFF09\uFF1Alatest_success_work_id = \u8BE5\u8282\u70B9\u6700\u8FD1\u4E00\u4E2A status=success \u7684**\u4E3B\u94FE** work
|
|
7835
|
+
-- \uFF08\u6392\u9664\u56DE\u4FE1 work reply_to_issue_id\uFF09\u3002\u5FC5\u987B\u5728\u4E0A\u9762 works.status \u56DE\u586B\u4E4B\u540E\uFF08\u4F9D\u8D56 status \u5DF2\u843D\u5E93\uFF09\u3002
|
|
7836
|
+
-- \u53EA\u586B\u5F53\u524D\u4E3A NULL \u7684\u884C \u2192 \u5E42\u7B49\uFF1A\u91CD\u542F\u65F6\u4E0D\u8986\u76D6 handler \u5DF2\u7EF4\u62A4\u7684\u503C\uFF1B\u4ECE\u672A\u6210\u529F\u8FC7\u7684\u8282\u70B9\u4FDD\u6301 NULL\u3002
|
|
7837
|
+
UPDATE ${schema}.workflow_nodes n SET latest_success_work_id = (
|
|
7838
|
+
SELECT w.id FROM ${schema}.works w
|
|
7839
|
+
WHERE w.node_id = n.id AND w.status = 'success' AND w.reply_to_issue_id IS NULL
|
|
7840
|
+
ORDER BY w.created_at DESC, w.id DESC
|
|
7841
|
+
LIMIT 1
|
|
7842
|
+
)
|
|
7843
|
+
WHERE n.latest_success_work_id IS NULL;
|
|
7666
7844
|
`;
|
|
7667
7845
|
}
|
|
7668
7846
|
function ENGINE_VIEWS(schema) {
|
|
@@ -7678,20 +7856,25 @@ SELECT r.*, r.status AS state
|
|
|
7678
7856
|
FROM ${schema}.reviews r;
|
|
7679
7857
|
|
|
7680
7858
|
DROP VIEW IF EXISTS ${schema}.node_view;
|
|
7859
|
+
-- \u2605 ADR 0148\uFF1A\u4E3B\u6001\u6309 latest_success\uFF08\u6700\u8FD1\u4E00\u4E2A\u6709\u6548\u4EA7\u51FA\uFF09\u8D70\u2014\u2014\u300C\u5DF2\u4EA4\u4ED8 v3\u3001\u672B\u6B21\u5C1D\u8BD5 v4 \u5D29\u4E86\u300D\u663E\u793A\u5DF2\u5B8C\u6210\uFF0C
|
|
7860
|
+
-- \u672B\u6B21\u5931\u8D25\u53EA\u4F5C\u526F\u6807\u8BB0\uFF08\u7248\u672C\u5217\u8868\u91CC\u90A3\u6761 failed \u4ECD\u5728\uFF09\uFF0C\u4E0D\u518D\u51FA\u73B0\u300C\u56FE\u4E0A\u7EA2\u3001\u4E0B\u6E38\u5728\u8DD1\u300D\u7684\u5272\u88C2\u3002
|
|
7861
|
+
-- \u4E0E views.ts nodeState \u540C\u6E90\uFF1A\u5728\u8DD1\uFF08latest_work running\uFF09\u4F18\u5148\uFF1B\u5426\u5219\u6709 latest_success \u5C31\u6309\u5176\u9A8C\u6536\u4E0E\u5426\u5206
|
|
7862
|
+
-- \u5DF2\u5B8C\u6210/\u8BC4\u5BA1\u4E2D\uFF1B\u518D\u5426\u5219\u672B\u6B21\u5C1D\u8BD5\u5931\u8D25/dead \u2192 \u5361\u4F4F\u3002
|
|
7681
7863
|
CREATE VIEW ${schema}.node_view AS
|
|
7682
7864
|
SELECT n.*,
|
|
7683
7865
|
CASE
|
|
7684
7866
|
WHEN n.cancelled_at IS NOT NULL THEN 'dead' -- \u5DF2\u4F5C\u5E9F\uFF08\u79FB\u51FA\u8BA1\u5212\uFF09
|
|
7685
7867
|
WHEN lw.id IS NULL THEN 'waiting' -- \u672A\u8FD0\u884C\uFF08latest_work \u4E3A\u7A7A\uFF09
|
|
7686
7868
|
WHEN lw.status = 'retry' THEN 'waiting' -- \u672A\u8FD0\u884C\uFF08\u7528\u6237\u4ECB\u5165\u91CD\u8BD5\uFF0C\u7B49 try_to_run\uFF09
|
|
7687
|
-
WHEN lw.status = 'running' THEN 'running' -- \u5728\u8FDB\u884C
|
|
7688
|
-
WHEN
|
|
7689
|
-
WHEN
|
|
7690
|
-
WHEN lw.status IN ('failed','dead') THEN 'dead' -- \u5361\u4F4F\u4E86
|
|
7869
|
+
WHEN lw.status = 'running' THEN 'running' -- \u5728\u8FDB\u884C\uFF08\u91CD\u8DD1\u8FDB\u884C\u4E2D\u4E5F\u4F18\u5148\u663E\u793A\u5728\u8FDB\u884C\uFF09
|
|
7870
|
+
WHEN ls.id IS NOT NULL AND n.latest_accept_id = ls.id THEN 'done' -- \u5DF2\u5B8C\u6210\uFF08\u6709\u6548\u4EA7\u51FA\u5DF2\u9A8C\u6536\uFF09
|
|
7871
|
+
WHEN ls.id IS NOT NULL AND n.latest_accept_id IS DISTINCT FROM ls.id THEN 'reviewing' -- \u8BC4\u5BA1\u4E2D\uFF08\u6709\u6548\u4EA7\u51FA\u672A\u9A8C\u6536\uFF09
|
|
7872
|
+
WHEN lw.status IN ('failed','dead') THEN 'dead' -- \u4ECE\u672A\u4EA7\u51FA\u4E14\u672B\u6B21\u5C1D\u8BD5\u5931\u8D25 \u2192 \u5361\u4F4F\u4E86
|
|
7691
7873
|
ELSE 'waiting'
|
|
7692
7874
|
END AS state
|
|
7693
7875
|
FROM ${schema}.workflow_nodes n
|
|
7694
|
-
LEFT JOIN ${schema}.works lw ON lw.id = n.latest_work_id
|
|
7876
|
+
LEFT JOIN ${schema}.works lw ON lw.id = n.latest_work_id
|
|
7877
|
+
LEFT JOIN ${schema}.works ls ON ls.id = n.latest_success_work_id;
|
|
7695
7878
|
|
|
7696
7879
|
DROP VIEW IF EXISTS ${schema}.workorder_view;
|
|
7697
7880
|
CREATE VIEW ${schema}.workorder_view AS
|
|
@@ -12971,6 +13154,7 @@ var init_store_postgres = __esm({
|
|
|
12971
13154
|
assigneeActorId: n.assignee_actor_id,
|
|
12972
13155
|
assigneeRole: n.assignee_role,
|
|
12973
13156
|
latestWorkId: n.latest_work_id,
|
|
13157
|
+
latestSuccessWorkId: n.latest_success_work_id ?? null,
|
|
12974
13158
|
latestAcceptId: n.latest_accept_id,
|
|
12975
13159
|
isFinalOutput: n.is_final_output,
|
|
12976
13160
|
cancelledAt: iso(n.cancelled_at),
|
|
@@ -13054,6 +13238,7 @@ var init_store_postgres = __esm({
|
|
|
13054
13238
|
authorActorId: i.author_actor_id,
|
|
13055
13239
|
handActorId: i.hand_actor_id,
|
|
13056
13240
|
raisedByNodeId: i.raised_by_node_id,
|
|
13241
|
+
gapId: i.gap_id ?? null,
|
|
13057
13242
|
proposedResolution: i.proposed_resolution,
|
|
13058
13243
|
proposedBy: i.proposed_by,
|
|
13059
13244
|
proposedAt: iso(i.proposed_at),
|
|
@@ -13146,9 +13331,9 @@ var init_store_postgres = __esm({
|
|
|
13146
13331
|
await this.c.query(
|
|
13147
13332
|
`INSERT INTO ${this.s}.workflow_nodes
|
|
13148
13333
|
(id,workorder_id,type,title,spec,version,assignee_actor_id,assignee_role,
|
|
13149
|
-
latest_work_id,latest_accept_id,is_final_output,
|
|
13334
|
+
latest_work_id,latest_success_work_id,latest_accept_id,is_final_output,
|
|
13150
13335
|
fields,cancelled_at,row_version,created_at,updated_at)
|
|
13151
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
|
|
13336
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)
|
|
13152
13337
|
ON CONFLICT (id) DO NOTHING`,
|
|
13153
13338
|
[
|
|
13154
13339
|
n.id,
|
|
@@ -13160,6 +13345,7 @@ var init_store_postgres = __esm({
|
|
|
13160
13345
|
n.assigneeActorId,
|
|
13161
13346
|
n.assigneeRole,
|
|
13162
13347
|
n.latestWorkId,
|
|
13348
|
+
n.latestSuccessWorkId,
|
|
13163
13349
|
n.latestAcceptId,
|
|
13164
13350
|
n.isFinalOutput,
|
|
13165
13351
|
n.fields ? JSON.stringify(n.fields) : null,
|
|
@@ -13288,9 +13474,9 @@ var init_store_postgres = __esm({
|
|
|
13288
13474
|
await this.c.query(
|
|
13289
13475
|
`INSERT INTO ${this.s}.issues
|
|
13290
13476
|
(id,workorder_id,kind,body,about_node_id,about_work_id,author_actor_id,hand_actor_id,
|
|
13291
|
-
raised_by_node_id,proposed_resolution,proposed_by,proposed_at,
|
|
13477
|
+
raised_by_node_id,gap_id,proposed_resolution,proposed_by,proposed_at,
|
|
13292
13478
|
resolved_at,resolution,resolved_by,resolved_note,resolved_via_work_id,created_at)
|
|
13293
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18)
|
|
13479
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19)
|
|
13294
13480
|
ON CONFLICT (id) DO NOTHING`,
|
|
13295
13481
|
[
|
|
13296
13482
|
i.id,
|
|
@@ -13302,6 +13488,7 @@ var init_store_postgres = __esm({
|
|
|
13302
13488
|
i.authorActorId,
|
|
13303
13489
|
i.handActorId,
|
|
13304
13490
|
i.raisedByNodeId,
|
|
13491
|
+
i.gapId,
|
|
13305
13492
|
i.proposedResolution,
|
|
13306
13493
|
i.proposedBy,
|
|
13307
13494
|
i.proposedAt,
|
|
@@ -13427,11 +13614,14 @@ var init_store_postgres = __esm({
|
|
|
13427
13614
|
* `NOT EXISTS` 那一段是"我前面还有没跑完的兄弟就别动我";`SKIP LOCKED` 是
|
|
13428
13615
|
* "别的 worker 正在处理这张工单就跳过整张"。两者合起来 = 按工单分区的串行队列。
|
|
13429
13616
|
*/
|
|
13430
|
-
async claimNextEvent(now) {
|
|
13617
|
+
async claimNextEvent(now, recoverAppliedBefore) {
|
|
13431
13618
|
const res = await this.c.query(
|
|
13432
13619
|
`SELECT e.* FROM ${this.s}.events e
|
|
13433
|
-
WHERE
|
|
13434
|
-
|
|
13620
|
+
WHERE (
|
|
13621
|
+
(e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
|
|
13622
|
+
-- \u5D29\u6E83\u81EA\u6108\uFF1A\u5361\u5728 applied \u592A\u4E45\u7684\uFF08\u8FDB\u7A0B\u6B7B\u5728\u300C\u843D\u72B6\u6001\u300D\u4E0E\u300C\u6536\u5C3E\u300D\u4E4B\u95F4\uFF09\u4E5F\u56DE\u6536\u91CD\u8DD1
|
|
13623
|
+
OR ($2::timestamptz IS NOT NULL AND e.status = 'applied' AND e.applied_at < $2::timestamptz)
|
|
13624
|
+
)
|
|
13435
13625
|
AND NOT EXISTS (
|
|
13436
13626
|
SELECT 1 FROM ${this.s}.events p
|
|
13437
13627
|
WHERE p.workorder_id = e.workorder_id
|
|
@@ -13440,7 +13630,7 @@ var init_store_postgres = __esm({
|
|
|
13440
13630
|
ORDER BY e.seq
|
|
13441
13631
|
FOR UPDATE OF e SKIP LOCKED
|
|
13442
13632
|
LIMIT 1`,
|
|
13443
|
-
[now]
|
|
13633
|
+
[now, recoverAppliedBefore ?? null]
|
|
13444
13634
|
);
|
|
13445
13635
|
return res.rows[0] ? toRecord(res.rows[0]) : null;
|
|
13446
13636
|
}
|
|
@@ -13481,10 +13671,14 @@ var init_store_postgres = __esm({
|
|
|
13481
13671
|
);
|
|
13482
13672
|
return (res.rowCount ?? 0) > 0;
|
|
13483
13673
|
}
|
|
13484
|
-
async claimAllPending(now) {
|
|
13674
|
+
async claimAllPending(now, recoverAppliedBefore) {
|
|
13485
13675
|
const res = await this.c.query(
|
|
13486
13676
|
`SELECT e.* FROM ${this.s}.events e
|
|
13487
|
-
WHERE
|
|
13677
|
+
WHERE (
|
|
13678
|
+
(e.status = 'pending' AND (e.available_at IS NULL OR e.available_at <= $1))
|
|
13679
|
+
-- \u5D29\u6E83\u81EA\u6108\uFF1A\u89C1 claimNextEvent \u7684\u6CE8\u91CA
|
|
13680
|
+
OR ($2::timestamptz IS NOT NULL AND e.status = 'applied' AND e.applied_at < $2::timestamptz)
|
|
13681
|
+
)
|
|
13488
13682
|
AND NOT EXISTS (SELECT 1 FROM ${this.s}.events p WHERE p.workorder_id = e.workorder_id
|
|
13489
13683
|
AND p.status IN ('pending','applied') AND p.seq < e.seq)
|
|
13490
13684
|
-- \u2605 scan \u4E8B\u4EF6\u6392\u524D\u9762\uFF1Aorigin='apply' \u4E14\u65E0\u4E0A\u6E38\u4E8B\u4EF6\uFF08\u4E0D\u662F\u88AB\u5176\u4ED6\u4E8B\u4EF6\u89E6\u53D1\u7684\uFF09
|
|
@@ -13494,7 +13688,7 @@ var init_store_postgres = __esm({
|
|
|
13494
13688
|
CASE WHEN e.origin = 'apply' AND e.caused_by_seq IS NULL THEN 0 ELSE 1 END,
|
|
13495
13689
|
e.seq
|
|
13496
13690
|
FOR UPDATE OF e SKIP LOCKED`,
|
|
13497
|
-
[now]
|
|
13691
|
+
[now, recoverAppliedBefore ?? null]
|
|
13498
13692
|
);
|
|
13499
13693
|
return res.rows.map(toRecord);
|
|
13500
13694
|
}
|
|
@@ -13513,7 +13707,25 @@ var init_store_postgres = __esm({
|
|
|
13513
13707
|
async lockWorkorder(workorderId) {
|
|
13514
13708
|
await this.c.query(`SELECT 1 FROM ${this.s}.workorders WHERE id = $1 FOR UPDATE`, [workorderId]);
|
|
13515
13709
|
}
|
|
13516
|
-
async listEvents(workorderId, fromSeq = 0) {
|
|
13710
|
+
async listEvents(workorderId, fromSeq = 0, limit, kinds) {
|
|
13711
|
+
if (kinds && kinds.length > 0) {
|
|
13712
|
+
const res2 = workorderId ? await this.c.query(
|
|
13713
|
+
`SELECT * FROM ${this.s}.events WHERE workorder_id = $1 AND seq > $2 AND kind = ANY($3) ORDER BY seq${limit !== void 0 ? ` DESC LIMIT ${Number(limit)}` : ""}`,
|
|
13714
|
+
[workorderId, fromSeq, [...kinds]]
|
|
13715
|
+
) : await this.c.query(
|
|
13716
|
+
`SELECT * FROM ${this.s}.events WHERE seq > $1 AND kind = ANY($2) ORDER BY seq${limit !== void 0 ? ` DESC LIMIT ${Number(limit)}` : ""}`,
|
|
13717
|
+
[fromSeq, [...kinds]]
|
|
13718
|
+
);
|
|
13719
|
+
const rows = res2.rows.map(toRecord);
|
|
13720
|
+
return limit !== void 0 ? rows.reverse() : rows;
|
|
13721
|
+
}
|
|
13722
|
+
if (limit !== void 0) {
|
|
13723
|
+
const res2 = workorderId ? await this.c.query(
|
|
13724
|
+
`SELECT * FROM ${this.s}.events WHERE workorder_id = $1 AND seq > $2 ORDER BY seq DESC LIMIT $3`,
|
|
13725
|
+
[workorderId, fromSeq, limit]
|
|
13726
|
+
) : await this.c.query(`SELECT * FROM ${this.s}.events WHERE seq > $1 ORDER BY seq DESC LIMIT $2`, [fromSeq, limit]);
|
|
13727
|
+
return res2.rows.map(toRecord).reverse();
|
|
13728
|
+
}
|
|
13517
13729
|
const res = workorderId ? await this.c.query(
|
|
13518
13730
|
`SELECT * FROM ${this.s}.events WHERE workorder_id = $1 AND seq > $2 ORDER BY seq`,
|
|
13519
13731
|
[workorderId, fromSeq]
|
|
@@ -13633,6 +13845,7 @@ function newNode(id, workorderId, at, version2) {
|
|
|
13633
13845
|
assigneeActorId: null,
|
|
13634
13846
|
assigneeRole: null,
|
|
13635
13847
|
latestWorkId: null,
|
|
13848
|
+
latestSuccessWorkId: null,
|
|
13636
13849
|
latestAcceptId: null,
|
|
13637
13850
|
isFinalOutput: false,
|
|
13638
13851
|
cancelledAt: null,
|
|
@@ -13961,6 +14174,10 @@ var init_workorder = __esm({
|
|
|
13961
14174
|
});
|
|
13962
14175
|
|
|
13963
14176
|
// ../engine/src/handlers/work.ts
|
|
14177
|
+
function latestSuccessOf(state, nodeId) {
|
|
14178
|
+
const successes = state.worksOf(nodeId).filter((w2) => !w2.replyToIssueId && w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
14179
|
+
return successes.length > 0 ? successes[successes.length - 1].id : null;
|
|
14180
|
+
}
|
|
13964
14181
|
var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workTimeout;
|
|
13965
14182
|
var init_work = __esm({
|
|
13966
14183
|
"../engine/src/handlers/work.ts"() {
|
|
@@ -14064,7 +14281,7 @@ var init_work = __esm({
|
|
|
14064
14281
|
// 回信 work 不杀前会话(apply 不 kill 主链,节点已完结、无在跑会话),只派发。
|
|
14065
14282
|
async effect(e, ctx) {
|
|
14066
14283
|
if (!e.replyToIssueId) await ctx.cancelPreviousWork(e.nodeId, e.workId);
|
|
14067
|
-
await ctx.dispatchWork(e.workId);
|
|
14284
|
+
await ctx.dispatchWork(e.workId, e.resumeEligible !== void 0 ? { resumeEligible: e.resumeEligible } : void 0);
|
|
14068
14285
|
}
|
|
14069
14286
|
};
|
|
14070
14287
|
workKill = {
|
|
@@ -14090,6 +14307,12 @@ var init_work = __esm({
|
|
|
14090
14307
|
...w2.status !== "dead" ? { status: "dead" } : {}
|
|
14091
14308
|
});
|
|
14092
14309
|
}
|
|
14310
|
+
if (node.latestSuccessWorkId) {
|
|
14311
|
+
const ls = state.work(node.latestSuccessWorkId);
|
|
14312
|
+
if (!ls || ls.status !== "success") {
|
|
14313
|
+
state.updateNode(e.nodeId, { latestSuccessWorkId: latestSuccessOf(state, e.nodeId) });
|
|
14314
|
+
}
|
|
14315
|
+
}
|
|
14093
14316
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
14094
14317
|
},
|
|
14095
14318
|
async effect(e, ctx) {
|
|
@@ -14123,6 +14346,9 @@ var init_work = __esm({
|
|
|
14123
14346
|
outcomeDetail: businessFailed ? { ...e.detail ?? {}, reason: "agent business handoff not delivered" } : e.detail,
|
|
14124
14347
|
status
|
|
14125
14348
|
});
|
|
14349
|
+
if (status === "success") {
|
|
14350
|
+
state.updateNode(w2.nodeId, { latestSuccessWorkId: e.workId });
|
|
14351
|
+
}
|
|
14126
14352
|
}
|
|
14127
14353
|
};
|
|
14128
14354
|
workHandoffRecorded = {
|
|
@@ -14132,17 +14358,20 @@ var init_work = __esm({
|
|
|
14132
14358
|
const w2 = state.work(e.workId);
|
|
14133
14359
|
if (!w2 || e.producedBy !== "agent") return;
|
|
14134
14360
|
if (w2.agentHandoffAttemptId && w2.agentHandoffAttemptId !== e.attemptId) return;
|
|
14361
|
+
const flipsToFailed = e.status === "not_delivered" && w2.endedAt && !w2.deadAt;
|
|
14135
14362
|
state.updateWork(e.workId, {
|
|
14136
14363
|
agentHandoffAttemptId: e.attemptId,
|
|
14137
14364
|
businessHandoffStatus: e.status,
|
|
14138
14365
|
businessHandoffRecordedAt: ctx.at,
|
|
14139
|
-
|
|
14140
|
-
...e.status === "not_delivered" && w2.endedAt && !w2.deadAt ? {
|
|
14366
|
+
...flipsToFailed ? {
|
|
14141
14367
|
status: "failed",
|
|
14142
14368
|
outcome: "failed",
|
|
14143
14369
|
outcomeDetail: { ...w2.outcomeDetail ?? {}, reason: "agent business handoff not delivered" }
|
|
14144
14370
|
} : {}
|
|
14145
14371
|
});
|
|
14372
|
+
if (flipsToFailed && state.node(w2.nodeId)?.latestSuccessWorkId === e.workId) {
|
|
14373
|
+
state.updateNode(w2.nodeId, { latestSuccessWorkId: latestSuccessOf(state, w2.nodeId) });
|
|
14374
|
+
}
|
|
14146
14375
|
}
|
|
14147
14376
|
};
|
|
14148
14377
|
workStart = {
|
|
@@ -14226,6 +14455,7 @@ var init_issue = __esm({
|
|
|
14226
14455
|
authorActorId: e.authorActorId,
|
|
14227
14456
|
handActorId: e.handActorId,
|
|
14228
14457
|
raisedByNodeId: e.raisedByNodeId,
|
|
14458
|
+
gapId: e.gapId ?? null,
|
|
14229
14459
|
proposedResolution: null,
|
|
14230
14460
|
proposedBy: null,
|
|
14231
14461
|
proposedAt: null,
|
|
@@ -14291,6 +14521,22 @@ var init_issue = __esm({
|
|
|
14291
14521
|
resolvedNote: e.note,
|
|
14292
14522
|
resolvedViaWorkId: e.viaWorkId
|
|
14293
14523
|
});
|
|
14524
|
+
if (issue2.kind === "gap") {
|
|
14525
|
+
for (const other of state.allIssues()) {
|
|
14526
|
+
if (other.kind === "escalation" && other.resolvedAt === null && other.workorderId === issue2.workorderId && other.gapId === issue2.id) {
|
|
14527
|
+
state.updateIssue(other.id, {
|
|
14528
|
+
proposedResolution: null,
|
|
14529
|
+
proposedBy: null,
|
|
14530
|
+
proposedAt: null,
|
|
14531
|
+
resolvedAt: ctx.at,
|
|
14532
|
+
resolution: "resolved",
|
|
14533
|
+
resolvedBy: "actor:system:engine",
|
|
14534
|
+
resolvedNote: e.note,
|
|
14535
|
+
resolvedViaWorkId: null
|
|
14536
|
+
});
|
|
14537
|
+
}
|
|
14538
|
+
}
|
|
14539
|
+
}
|
|
14294
14540
|
if (issue2.kind === "gap" || issue2.kind === "escalation") {
|
|
14295
14541
|
const nodeId = issue2.raisedByNodeId ?? issue2.aboutNodeId;
|
|
14296
14542
|
if (nodeId) markNodeForRetry(state, nodeId, ctx.at);
|
|
@@ -14461,11 +14707,9 @@ var init_review = __esm({
|
|
|
14461
14707
|
name: "work/accept",
|
|
14462
14708
|
kind: "work.accept",
|
|
14463
14709
|
apply(e, state, ctx) {
|
|
14710
|
+
const block = acceptBlock(state, e.workId);
|
|
14711
|
+
if (block && !(block.overridable && e.override)) return;
|
|
14464
14712
|
const w2 = state.work(e.workId);
|
|
14465
|
-
if (!w2 || w2.deadAt || w2.acceptanceState === "accepted") return;
|
|
14466
|
-
const node = state.node(w2.nodeId);
|
|
14467
|
-
if (!node) return;
|
|
14468
|
-
if (!businessHandoffBarrierSatisfied(state, w2.nodeId, w2.id)) return;
|
|
14469
14713
|
if (w2.replyToIssueId) {
|
|
14470
14714
|
state.updateWork(e.workId, {
|
|
14471
14715
|
acceptanceState: "accepted",
|
|
@@ -14673,7 +14917,7 @@ function emptySnapshotPlaceholder(record8) {
|
|
|
14673
14917
|
reviews: []
|
|
14674
14918
|
};
|
|
14675
14919
|
}
|
|
14676
|
-
var SYSTEM_ACTOR2, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, EngineBus, ApplyFailure, sleep;
|
|
14920
|
+
var SYSTEM_ACTOR2, NOOP_IO, SYSTEM_CLOCK, HandlerRegistry, DEFAULT_STUCK_APPLIED_RECOVERY_MS, EngineBus, ApplyFailure, sleep;
|
|
14677
14921
|
var init_bus = __esm({
|
|
14678
14922
|
"../engine/src/bus.ts"() {
|
|
14679
14923
|
"use strict";
|
|
@@ -14711,6 +14955,7 @@ var init_bus = __esm({
|
|
|
14711
14955
|
return [...this.byKind.values()].flat();
|
|
14712
14956
|
}
|
|
14713
14957
|
};
|
|
14958
|
+
DEFAULT_STUCK_APPLIED_RECOVERY_MS = 5 * 6e4;
|
|
14714
14959
|
EngineBus = class {
|
|
14715
14960
|
store;
|
|
14716
14961
|
io;
|
|
@@ -14719,6 +14964,7 @@ var init_bus = __esm({
|
|
|
14719
14964
|
policy;
|
|
14720
14965
|
registry;
|
|
14721
14966
|
maxEffectAttempts;
|
|
14967
|
+
stuckAppliedRecoveryMs;
|
|
14722
14968
|
running = false;
|
|
14723
14969
|
/** 事件监听器(内存 pubsub):每批事件处理完调用,供 /api/events SSE 推送(替代旧 oplog.subscribe)。 */
|
|
14724
14970
|
eventListener = null;
|
|
@@ -14730,10 +14976,19 @@ var init_bus = __esm({
|
|
|
14730
14976
|
this.policy = opts.policy ?? CODE_DEFAULT_POLICY;
|
|
14731
14977
|
this.registry = new HandlerRegistry(opts.handlers);
|
|
14732
14978
|
this.maxEffectAttempts = opts.maxEffectAttempts ?? 3;
|
|
14979
|
+
this.stuckAppliedRecoveryMs = opts.stuckAppliedRecoveryMs ?? DEFAULT_STUCK_APPLIED_RECOVERY_MS;
|
|
14733
14980
|
}
|
|
14734
14981
|
setIO(io) {
|
|
14735
14982
|
this.io = io;
|
|
14736
14983
|
}
|
|
14984
|
+
/**
|
|
14985
|
+
* 崩溃自愈水位线:早于此刻仍停在 `applied` 的事件,认为是「进程死在落状态与收尾之间」的残留,
|
|
14986
|
+
* 回收重跑。`stuckAppliedRecoveryMs <= 0` 时返回 undefined(= 关闭自愈,维持旧行为)。
|
|
14987
|
+
*/
|
|
14988
|
+
recoverAppliedBefore() {
|
|
14989
|
+
if (this.stuckAppliedRecoveryMs <= 0) return void 0;
|
|
14990
|
+
return new Date(Date.parse(this.clock.now()) - this.stuckAppliedRecoveryMs).toISOString();
|
|
14991
|
+
}
|
|
14737
14992
|
/** 注册事件监听器——新事件处理完即回调(serve 桥接 SSE 实时推送,替代 oplog.subscribe)。返回退订。 */
|
|
14738
14993
|
onEvent(fn) {
|
|
14739
14994
|
this.eventListener = fn;
|
|
@@ -14742,8 +14997,10 @@ var init_bus = __esm({
|
|
|
14742
14997
|
};
|
|
14743
14998
|
}
|
|
14744
14999
|
/** 读最近事件(/api/events 首次连接回放 / 调试视图),替代 oplog.read。 */
|
|
15000
|
+
/** 最近 N 条事件(时间线/详情页用)。★ 2026-08-16:limit 以前**声明了但没传下去**——
|
|
15001
|
+
* 百万级事件的病态工单一次性全量拉回内存,直接把 serve 打崩(ws:wo-18c2bc4b 实证)。 */
|
|
14745
15002
|
recentEvents(workorderId, fromSeq, limit = 200) {
|
|
14746
|
-
return this.store.transaction((tx) => tx.listEvents(workorderId, fromSeq));
|
|
15003
|
+
return this.store.transaction((tx) => tx.listEvents(workorderId, fromSeq, limit));
|
|
14747
15004
|
}
|
|
14748
15005
|
/** 取存储引用——供 serve 层创建 IO 适配器时使用。 */
|
|
14749
15006
|
getStore() {
|
|
@@ -14820,7 +15077,7 @@ var init_bus = __esm({
|
|
|
14820
15077
|
let lastFullScan = 0;
|
|
14821
15078
|
while (true) {
|
|
14822
15079
|
try {
|
|
14823
|
-
const batch = await this.store.transaction((tx) => tx.claimAllPending(this.clock.now()));
|
|
15080
|
+
const batch = await this.store.transaction((tx) => tx.claimAllPending(this.clock.now(), this.recoverAppliedBefore()));
|
|
14824
15081
|
const touched = /* @__PURE__ */ new Set();
|
|
14825
15082
|
if (batch.length > 0) {
|
|
14826
15083
|
for (const record8 of batch) {
|
|
@@ -15021,7 +15278,7 @@ var init_bus = __esm({
|
|
|
15021
15278
|
eventSeq: record8.seq,
|
|
15022
15279
|
actorId: record8.actorId,
|
|
15023
15280
|
emit: (event, opts) => void buffered.push({ event, ...opts }),
|
|
15024
|
-
dispatchWork: (id) => this.io.dispatchWork(id),
|
|
15281
|
+
dispatchWork: (id, opts) => this.io.dispatchWork(id, opts),
|
|
15025
15282
|
dispatchReview: (id, nodeId, reviewerActorId) => this.io.dispatchReview(id, nodeId, reviewerActorId),
|
|
15026
15283
|
cancelSession: (ref2) => this.io.cancelSession(ref2),
|
|
15027
15284
|
// ★ work.create 的「kill 旧会话」:apply 已把该节点上一个 latest_work 置 deadAt(会话可能还挂着),
|
|
@@ -15053,6 +15310,10 @@ var init_bus = __esm({
|
|
|
15053
15310
|
};
|
|
15054
15311
|
let ok = true;
|
|
15055
15312
|
for (const h of handlers) {
|
|
15313
|
+
const effectKey = `${h.name}#effect`;
|
|
15314
|
+
if (record8.appliedAt && await this.store.transaction((tx) => tx.isHandlerDone(record8.seq, effectKey))) {
|
|
15315
|
+
continue;
|
|
15316
|
+
}
|
|
15056
15317
|
let lastErr = "";
|
|
15057
15318
|
for (let attempt = 1; attempt <= this.maxEffectAttempts; attempt++) {
|
|
15058
15319
|
try {
|
|
@@ -15065,6 +15326,8 @@ var init_bus = __esm({
|
|
|
15065
15326
|
}
|
|
15066
15327
|
if (lastErr) {
|
|
15067
15328
|
ok = false;
|
|
15329
|
+
} else {
|
|
15330
|
+
await this.store.transaction((tx) => tx.markHandlerDone(record8.seq, effectKey));
|
|
15068
15331
|
}
|
|
15069
15332
|
}
|
|
15070
15333
|
await this.store.transaction(async (tx) => {
|
|
@@ -15123,7 +15386,7 @@ var init_bus = __esm({
|
|
|
15123
15386
|
/* ═══════════════════════ 内部:旧 applyPhase(claim + apply 一体) ═══════════════════════ */
|
|
15124
15387
|
async applyPhase() {
|
|
15125
15388
|
return this.store.transaction(async (tx) => {
|
|
15126
|
-
const record8 = await tx.claimNextEvent(this.clock.now());
|
|
15389
|
+
const record8 = await tx.claimNextEvent(this.clock.now(), this.recoverAppliedBefore());
|
|
15127
15390
|
if (!record8) return null;
|
|
15128
15391
|
const snapshot = await tx.loadWorkorder(record8.workorderId);
|
|
15129
15392
|
if (snapshot?.workorder.doneAt) {
|
|
@@ -15248,6 +15511,10 @@ function mapWorkStateToRevisionState(w2) {
|
|
|
15248
15511
|
if (w2.acceptanceState === "rejected") return "rejected";
|
|
15249
15512
|
return "proposed";
|
|
15250
15513
|
// success but not yet judged
|
|
15514
|
+
// ★ ADR 0148:failed 尝试没有对应的 RevisionState(枚举只有 working/proposed/merged/rejected/
|
|
15515
|
+
// superseded,改枚举会牵动契约与前端,见红线 4)——仍投影成 superseded 保持生命周期语义,但**内容
|
|
15516
|
+
// 如实为 empty**(workToRevision 对无产出 work 给 contentKind="empty")。读面(CLI 版本列表)据此把
|
|
15517
|
+
// 「superseded + empty」如实标成「未产出」,不再让「这次失败了」被读成「被更好的版本取代」。
|
|
15251
15518
|
case "failed":
|
|
15252
15519
|
return "superseded";
|
|
15253
15520
|
case "retry":
|
|
@@ -15491,6 +15758,18 @@ function bridgeLoadReadModel(input) {
|
|
|
15491
15758
|
name: a.name
|
|
15492
15759
|
})));
|
|
15493
15760
|
}
|
|
15761
|
+
const acceptBlockers = /* @__PURE__ */ new Map();
|
|
15762
|
+
for (const snap of snapshots2) {
|
|
15763
|
+
let state = null;
|
|
15764
|
+
for (const node of snap.nodes) {
|
|
15765
|
+
if (!node.latestWorkId || node.latestAcceptId === node.latestWorkId) continue;
|
|
15766
|
+
const lw = snap.works.find((w2) => w2.id === node.latestWorkId);
|
|
15767
|
+
if (!lw || !lw.endedAt || lw.deadAt) continue;
|
|
15768
|
+
state ??= new WorkorderState(snap);
|
|
15769
|
+
const block = acceptBlock(state, lw.id);
|
|
15770
|
+
if (block) acceptBlockers.set(node.id, block);
|
|
15771
|
+
}
|
|
15772
|
+
}
|
|
15494
15773
|
const artifactFiles = /* @__PURE__ */ new Map();
|
|
15495
15774
|
for (const a of allArtifacts) {
|
|
15496
15775
|
const list = artifactFiles.get(a.workId) ?? [];
|
|
@@ -15505,8 +15784,16 @@ function bridgeLoadReadModel(input) {
|
|
|
15505
15784
|
const lw = node.latestWorkId ? workById.get(node.latestWorkId) : void 0;
|
|
15506
15785
|
if (lw) {
|
|
15507
15786
|
const st = lw.status ?? (lw.retryAt ? "retry" : lw.deadAt ? "dead" : lw.endedAt ? lw.outcome === "failed" ? "failed" : "success" : "running");
|
|
15508
|
-
|
|
15509
|
-
|
|
15787
|
+
let uiSt;
|
|
15788
|
+
if (st === "retry") uiSt = null;
|
|
15789
|
+
else if (st === "running") uiSt = "running";
|
|
15790
|
+
else if (node.latestSuccessWorkId) uiSt = "success";
|
|
15791
|
+
else uiSt = st;
|
|
15792
|
+
art.latestWorkState = uiSt;
|
|
15793
|
+
art.latestAccepted = node.latestSuccessWorkId !== null && node.latestAcceptId === node.latestSuccessWorkId;
|
|
15794
|
+
if ((st === "failed" || st === "dead") && node.latestSuccessWorkId) {
|
|
15795
|
+
art.currentRev = node.latestSuccessWorkId;
|
|
15796
|
+
}
|
|
15510
15797
|
} else {
|
|
15511
15798
|
art.latestWorkState = null;
|
|
15512
15799
|
art.latestAccepted = false;
|
|
@@ -15685,7 +15972,8 @@ function bridgeLoadReadModel(input) {
|
|
|
15685
15972
|
pinMeta,
|
|
15686
15973
|
gaps,
|
|
15687
15974
|
escalations,
|
|
15688
|
-
pendingReviews
|
|
15975
|
+
pendingReviews,
|
|
15976
|
+
acceptBlockers
|
|
15689
15977
|
};
|
|
15690
15978
|
}
|
|
15691
15979
|
var init_bridge_readmodel = __esm({
|
|
@@ -15695,6 +15983,26 @@ var init_bridge_readmodel = __esm({
|
|
|
15695
15983
|
}
|
|
15696
15984
|
});
|
|
15697
15985
|
|
|
15986
|
+
// ../core/src/propose-guard.ts
|
|
15987
|
+
function proposeBlock(head, headArtifactCount) {
|
|
15988
|
+
if (!head) return null;
|
|
15989
|
+
const state = workState(head, hasOutput(head, headArtifactCount));
|
|
15990
|
+
if (state === "dead") {
|
|
15991
|
+
const why = head.rejectedReason ? `\uFF1A${head.rejectedReason}` : "";
|
|
15992
|
+
return {
|
|
15993
|
+
code: "head-dead",
|
|
15994
|
+
detail: `\u8FD9\u4E00\u7248\uFF08${head.id}\uFF09\u5DF2\u88AB\u505C\u6389\uFF08${head.deadAt ?? "?"}${why}\uFF09\u2014\u2014\u4F5C\u5E9F\u7248\u4E4B\u4E0A\u4E0D\u80FD\u518D\u4EA4\u7A3F\u3002\u8981\u7EE7\u7EED\uFF0C\u5148\u91CD\u8DD1\u672C\u8282\u70B9\uFF08\u5728\u63A7\u5236\u53F0\u70B9\u300C\u91CD\u65B0\u8FD0\u884C\u300D\uFF0C\u6216\u8BA9\u7BA1\u7406\u8005\u91CD\u6D3E\uFF09\u62FF\u5230\u4E00\u4E2A\u53EF\u5199\u7684\u65B0\u7248\u672C\uFF0C\u518D propose\u3002`
|
|
15995
|
+
};
|
|
15996
|
+
}
|
|
15997
|
+
return null;
|
|
15998
|
+
}
|
|
15999
|
+
var init_propose_guard = __esm({
|
|
16000
|
+
"../core/src/propose-guard.ts"() {
|
|
16001
|
+
"use strict";
|
|
16002
|
+
init_src3();
|
|
16003
|
+
}
|
|
16004
|
+
});
|
|
16005
|
+
|
|
15698
16006
|
// ../core/src/preview-intervention.ts
|
|
15699
16007
|
function resolveOwner(type, typeDef, roleIndex, schemaSize) {
|
|
15700
16008
|
if (!typeDef) {
|
|
@@ -16160,6 +16468,7 @@ var init_kernel_bridge = __esm({
|
|
|
16160
16468
|
init_closure_gate();
|
|
16161
16469
|
init_kernel();
|
|
16162
16470
|
init_bridge_readmodel();
|
|
16471
|
+
init_propose_guard();
|
|
16163
16472
|
init_src3();
|
|
16164
16473
|
init_src3();
|
|
16165
16474
|
init_preview_intervention();
|
|
@@ -16490,8 +16799,14 @@ var init_kernel_bridge = __esm({
|
|
|
16490
16799
|
}
|
|
16491
16800
|
}
|
|
16492
16801
|
if (!workId) {
|
|
16493
|
-
const
|
|
16494
|
-
|
|
16802
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16803
|
+
const headId = snap?.nodes.find((n) => n.id === args.artifactId)?.latestWorkId ?? null;
|
|
16804
|
+
if (headId) {
|
|
16805
|
+
const headWork = snap?.works.find((w2) => w2.id === headId);
|
|
16806
|
+
const headOutputs = snap?.artifacts.filter((a) => a.workId === headId).length ?? 0;
|
|
16807
|
+
const block = proposeBlock(headWork, headOutputs);
|
|
16808
|
+
if (block) throw new KernelError(block.detail, block.code);
|
|
16809
|
+
}
|
|
16495
16810
|
const worksCount = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId).length;
|
|
16496
16811
|
const revisionId = `wk:${args.artifactId}:${worksCount + 1}`;
|
|
16497
16812
|
await this.commit({
|
|
@@ -16677,8 +16992,8 @@ var init_kernel_bridge = __esm({
|
|
|
16677
16992
|
const pendingHead = pendingConcludeAttempt(this.model, args.artifactId)?.head;
|
|
16678
16993
|
const targetWorkId = args.revisionId ?? (currentRev && currentRev.state === "proposed" ? currentRev.id : null) ?? pendingHead ?? concludedHead(this.model, args.artifactId);
|
|
16679
16994
|
if (!targetWorkId) throw new Error("no concluded head to review");
|
|
16680
|
-
|
|
16681
|
-
let
|
|
16995
|
+
const verdict = args.verdict === "approve" ? "approve" : "request_changes";
|
|
16996
|
+
let targets = [];
|
|
16682
16997
|
if (args.reviewId !== void 0) {
|
|
16683
16998
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16684
16999
|
const row = snap?.reviews.find((r) => r.id === args.reviewId) ?? null;
|
|
@@ -16698,32 +17013,67 @@ var init_kernel_bridge = __esm({
|
|
|
16698
17013
|
if (row.verdict === null && (row.cancelledAt || row.endedAt)) {
|
|
16699
17014
|
throw new Error(`review ${args.reviewId} is no longer open\uFF08\u8BE5\u8BC4\u5BA1\u884C\u5DF2\u8D85\u65F6\u5931\u8D25/\u88AB\u53D6\u6D88\uFF0C\u8FDF\u5230\u7968\u88AB\u62D2\uFF09`);
|
|
16700
17015
|
}
|
|
16701
|
-
|
|
16702
|
-
|
|
16703
|
-
} else if (!args.revisionId) {
|
|
17016
|
+
targets = [{ id: row.id, group: row.reviewGroup }];
|
|
17017
|
+
} else {
|
|
16704
17018
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16705
|
-
const
|
|
16706
|
-
|
|
16707
|
-
|
|
17019
|
+
const rows = snap?.reviews.filter(
|
|
17020
|
+
(r) => r.targetWorkId === targetWorkId && r.reviewerActorId === args.actor
|
|
17021
|
+
) ?? [];
|
|
17022
|
+
const open2 = rows.filter((r) => isReviewOpen(r));
|
|
17023
|
+
if (open2.length === 0) {
|
|
17024
|
+
const decided = rows.filter((r) => r.verdict !== null);
|
|
17025
|
+
if (decided.length > 0) {
|
|
17026
|
+
if (decided.every((r) => r.verdict === verdict)) {
|
|
17027
|
+
return { status: verdict === "approve" ? "satisfied" : "failed" };
|
|
17028
|
+
}
|
|
17029
|
+
throw new KernelError(
|
|
17030
|
+
`\u4F60\u5DF2\u5BF9\u8FD9\u4E00\u7248\u6295\u8FC7\u300C${decided[0].verdict}\u300D\uFF08${args.artifactId} @ ${targetWorkId}\uFF09\u2014\u2014\u540C\u4E00\u7248\u4E0D\u63A5\u53D7\u6539\u5224\uFF0C\u8981\u91CD\u6295\u8BF7\u5148\u91CD\u65B0\u53D1\u8D77\u8BC4\u5BA1\uFF08oasis rereview / \u63A7\u5236\u53F0\u300C\u91CD\u65B0\u8BC4\u5BA1\u300D\uFF09\u3002`,
|
|
17031
|
+
"review-already-decided"
|
|
17032
|
+
);
|
|
17033
|
+
}
|
|
17034
|
+
const reqs = snap?.requirements.filter((r) => r.nodeId === args.artifactId) ?? [];
|
|
17035
|
+
const mine = reqs.filter((r) => r.reviewerActorId === args.actor);
|
|
17036
|
+
if (mine.length > 0) {
|
|
17037
|
+
targets = [...new Set(mine.map((r) => r.reviewGroup))].map((group) => ({ id: null, group }));
|
|
17038
|
+
} else {
|
|
17039
|
+
const stale = (snap?.reviews ?? []).filter(
|
|
17040
|
+
(r) => r.nodeId === args.artifactId && r.reviewerActorId === args.actor && r.targetWorkId !== targetWorkId
|
|
17041
|
+
);
|
|
17042
|
+
if (reqs.length === 0) {
|
|
17043
|
+
throw new KernelError(
|
|
17044
|
+
`${args.artifactId} \u6CA1\u6709\u8BC4\u5BA1\u8981\u6C42\u2014\u2014\u8FD9\u4E2A\u8282\u70B9\u7684\u6536\u53E3\u4E0D\u9700\u8981\u6295\u7968\uFF0C\u4F60\u7684\u7968\u6CA1\u6709\u53EF\u843D\u7684\u884C\uFF08\u8981\u8D8A\u8FC7\u6D41\u7A0B\u76F4\u63A5\u6536\u53E3\u7528 force-conclude\uFF09\u3002`,
|
|
17045
|
+
"no-review-requirement"
|
|
17046
|
+
);
|
|
17047
|
+
}
|
|
17048
|
+
throw new KernelError(
|
|
17049
|
+
`\u8FD9\u4E00\u7248\u4E0A\u6CA1\u6709\u6D3E\u7ED9\u4F60\u7684\u8BC4\u5BA1\u884C\uFF08${args.artifactId} @ ${targetWorkId}\uFF09\uFF1A\u672C\u7248\u8BC4\u5BA1\u4EBA = [${[...new Set(reqs.map((r) => r.reviewerActorId))].join("\u3001")}]${stale.length > 0 ? `\uFF1B\u4F60\u5728\u65E7\u7248 ${stale[0].targetWorkId} \u4E0A\u6709\u4E00\u884C\uFF0C\u90A3\u4E00\u7248\u5DF2\u7ECF\u4E0D\u662F\u5F53\u524D\u5F85\u5BA1\u7248\u672C` : ""}\u2014\u2014\u7968\u6CA1\u6709\u843D\u8D26\uFF08\u4EE5\u524D\u8FD9\u91CC\u4F1A\u6084\u6084\u65B0\u5EFA\u4E00\u884C\u628A\u7968\u541E\u6389\uFF09\u3002`,
|
|
17050
|
+
"review-row-not-found"
|
|
17051
|
+
);
|
|
17052
|
+
}
|
|
17053
|
+
} else {
|
|
17054
|
+
targets = open2.map((r) => ({ id: r.id, group: r.reviewGroup }));
|
|
17055
|
+
}
|
|
16708
17056
|
}
|
|
16709
|
-
|
|
16710
|
-
|
|
16711
|
-
|
|
16712
|
-
actorId: args.actor,
|
|
16713
|
-
handActorId: args.hand ?? null,
|
|
16714
|
-
event: {
|
|
16715
|
-
kind: "review.response",
|
|
17057
|
+
for (const target of targets) {
|
|
17058
|
+
await this.commit({
|
|
17059
|
+
companyId: "",
|
|
16716
17060
|
workorderId: wid,
|
|
16717
|
-
|
|
16718
|
-
|
|
16719
|
-
|
|
16720
|
-
|
|
16721
|
-
|
|
16722
|
-
|
|
16723
|
-
|
|
16724
|
-
|
|
16725
|
-
|
|
16726
|
-
|
|
17061
|
+
actorId: args.actor,
|
|
17062
|
+
handActorId: args.hand ?? null,
|
|
17063
|
+
event: {
|
|
17064
|
+
kind: "review.response",
|
|
17065
|
+
workorderId: wid,
|
|
17066
|
+
reviewId: target.id,
|
|
17067
|
+
// 有行投行;无行时带真实桶名建本人的行(不再是没人指的 default 幽灵行)
|
|
17068
|
+
targetWorkId,
|
|
17069
|
+
reviewerActorId: args.actor,
|
|
17070
|
+
reviewGroup: target.group,
|
|
17071
|
+
verdict,
|
|
17072
|
+
note: args.note ?? null,
|
|
17073
|
+
handActorId: args.hand ?? null
|
|
17074
|
+
}
|
|
17075
|
+
});
|
|
17076
|
+
}
|
|
16727
17077
|
const attempt = this.model.concludeAttempts.get(args.artifactId)?.at(-1);
|
|
16728
17078
|
return attempt ? { status: args.verdict === "approve" ? "satisfied" : "failed" } : { status: "pending" };
|
|
16729
17079
|
}
|
|
@@ -16731,8 +17081,18 @@ var init_kernel_bridge = __esm({
|
|
|
16731
17081
|
const wid = this.wo(args.artifactId);
|
|
16732
17082
|
const artifact = this.model.artifacts.get(args.artifactId);
|
|
16733
17083
|
const currentRev = artifact?.currentRev ? this.model.revisions.get(artifact.currentRev) : void 0;
|
|
16734
|
-
const
|
|
16735
|
-
|
|
17084
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
17085
|
+
const targetNode = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
17086
|
+
const head = (currentRev && currentRev.state === "proposed" ? currentRev.id : null) ?? concludedHead(this.model, args.artifactId) ?? (targetNode?.latestSuccessWorkId ?? targetNode?.latestWorkId ?? null);
|
|
17087
|
+
if (!head) throw new KernelError(`force-conclude \u65E0\u4ECE\u4E0B\u624B\uFF08${args.artifactId}\uFF09\uFF1A\u672C\u8282\u70B9\u8FD8\u6CA1\u6709\u4EFB\u4F55\u4E00\u7248\u4EA7\u51FA`, "no-head");
|
|
17088
|
+
const block = snap ? acceptBlock(new WorkorderState(snap), head) : null;
|
|
17089
|
+
if (block?.code === "already-accepted") return;
|
|
17090
|
+
if (block && !block.overridable) {
|
|
17091
|
+
throw new KernelError(
|
|
17092
|
+
`force-conclude \u672A\u6267\u884C\uFF08${args.artifactId} @ ${head}\uFF09\uFF1A${block.detail}`,
|
|
17093
|
+
`accept-blocked:${block.code}`
|
|
17094
|
+
);
|
|
17095
|
+
}
|
|
16736
17096
|
await this.commit({
|
|
16737
17097
|
companyId: "",
|
|
16738
17098
|
workorderId: wid,
|
|
@@ -16745,6 +17105,14 @@ var init_kernel_bridge = __esm({
|
|
|
16745
17105
|
override: { by: args.actor, reason: args.reason ?? args.note ?? "force concluded" }
|
|
16746
17106
|
}
|
|
16747
17107
|
});
|
|
17108
|
+
const after = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
17109
|
+
const accepted = after?.works.find((w2) => w2.id === head)?.acceptanceState === "accepted";
|
|
17110
|
+
if (!accepted) {
|
|
17111
|
+
throw new KernelError(
|
|
17112
|
+
`force-conclude \u672A\u751F\u6548\uFF08${args.artifactId} @ ${head}\uFF09\uFF1Awork.accept \u4E8B\u4EF6\u5DF2\u5165 oplog\uFF0C\u4F46 apply \u672A\u6539\u53D8\u9A8C\u6536\u6001${block ? `\u2014\u2014\u4E8B\u524D\u5224\u636E\uFF1A${block.detail}` : "\u2014\u2014\u539F\u56E0\u672A\u77E5\uFF0C\u8BF7\u67E5 work/node \u73B0\u72B6"}`,
|
|
17113
|
+
"accept-no-effect"
|
|
17114
|
+
);
|
|
17115
|
+
}
|
|
16748
17116
|
}
|
|
16749
17117
|
async groupCommit(args) {
|
|
16750
17118
|
await this.merge({ artifactId: args.artifactId, actor: args.actor });
|
|
@@ -16953,6 +17321,7 @@ var init_kernel_bridge = __esm({
|
|
|
16953
17321
|
issueId,
|
|
16954
17322
|
issueKind: "escalation",
|
|
16955
17323
|
body,
|
|
17324
|
+
gapId: args.gapId,
|
|
16956
17325
|
aboutNodeId: args.artifactId,
|
|
16957
17326
|
aboutWorkId: null,
|
|
16958
17327
|
// ★ 修复(实证 ws:wo-a558f8cc):raisedByNodeId 只在 **agent/人自提**(/api/cmd escalate)时填节点——
|
|
@@ -17234,7 +17603,10 @@ var init_kernel_bridge = __esm({
|
|
|
17234
17603
|
});
|
|
17235
17604
|
}
|
|
17236
17605
|
async bumpPin(args) {
|
|
17237
|
-
|
|
17606
|
+
throw new KernelError(
|
|
17607
|
+
`pin \u5DF2\u5E9F\u5F03\uFF08${args.artifactId} \u2192 ${args.to}@${args.pinned} \u672A\u6267\u884C\uFF09\uFF1A\u65B0\u5F15\u64CE\u7684\u4E0B\u6E38\u7248\u672C\u56FA\u5B9A\u7531 work.accept \u81EA\u52A8\u63A8\u8FDB\u2014\u2014\u8981\u8BA9\u4E0B\u6E38\u6362\u7248\uFF0C\u53BB\u9A8C\u6536\u4E0A\u6E38\u5BF9\u5E94\u7684\u90A3\u4E00\u7248\uFF08conclude / force-conclude\uFF09\u3002`,
|
|
17608
|
+
"deprecated-op"
|
|
17609
|
+
);
|
|
17238
17610
|
}
|
|
17239
17611
|
async recordToolCall(_args) {
|
|
17240
17612
|
}
|
|
@@ -17331,6 +17703,7 @@ var init_kernel_bridge = __esm({
|
|
|
17331
17703
|
}
|
|
17332
17704
|
const wid = plan.workspace ?? plan.ops.find((o) => o.action === "spawn" && o.workspace)?.workspace ?? "ws:default";
|
|
17333
17705
|
let applied = 0;
|
|
17706
|
+
const unsupported = [];
|
|
17334
17707
|
await this.ensureWorkorder(wid, "", _actor);
|
|
17335
17708
|
for (const op of plan.ops) {
|
|
17336
17709
|
switch (op.action) {
|
|
@@ -17469,6 +17842,7 @@ var init_kernel_bridge = __esm({
|
|
|
17469
17842
|
break;
|
|
17470
17843
|
}
|
|
17471
17844
|
default:
|
|
17845
|
+
unsupported.push(`${op.action}\uFF08${op.artifactId ?? "?"}\uFF09`);
|
|
17472
17846
|
break;
|
|
17473
17847
|
}
|
|
17474
17848
|
}
|
|
@@ -17492,6 +17866,12 @@ var init_kernel_bridge = __esm({
|
|
|
17492
17866
|
this.draftCards.delete(plan.resolvesProposal);
|
|
17493
17867
|
await this.refreshModel();
|
|
17494
17868
|
}
|
|
17869
|
+
if (unsupported.length > 0) {
|
|
17870
|
+
throw new KernelError(
|
|
17871
|
+
`\u5DF2\u843D\u5730 ${applied} \u6761 op\uFF1B\u4EE5\u4E0B op \u672C\u5F15\u64CE\u4E0D\u652F\u6301\u3001\u672A\u6267\u884C\uFF1A${unsupported.join("\u3001")}\uFF08pin \u5DF2\u5E9F\u5F03\u2014\u2014\u4E0B\u6E38\u7248\u672C\u7531\u9A8C\u6536\u4E0A\u6E38\u81EA\u52A8\u63A8\u8FDB\uFF09`,
|
|
17872
|
+
"unsupported-op"
|
|
17873
|
+
);
|
|
17874
|
+
}
|
|
17495
17875
|
return { interventionId: `intervention:${(0, import_node_crypto3.randomUUID)()}`, applied };
|
|
17496
17876
|
}
|
|
17497
17877
|
};
|
|
@@ -17516,6 +17896,7 @@ var init_src4 = __esm({
|
|
|
17516
17896
|
init_workorder_ledger();
|
|
17517
17897
|
init_closure_gate();
|
|
17518
17898
|
init_exit_classify();
|
|
17899
|
+
init_produce_resume_fallback();
|
|
17519
17900
|
init_dispatcher();
|
|
17520
17901
|
init_session_limits();
|
|
17521
17902
|
init_kernel_bridge();
|
|
@@ -160686,6 +161067,9 @@ ${task}` : task;
|
|
|
160686
161067
|
});
|
|
160687
161068
|
|
|
160688
161069
|
// ../adapters/src/_core/workdir-gc.ts
|
|
161070
|
+
function reclaimableBytes(c) {
|
|
161071
|
+
return c.sizeBytes + (c.runtimeStateBytes ?? 0);
|
|
161072
|
+
}
|
|
160689
161073
|
function idleMs(c, now) {
|
|
160690
161074
|
const last = c.meta?.lastUsedAt ? Date.parse(c.meta.lastUsedAt) : NaN;
|
|
160691
161075
|
return now - (Number.isFinite(last) ? last : c.mtimeMs);
|
|
@@ -160706,19 +161090,58 @@ function decideOne(c, policy, now) {
|
|
|
160706
161090
|
}
|
|
160707
161091
|
if (c.artifactState === void 0) return mk("keep", "artifact-unknown");
|
|
160708
161092
|
if (c.artifactState === "active") {
|
|
160709
|
-
|
|
160710
|
-
return c.meta.lastExit && idle > policy.abnormalGraceMs ? mk("delete", "one-shot-ttl") : mk("keep", "artifact-active");
|
|
161093
|
+
return mk("keep", "artifact-active");
|
|
160711
161094
|
}
|
|
160712
|
-
if (c.meta.lastExit?.reason === "clean") return mk("delete", "settled-clean");
|
|
160713
161095
|
return idle > policy.ttlMs ? mk("delete", "settled-ttl") : mk("keep", "within-ttl");
|
|
160714
161096
|
}
|
|
160715
161097
|
function planGc(candidates, policy, now) {
|
|
160716
161098
|
const decisions = candidates.map((c) => decideOne(c, policy, now));
|
|
160717
|
-
const { highWaterBytes, lowWaterBytes } = policy;
|
|
160718
|
-
if (highWaterBytes === void 0) return decisions;
|
|
160719
161099
|
const byDir = new Map(candidates.map((c) => [c.dir, c]));
|
|
161100
|
+
applyRetentionBudget(decisions, byDir, policy, now);
|
|
161101
|
+
applyHighWaterLru(decisions, byDir, policy, now);
|
|
161102
|
+
return decisions;
|
|
161103
|
+
}
|
|
161104
|
+
function applyRetentionBudget(decisions, byDir, policy, now) {
|
|
161105
|
+
const maxRounds = policy.maxRoundsPerArtifact;
|
|
161106
|
+
const capBytes = policy.sessionStoreCapBytes;
|
|
161107
|
+
const evictable = () => decisions.filter((d) => d.action === "keep").map((d) => ({ d, c: byDir.get(d.dir) })).filter(({ c }) => !c.live && (c.artifactState === "active" || c.artifactState === "settled"));
|
|
161108
|
+
if (maxRounds !== void 0 && maxRounds >= 0) {
|
|
161109
|
+
const byArtifact = /* @__PURE__ */ new Map();
|
|
161110
|
+
for (const item of evictable()) {
|
|
161111
|
+
const id = item.c.meta?.artifactId;
|
|
161112
|
+
if (!id) continue;
|
|
161113
|
+
(byArtifact.get(id) ?? byArtifact.set(id, []).get(id)).push(item);
|
|
161114
|
+
}
|
|
161115
|
+
for (const group of byArtifact.values()) {
|
|
161116
|
+
if (group.length <= maxRounds) continue;
|
|
161117
|
+
group.sort((a, b2) => idleMs(a.c, now) - idleMs(b2.c, now));
|
|
161118
|
+
for (const { d } of group.slice(maxRounds)) {
|
|
161119
|
+
d.action = "delete";
|
|
161120
|
+
d.reason = "budget-rounds";
|
|
161121
|
+
}
|
|
161122
|
+
}
|
|
161123
|
+
}
|
|
161124
|
+
if (capBytes !== void 0) {
|
|
161125
|
+
let remaining = decisions.filter((d) => d.action === "keep").reduce((sum, d) => sum + reclaimableBytes(byDir.get(d.dir)), 0);
|
|
161126
|
+
if (remaining > capBytes) {
|
|
161127
|
+
const tier = (c) => c.artifactState === "settled" ? 0 : 1;
|
|
161128
|
+
const order = evictable().sort(
|
|
161129
|
+
(a, b2) => tier(a.c) - tier(b2.c) || idleMs(b2.c, now) - idleMs(a.c, now)
|
|
161130
|
+
);
|
|
161131
|
+
for (const { d, c } of order) {
|
|
161132
|
+
if (remaining <= capBytes) break;
|
|
161133
|
+
d.action = "delete";
|
|
161134
|
+
d.reason = "budget-cap";
|
|
161135
|
+
remaining -= reclaimableBytes(c);
|
|
161136
|
+
}
|
|
161137
|
+
}
|
|
161138
|
+
}
|
|
161139
|
+
}
|
|
161140
|
+
function applyHighWaterLru(decisions, byDir, policy, now) {
|
|
161141
|
+
const { highWaterBytes, lowWaterBytes } = policy;
|
|
161142
|
+
if (highWaterBytes === void 0) return;
|
|
160720
161143
|
let remaining = decisions.filter((d) => d.action === "keep").reduce((sum, d) => sum + d.sizeBytes, 0);
|
|
160721
|
-
if (remaining <= highWaterBytes) return
|
|
161144
|
+
if (remaining <= highWaterBytes) return;
|
|
160722
161145
|
const target = lowWaterBytes ?? highWaterBytes;
|
|
160723
161146
|
const lru = decisions.filter((d) => d.action === "keep").map((d) => ({ d, c: byDir.get(d.dir) })).filter(({ c }) => !c.live && c.artifactState === "settled").sort((a, b2) => idleMs(b2.c, now) - idleMs(a.c, now));
|
|
160724
161147
|
for (const { d } of lru) {
|
|
@@ -160727,17 +161150,23 @@ function planGc(candidates, policy, now) {
|
|
|
160727
161150
|
d.reason = "high-water-lru";
|
|
160728
161151
|
remaining -= d.sizeBytes;
|
|
160729
161152
|
}
|
|
160730
|
-
return decisions;
|
|
160731
161153
|
}
|
|
160732
|
-
function gcAccounting(decisions, policy) {
|
|
160733
|
-
const
|
|
160734
|
-
const
|
|
161154
|
+
function gcAccounting(decisions, policy, candidates) {
|
|
161155
|
+
const byDir = candidates ? new Map(candidates.map((c) => [c.dir, c])) : void 0;
|
|
161156
|
+
const sizeOf = (d) => {
|
|
161157
|
+
const c = byDir?.get(d.dir);
|
|
161158
|
+
return c ? reclaimableBytes(c) : d.sizeBytes;
|
|
161159
|
+
};
|
|
161160
|
+
const totalBytes = decisions.reduce((s2, d) => s2 + sizeOf(d), 0);
|
|
161161
|
+
const freedBytes = decisions.filter((d) => d.action === "delete").reduce((s2, d) => s2 + sizeOf(d), 0);
|
|
160735
161162
|
const keptBytes = totalBytes - freedBytes;
|
|
161163
|
+
const cap = policy.sessionStoreCapBytes;
|
|
160736
161164
|
return {
|
|
160737
161165
|
totalBytes,
|
|
160738
161166
|
keptBytes,
|
|
160739
161167
|
freedBytes,
|
|
160740
|
-
|
|
161168
|
+
// 高水位报警:旧 highWaterBytes 或新总量上限任一被突破都报(新上限缺省即开)。
|
|
161169
|
+
overHighWater: policy.highWaterBytes !== void 0 && keptBytes > policy.highWaterBytes || cap !== void 0 && keptBytes > cap
|
|
160741
161170
|
};
|
|
160742
161171
|
}
|
|
160743
161172
|
var DEFAULT_GC_POLICY;
|
|
@@ -160748,7 +161177,9 @@ var init_workdir_gc = __esm({
|
|
|
160748
161177
|
orphanTtlMs: 24 * 36e5,
|
|
160749
161178
|
ttlMs: 7 * 24 * 36e5,
|
|
160750
161179
|
chatTtlMs: 14 * 24 * 36e5,
|
|
160751
|
-
abnormalGraceMs: 24 * 36e5
|
|
161180
|
+
abnormalGraceMs: 24 * 36e5,
|
|
161181
|
+
maxRoundsPerArtifact: 3,
|
|
161182
|
+
sessionStoreCapBytes: 2 * 1024 ** 3
|
|
160752
161183
|
};
|
|
160753
161184
|
}
|
|
160754
161185
|
});
|
|
@@ -160819,14 +161250,20 @@ function kindFromPath(workRoot, dir) {
|
|
|
160819
161250
|
if (parts[0] === "tmp" && parts[1]) return parts[1].replace(/-[^-]*$/, "");
|
|
160820
161251
|
return void 0;
|
|
160821
161252
|
}
|
|
160822
|
-
function observe(dir) {
|
|
161253
|
+
function observe(dir, workRoot, measureRuntimeState) {
|
|
160823
161254
|
const meta = readMeta(dir);
|
|
160824
161255
|
let mtimeMs = 0;
|
|
160825
161256
|
try {
|
|
160826
161257
|
mtimeMs = import_node_fs11.default.statSync(dir).mtimeMs;
|
|
160827
161258
|
} catch {
|
|
160828
161259
|
}
|
|
160829
|
-
|
|
161260
|
+
const runtimeKind = meta?.runtimeKind ?? kindFromPath(workRoot, dir);
|
|
161261
|
+
let runtimeStateBytes = 0;
|
|
161262
|
+
try {
|
|
161263
|
+
runtimeStateBytes = measureRuntimeState?.(dir, runtimeKind) ?? 0;
|
|
161264
|
+
} catch {
|
|
161265
|
+
}
|
|
161266
|
+
return { dir, meta, sizeBytes: dirSizeBytes(dir), runtimeStateBytes, mtimeMs, live: isWorkdirLive(dir) };
|
|
160830
161267
|
}
|
|
160831
161268
|
async function runGcSweep(deps) {
|
|
160832
161269
|
const policy = { ...DEFAULT_GC_POLICY, ...deps.policy };
|
|
@@ -160834,7 +161271,7 @@ async function runGcSweep(deps) {
|
|
|
160834
161271
|
const log3 = deps.log ?? (() => {
|
|
160835
161272
|
});
|
|
160836
161273
|
const dirs = listWorkdirs(deps.workRoot, deps.legacyRoot);
|
|
160837
|
-
const candidates = dirs.map((d) => observe(d));
|
|
161274
|
+
const candidates = dirs.map((d) => observe(d, deps.workRoot, deps.measureRuntimeState));
|
|
160838
161275
|
const needStates = candidates.filter(
|
|
160839
161276
|
(c) => c.meta?.artifactId && c.meta.kind !== "chat" && c.meta.kind !== "one-shot"
|
|
160840
161277
|
);
|
|
@@ -160852,7 +161289,7 @@ async function runGcSweep(deps) {
|
|
|
160852
161289
|
if (st) c.artifactState = st;
|
|
160853
161290
|
}
|
|
160854
161291
|
const decisions = planGc(candidates, policy, now);
|
|
160855
|
-
const acct = gcAccounting(decisions, policy);
|
|
161292
|
+
const acct = gcAccounting(decisions, policy, candidates);
|
|
160856
161293
|
let deleted = 0;
|
|
160857
161294
|
for (const d of decisions) {
|
|
160858
161295
|
if (d.action !== "delete") continue;
|
|
@@ -164661,7 +165098,13 @@ async function startOasisServer(opts) {
|
|
|
164661
165098
|
try {
|
|
164662
165099
|
let companyId;
|
|
164663
165100
|
if (companyCtx) {
|
|
164664
|
-
if (companyCtx.kind === "not-found")
|
|
165101
|
+
if (companyCtx.kind === "not-found") {
|
|
165102
|
+
throw companyCtx.selector ? new ApiError(404, "COMPANY_NOT_FOUND", `\u8BF7\u6C42\u6307\u5B9A\u7684\u516C\u53F8\u4E0D\u5B58\u5728\uFF1A${companyCtx.selector}`) : new ApiError(
|
|
165103
|
+
404,
|
|
165104
|
+
"COMPANY_UNRESOLVED",
|
|
165105
|
+
`\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u516C\u53F8\uFF1A${actor} \u672A\u6307\u5B9A\u516C\u53F8\uFF08x-oasis-company\uFF09\uFF0C\u81EA\u8EAB\u4E5F\u4E0D\u5C5E\u4E8E\u4EFB\u4F55\u516C\u53F8\u3002agent \u4F1A\u8BDD\u5C5E\u4E8E\u5176\u88AB\u6D3E\u53D1\u7684\u90A3\u5BB6\u516C\u53F8\uFF0C\u5E94\u7531\u6D3E\u53D1\u65B9\u5728\u4F1A\u8BDD token \u91CC\u7B7E companyId\u2014\u2014\u6536\u5230\u672C\u9519\u8BEF\u901A\u5E38\u662F\u7B7E\u53D1\u4FA7\u6F0F\u7B7E\uFF0C\u800C\u975E\u516C\u53F8\u8BB0\u5F55\u6709\u95EE\u9898\u3002`
|
|
165106
|
+
);
|
|
165107
|
+
}
|
|
164665
165108
|
if (companyCtx.kind === "forbidden") throw new ApiError(403, "NOT_A_MEMBER", "\u4E0D\u662F\u8BE5\u516C\u53F8\u6210\u5458\uFF0C\u8BBF\u95EE\u88AB\u62D2");
|
|
164666
165109
|
if (companyCtx.kind === "ok") companyId = companyCtx.companyId;
|
|
164667
165110
|
}
|
|
@@ -168146,6 +168589,7 @@ var init_service3 = __esm({
|
|
|
168146
168589
|
* 队首若 stale(base ≠ 当前 head,文档层乱序 approve 会撞上)→ 内核拒绝并指 rebase/group-commit。
|
|
168147
168590
|
*/
|
|
168148
168591
|
async mergeUpTo(artifactId, revisionId, actor) {
|
|
168592
|
+
let voted = false;
|
|
168149
168593
|
for (let guard = 0; guard < 1e3; guard++) {
|
|
168150
168594
|
const target = this.kernel.model.revisions.get(revisionId);
|
|
168151
168595
|
if (!target) throw new Error(`revision not found in kernel: ${revisionId}`);
|
|
@@ -168153,7 +168597,14 @@ var init_service3 = __esm({
|
|
|
168153
168597
|
if (target.state === "working") {
|
|
168154
168598
|
await this.kernel.merge({ artifactId, actor });
|
|
168155
168599
|
} else if (target.state === "proposed") {
|
|
168600
|
+
if (voted) {
|
|
168601
|
+
const gate = pendingConcludeAttempt(this.kernel.model, artifactId)?.gate;
|
|
168602
|
+
throw new Error(
|
|
168603
|
+
`\u5DF2\u8BB0\u5F55\u4F60\u7684\u901A\u8FC7\u7968\uFF0C\u4F46\u8FD9\u4E00\u7248\u8FD8\u6CA1\u6536\u9F50\u8BC4\u5BA1${gate ? `\uFF08\u9700 ${gate.quorum} \u7968\uFF0C\u8BC4\u5BA1\u4EBA [${gate.eligible.join("\u3001")}]\uFF09` : ""}\u2014\u2014\u7B49\u5176\u4F59\u8BC4\u5BA1\u4EBA\u8868\u6001\u540E\u81EA\u52A8\u843D\u5730\u3002`
|
|
168604
|
+
);
|
|
168605
|
+
}
|
|
168156
168606
|
await this.kernel.submitReview({ artifactId, actor, verdict: "approve", revisionId });
|
|
168607
|
+
voted = true;
|
|
168157
168608
|
} else {
|
|
168158
168609
|
throw new Error(`revision not approvable (state=${target.state}): ${revisionId}`);
|
|
168159
168610
|
}
|
|
@@ -180387,7 +180838,7 @@ function collabDomain(opts) {
|
|
|
180387
180838
|
const resolveProject = await buildProjectResolver(artifacts);
|
|
180388
180839
|
const dispatchPausedOf = await buildDispatchPausedResolver(kernel.model, artifacts);
|
|
180389
180840
|
const wsArts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === req.params.id);
|
|
180390
|
-
const coordinatorEvents = engineStore ? await engineStore.transaction((tx) => tx.listEvents(req.params.id)) : void 0;
|
|
180841
|
+
const coordinatorEvents = engineStore ? await engineStore.transaction((tx) => tx.listEvents(req.params.id, 0, void 0, COORDINATOR_ACTIVITY_EVENT_KINDS)) : void 0;
|
|
180391
180842
|
const signalsForThisCompany = opts.dispatcherSignalsOf ? (id) => opts.dispatcherSignalsOf(req.auth.companyId, id) : void 0;
|
|
180392
180843
|
const roleDefsByName = opts.roles ? new Map((await opts.roles.list()).map((r) => [r.name, { label: r.label, defaultOwner: r.defaultOwner }])) : void 0;
|
|
180393
180844
|
const roleDefaultOwnerOf = roleDefsByName ? (role) => {
|
|
@@ -180739,6 +181190,7 @@ function collabDomain(opts) {
|
|
|
180739
181190
|
});
|
|
180740
181191
|
};
|
|
180741
181192
|
}
|
|
181193
|
+
var COORDINATOR_ACTIVITY_EVENT_KINDS;
|
|
180742
181194
|
var init_collab = __esm({
|
|
180743
181195
|
"../server/src/domains/collab/index.ts"() {
|
|
180744
181196
|
"use strict";
|
|
@@ -180764,6 +181216,7 @@ var init_collab = __esm({
|
|
|
180764
181216
|
init_organization_usage();
|
|
180765
181217
|
init_planner();
|
|
180766
181218
|
init_closure_report();
|
|
181219
|
+
COORDINATOR_ACTIVITY_EVENT_KINDS = ["plan.changed", "issue.create", "issue.resolve"];
|
|
180767
181220
|
}
|
|
180768
181221
|
});
|
|
180769
181222
|
|
|
@@ -182041,6 +182494,7 @@ var init_service7 = __esm({
|
|
|
182041
182494
|
onCheckpointState;
|
|
182042
182495
|
readBundleFile;
|
|
182043
182496
|
onAgentHandoffPersisted;
|
|
182497
|
+
resolveNodeConclusions;
|
|
182044
182498
|
constructor(opts) {
|
|
182045
182499
|
this.store = opts.store;
|
|
182046
182500
|
this.mode = opts.mode;
|
|
@@ -182051,6 +182505,7 @@ var init_service7 = __esm({
|
|
|
182051
182505
|
this.onCheckpointState = opts.onCheckpointState;
|
|
182052
182506
|
this.readBundleFile = opts.readBundleFile;
|
|
182053
182507
|
this.onAgentHandoffPersisted = opts.onAgentHandoffPersisted;
|
|
182508
|
+
this.resolveNodeConclusions = opts.resolveNodeConclusions;
|
|
182054
182509
|
}
|
|
182055
182510
|
assertWritable() {
|
|
182056
182511
|
if (this.mode === "off") throw new Error("execution continuity is disabled");
|
|
@@ -182246,6 +182701,12 @@ var init_service7 = __esm({
|
|
|
182246
182701
|
async coordinatorView(workOrderId, companyId) {
|
|
182247
182702
|
const graph = await this.resolveGraph(workOrderId, companyId);
|
|
182248
182703
|
if (graph.length === 0) return null;
|
|
182704
|
+
let conclusions = {};
|
|
182705
|
+
try {
|
|
182706
|
+
conclusions = await this.resolveNodeConclusions?.(workOrderId, companyId) ?? {};
|
|
182707
|
+
} catch (error2) {
|
|
182708
|
+
console.warn(`[execution-continuity] conclusion \u8BFB\u53D6\u5931\u8D25\uFF08\u534F\u8C03\u8005\u89C6\u56FE\u7167\u5E38\u8FD4\u56DE\uFF09: ${String(error2)}`);
|
|
182709
|
+
}
|
|
182249
182710
|
const attempts = await this.store.listAttempts(workOrderId);
|
|
182250
182711
|
const latest = latestAttemptByNode(attempts);
|
|
182251
182712
|
const handoffs = /* @__PURE__ */ new Map();
|
|
@@ -182309,7 +182770,10 @@ var init_service7 = __esm({
|
|
|
182309
182770
|
recoverability: checkpoint.recoverability,
|
|
182310
182771
|
lastCheckpointAt: checkpoint.createdAt
|
|
182311
182772
|
} } : {},
|
|
182312
|
-
blockedBy: nodeState === "waiting" ? node.dependsOn.filter((id) => !DONE_STATES.has(stateByNode.get(id) ?? "waiting")) : []
|
|
182773
|
+
blockedBy: nodeState === "waiting" ? node.dependsOn.filter((id) => !DONE_STATES.has(stateByNode.get(id) ?? "waiting")) : [],
|
|
182774
|
+
// 纯透传(展示层兜底料源,见 CoordinatorNodeView.conclusion)。不参与本方法里任何
|
|
182775
|
+
// nodeState / todo / dependencyBlocks / collaborationStatus 的派生。
|
|
182776
|
+
...conclusions[node.nodeId]?.trim() ? { conclusion: conclusions[node.nodeId].trim() } : {}
|
|
182313
182777
|
};
|
|
182314
182778
|
});
|
|
182315
182779
|
const reverse = reverseEdges(graph);
|
|
@@ -182457,6 +182921,23 @@ var init_upstream_handoff_input = __esm({
|
|
|
182457
182921
|
}
|
|
182458
182922
|
});
|
|
182459
182923
|
|
|
182924
|
+
// ../server/src/domains/execution-continuity/node-conclusions.ts
|
|
182925
|
+
function nodeConclusionsFromSnapshot(snapshot) {
|
|
182926
|
+
const workById = new Map(snapshot.works.map((work) => [work.id, work]));
|
|
182927
|
+
const out = {};
|
|
182928
|
+
for (const node of snapshot.nodes) {
|
|
182929
|
+
const accepted = node.latestAcceptId ? workById.get(node.latestAcceptId) : void 0;
|
|
182930
|
+
const conclusion = accepted?.conclusion?.trim() || snapshot.works.filter((work) => work.nodeId === node.id && work.status === "success" && work.conclusion?.trim()).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt)).at(-1)?.conclusion?.trim();
|
|
182931
|
+
if (conclusion) out[node.id] = conclusion;
|
|
182932
|
+
}
|
|
182933
|
+
return out;
|
|
182934
|
+
}
|
|
182935
|
+
var init_node_conclusions = __esm({
|
|
182936
|
+
"../server/src/domains/execution-continuity/node-conclusions.ts"() {
|
|
182937
|
+
"use strict";
|
|
182938
|
+
}
|
|
182939
|
+
});
|
|
182940
|
+
|
|
182460
182941
|
// ../server/src/domains/execution-continuity/index.ts
|
|
182461
182942
|
function createExecutionContinuityDomain(opts) {
|
|
182462
182943
|
const service = new ExecutionContinuityService(opts);
|
|
@@ -182471,6 +182952,7 @@ var init_execution_continuity2 = __esm({
|
|
|
182471
182952
|
init_routes8();
|
|
182472
182953
|
init_handoff_work_bridge();
|
|
182473
182954
|
init_upstream_handoff_input();
|
|
182955
|
+
init_node_conclusions();
|
|
182474
182956
|
}
|
|
182475
182957
|
});
|
|
182476
182958
|
|
|
@@ -186141,15 +186623,16 @@ var init_builtin_skills = __esm({
|
|
|
186141
186623
|
|
|
186142
186624
|
// ../server/src/design/board-watch.ts
|
|
186143
186625
|
function decideBoardLetter(c, nowMs, quietMs) {
|
|
186626
|
+
if (c.hasOpenWatchLetter) return false;
|
|
186627
|
+
if (c.freshTerminalRunId !== null) return true;
|
|
186144
186628
|
if (c.boardMtime === null) return false;
|
|
186145
186629
|
if (!c.hasRevision) return false;
|
|
186146
186630
|
if (c.lastOpAtMs === null || c.boardMtime <= c.lastOpAtMs) return false;
|
|
186147
186631
|
if (nowMs - c.boardMtime < quietMs) return false;
|
|
186148
|
-
if (c.hasOpenWatchLetter) return false;
|
|
186149
186632
|
if (c.lastWatchMtime !== null && c.boardMtime <= c.lastWatchMtime) return false;
|
|
186150
186633
|
return true;
|
|
186151
186634
|
}
|
|
186152
|
-
var MARKER_PREFIX, boardWatchMarker, parseMarker, BoardWatcher;
|
|
186635
|
+
var MARKER_PREFIX, boardWatchMarker, parseMarker, RUN_MARKER_PREFIX, boardWatchRunMarker, parseRunMarker, TERMINAL_RUN_STATUS, BoardWatcher;
|
|
186153
186636
|
var init_board_watch = __esm({
|
|
186154
186637
|
"../server/src/design/board-watch.ts"() {
|
|
186155
186638
|
"use strict";
|
|
@@ -186162,6 +186645,17 @@ var init_board_watch = __esm({
|
|
|
186162
186645
|
const n = Number.parseInt(body.slice(i + MARKER_PREFIX.length), 10);
|
|
186163
186646
|
return Number.isFinite(n) ? n : null;
|
|
186164
186647
|
};
|
|
186648
|
+
RUN_MARKER_PREFIX = "[od-board-watch run:";
|
|
186649
|
+
boardWatchRunMarker = (runId) => `${RUN_MARKER_PREFIX}${runId}]`;
|
|
186650
|
+
parseRunMarker = (body) => {
|
|
186651
|
+
const i = body.indexOf(RUN_MARKER_PREFIX);
|
|
186652
|
+
if (i < 0) return null;
|
|
186653
|
+
const j = body.indexOf("]", i + RUN_MARKER_PREFIX.length);
|
|
186654
|
+
if (j < 0) return null;
|
|
186655
|
+
const id = body.slice(i + RUN_MARKER_PREFIX.length, j);
|
|
186656
|
+
return id.length > 0 ? id : null;
|
|
186657
|
+
};
|
|
186658
|
+
TERMINAL_RUN_STATUS = /* @__PURE__ */ new Set(["succeeded", "failed", "canceled", "cancelled"]);
|
|
186165
186659
|
BoardWatcher = class {
|
|
186166
186660
|
constructor(deps) {
|
|
186167
186661
|
this.deps = deps;
|
|
@@ -186178,6 +186672,22 @@ var init_board_watch = __esm({
|
|
|
186178
186672
|
return null;
|
|
186179
186673
|
}
|
|
186180
186674
|
}
|
|
186675
|
+
/**
|
|
186676
|
+
* 查一块板的 run 列表(`GET /api/runs?projectId=`,与 CLI `design status` 同源)。
|
|
186677
|
+
* run 注册表是 od 的**内存态**——重启即空,查不到不代表没跑过;所以这里失败一律降级成空数组,
|
|
186678
|
+
* 由板文件 mtime 那条慢路径兜底,绝不因为查不到 run 就压掉写信。
|
|
186679
|
+
*/
|
|
186680
|
+
async boardRuns(boardKey) {
|
|
186681
|
+
const fetchImpl = this.deps.fetchImpl ?? fetch;
|
|
186682
|
+
try {
|
|
186683
|
+
const res = await fetchImpl(`${this.deps.odBase}/api/runs?projectId=${encodeURIComponent(boardKey)}`);
|
|
186684
|
+
if (!res.ok) return [];
|
|
186685
|
+
const body = await res.json();
|
|
186686
|
+
return (body.runs ?? []).map((r) => ({ runId: String(r["runId"] ?? r["id"] ?? ""), status: String(r["status"] ?? "unknown") })).filter((r) => r.runId.length > 0);
|
|
186687
|
+
} catch {
|
|
186688
|
+
return [];
|
|
186689
|
+
}
|
|
186690
|
+
}
|
|
186181
186691
|
async tick(now = Date.now()) {
|
|
186182
186692
|
const { kernel, boardKeyOf, log: log3 } = this.deps;
|
|
186183
186693
|
const quietMs = this.deps.quietMs ?? 10 * 6e4;
|
|
@@ -186194,9 +186704,17 @@ var init_board_watch = __esm({
|
|
|
186194
186704
|
}
|
|
186195
186705
|
const boardKey = boardKeyOf(a.id);
|
|
186196
186706
|
const files = await this.boardFiles(boardKey);
|
|
186197
|
-
if (
|
|
186707
|
+
if (files === null) continue;
|
|
186708
|
+
const runs = await this.boardRuns(boardKey);
|
|
186709
|
+
if (files.length === 0 && runs.length === 0) continue;
|
|
186198
186710
|
const boardMtime = files.reduce((mx, f2) => f2.mtime !== null && (mx === null || f2.mtime > mx) ? f2.mtime : mx, null);
|
|
186199
|
-
const watchLetters = annotationsOf(model, a.id).filter(
|
|
186711
|
+
const watchLetters = annotationsOf(model, a.id).filter(
|
|
186712
|
+
(x2) => x2.author === actor && (parseMarker(x2.body) !== null || parseRunMarker(x2.body) !== null)
|
|
186713
|
+
);
|
|
186714
|
+
const letteredRuns = new Set(
|
|
186715
|
+
watchLetters.map((x2) => parseRunMarker(x2.body)).filter((s2) => s2 !== null)
|
|
186716
|
+
);
|
|
186717
|
+
const newestTerminalRun = runs.find((r) => TERMINAL_RUN_STATUS.has(r.status)) ?? null;
|
|
186200
186718
|
const lastOpAt = model.lastOpAt.get(a.id);
|
|
186201
186719
|
const candidate = {
|
|
186202
186720
|
boardMtime,
|
|
@@ -186206,21 +186724,41 @@ var init_board_watch = __esm({
|
|
|
186206
186724
|
lastWatchMtime: watchLetters.reduce((mx, x2) => {
|
|
186207
186725
|
const m2 = parseMarker(x2.body);
|
|
186208
186726
|
return m2 !== null && (mx === null || m2 > mx) ? m2 : mx;
|
|
186209
|
-
}, null)
|
|
186727
|
+
}, null),
|
|
186728
|
+
freshTerminalRunId: newestTerminalRun !== null && !letteredRuns.has(newestTerminalRun.runId) ? newestTerminalRun.runId : null
|
|
186210
186729
|
};
|
|
186211
186730
|
if (!decideBoardLetter(candidate, now, quietMs)) continue;
|
|
186212
|
-
const
|
|
186213
|
-
|
|
186214
|
-
|
|
186215
|
-
|
|
186216
|
-
|
|
186217
|
-
|
|
186218
|
-
|
|
186219
|
-
|
|
186220
|
-
|
|
186731
|
+
const fmt = (fs34) => fs34.slice(0, 5).map((f2) => `- ${f2.path}\uFF08${f2.kind ?? "?"}\uFF09\xB7 ${f2.mtime ? new Date(f2.mtime).toISOString() : "?"}`).join("\n");
|
|
186732
|
+
let body;
|
|
186733
|
+
let logLine;
|
|
186734
|
+
if (candidate.freshTerminalRunId !== null) {
|
|
186735
|
+
const status = newestTerminalRun.status;
|
|
186736
|
+
const ok = status === "succeeded";
|
|
186737
|
+
const listed = files.length > 0 ? fmt([...files].sort((x2, y) => (y.mtime ?? 0) - (x2.mtime ?? 0))) : "\uFF08\u677F\u4E0A\u6682\u65E0\u4EA4\u4ED8\u7269\uFF09";
|
|
186738
|
+
body = [
|
|
186739
|
+
// 两个 marker 都带:run marker 管快路径去重,mtime marker 顺手把慢路径也盖住——
|
|
186740
|
+
// 否则这封信被 resolve 后,慢路径会就**同一批板内容**再写一封(两路去重各管各的)。
|
|
186741
|
+
boardMtime !== null ? `${boardWatchRunMarker(candidate.freshTerminalRunId)} ${boardWatchMarker(boardMtime)}` : boardWatchRunMarker(candidate.freshTerminalRunId),
|
|
186742
|
+
ok ? `\u753B\u677F ${boardKey} \u7684\u5F15\u64CE\u8DD1\u5B8C\u4E86\uFF08run ${candidate.freshTerminalRunId.slice(0, 8)} \u7EC8\u6001=${status}\uFF09\uFF0C\u8D27\u5728\u677F\u4E0A\u7B49\u4F60\u6536\uFF1A` : `\u753B\u677F ${boardKey} \u7684\u5F15\u64CE\u8FD9\u4E00\u8F6E**\u6CA1\u8DD1\u6210**\uFF08run ${candidate.freshTerminalRunId.slice(0, 8)} \u7EC8\u6001=${status}\uFF09\u3002\u677F\u4E0A\u5F53\u524D\u5185\u5BB9\uFF1A`,
|
|
186743
|
+
listed,
|
|
186744
|
+
``,
|
|
186745
|
+
ok ? `\u53D6\u8D27\u6838\u5BF9\uFF1A\`oasis design status ${a.id} --pull\` \u2192 \u5BF9\u7740 DESIGN_BRIEF.md \u4E0E inputs/ \u9010\u5B57\u6BB5\u6838\uFF08\u6570\u636E\u5BF9\u4E0D\u5BF9\u3001\u72B6\u6001\u5168\u4E0D\u5168\u3001\u6709\u6CA1\u6709\u7F16\u9020\uFF09\u3002\u591F\u597D\u5C31 \`oasis propose ${a.id} --from-dir <\u76EE\u5F55> --reason "<\u8FD9\u7248\u505A\u4E86\u4EC0\u4E48>"\`\uFF1B\u8981\u6539\u5C31\u56DE\u677F\u4E0A\u518D\u6765\u4E00\u8F6E \`oasis design chat ${a.id} --turn continue --message "<\u8981\u6539\u4EC0\u4E48>" --async\`\uFF08\u26D4 \u522B\u628A --pull \u4E0B\u6765\u7684\u6587\u4EF6\u5728\u672C\u5730\u624B\u6539\u540E propose\uFF09\u3002` : `\u5148 \`oasis design status ${a.id}\` \u770B\u677F\u4E0A\u6709\u6CA1\u6709\u53EF\u7528\u7684\u4E0A\u4E00\u7248\uFF1A\u6709\u5C31\u7167\u5E38\u53D6\u8D27\u6838\u5BF9\uFF1B\u6CA1\u6709\u5C31\u628A\u9700\u6C42\u4E0E\u771F\u5B9E\u6570\u636E\u9489\u5F97\u66F4\u6B7B\u540E\u91CD\u8D77\u4E00\u8F6E \`oasis design chat ${a.id} --message "<\u2026>" --async\`\uFF0C\u4ECD\u4E0D\u884C\u7528 \`oasis gap ${a.id} --desc "<\u5361\u5728\u54EA>"\` \u5192\u6CE1\u7ED9\u4EBA\u3002`
|
|
186746
|
+
].join("\n");
|
|
186747
|
+
logLine = `[board-watch] ${a.id} \u2190 \u677F ${boardKey} \u5F15\u64CE\u6536\u5C3E\uFF08run ${candidate.freshTerminalRunId.slice(0, 8)} ${status}\uFF09\u2192 \u5DF2\u5199\u4FE1\u5524 owner`;
|
|
186748
|
+
} else {
|
|
186749
|
+
const newest = files.filter((f2) => f2.mtime !== null && candidate.lastOpAtMs !== null && f2.mtime > candidate.lastOpAtMs).sort((x2, y) => (y.mtime ?? 0) - (x2.mtime ?? 0));
|
|
186750
|
+
body = [
|
|
186751
|
+
boardWatchMarker(boardMtime),
|
|
186752
|
+
`\u753B\u677F ${boardKey} \u4E0A\u6709 Oasis \u4E4B\u5916\u7684\u65B0\u52A8\u9759\uFF08\u4EBA\u5728\u677F\u4E0A\u5BF9\u8BDD/\u6539\u7A3F\uFF0C\u6216\u5F15\u64CE\u4EA7\u4E86\u6CA1\u4EBA\u6536\u7684\u65B0\u7248\uFF09\uFF0C\u6BD4\u672C\u4EA7\u7269\u6700\u8FD1\u4E00\u6B21\u64CD\u4F5C\uFF08${lastOpAt ?? "?"}\uFF09\u65B0\u3001\u4E14\u5DF2\u9759\u9ED8 ${Math.round(quietMs / 6e4)} \u5206\u949F\uFF1A`,
|
|
186753
|
+
fmt(newest.length > 0 ? newest : files),
|
|
186754
|
+
``,
|
|
186755
|
+
`\u8BF7\u53D6\u8D27\u6838\u5BF9\uFF1A\`oasis design status ${a.id} --pull\` \u2192 \u5185\u5BB9\u5408\u610F\u5C31 \`oasis propose ${a.id} --from-dir <\u76EE\u5F55> --reason "<\u6536\u5F55\u677F\u4E0A\u4EBA\u5DE5\u8FED\u4EE3>"\` \u6536\u5F55\u4E3A\u65B0\u7248\u672C\uFF1B\u4E0D\u5408\u610F/\u4E0D\u5FC5\u6536\u5F55\uFF0C\u56DE\u5E16\u8BF4\u660E\u540E\u6309 wontfix \u5904\u7406\u5373\u53EF\uFF08\u540C\u4E00\u7248\u4E0D\u4F1A\u518D\u6765\u4FE1\uFF09\u3002`
|
|
186756
|
+
].join("\n");
|
|
186757
|
+
logLine = `[board-watch] ${a.id} \u2190 \u677F ${boardKey} \u6709\u65B0\u5185\u5BB9\uFF08mtime=${new Date(boardMtime).toISOString()}\uFF09\u2192 \u5DF2\u5199\u4FE1\u5524 owner`;
|
|
186758
|
+
}
|
|
186221
186759
|
await kernel.annotate({ artifactId: a.id, actor, body, kind: "comment" });
|
|
186222
186760
|
written++;
|
|
186223
|
-
log3?.(
|
|
186761
|
+
log3?.(logLine);
|
|
186224
186762
|
}
|
|
186225
186763
|
}
|
|
186226
186764
|
};
|
|
@@ -192761,12 +193299,12 @@ function makeResumeRetryProxy(first, spawnFallback) {
|
|
|
192761
193299
|
s2.onTelemetry?.((e) => fanTel(e));
|
|
192762
193300
|
};
|
|
192763
193301
|
wire(first);
|
|
192764
|
-
const
|
|
193302
|
+
const isSessionNotFound2 = (err) => {
|
|
192765
193303
|
const chatExit = err instanceof Error ? err.chatExit : void 0;
|
|
192766
193304
|
return chatExit?.reason === "session-not-found";
|
|
192767
193305
|
};
|
|
192768
193306
|
const done = first.done.catch(async (err) => {
|
|
192769
|
-
if (sawOutput || !
|
|
193307
|
+
if (sawOutput || !isSessionNotFound2(err)) throw err;
|
|
192770
193308
|
const second = await spawnFallback();
|
|
192771
193309
|
active = second;
|
|
192772
193310
|
wire(second);
|
|
@@ -193440,6 +193978,16 @@ async function startServe(opts) {
|
|
|
193440
193978
|
...liveSchemaMap.get(artifact.type)?.ownerRole ? { assignedRole: liveSchemaMap.get(artifact.type).ownerRole } : {},
|
|
193441
193979
|
dependsOn: artifact.inputs.map((edge) => edge.to)
|
|
193442
193980
|
}));
|
|
193981
|
+
},
|
|
193982
|
+
// 业务交接的展示料源(见 CoordinatorNodeView.conclusion):每个节点最近一版**已收口** Work 的
|
|
193983
|
+
// conclusion。纯读、纯可选——读不到(无引擎 store / 工单不在库 / 任何异常)就返回空,
|
|
193984
|
+
// 协调者视图照常工作。
|
|
193985
|
+
resolveNodeConclusions: async (workOrderId, companyId) => {
|
|
193986
|
+
const companyEngine = await getEngine(companyId ?? defaultCompanyId);
|
|
193987
|
+
const store = companyEngine.kernel.getStore?.();
|
|
193988
|
+
if (!store) return {};
|
|
193989
|
+
const snap = await store.transaction((tx) => tx.loadWorkorder(workOrderId));
|
|
193990
|
+
return snap ? nodeConclusionsFromSnapshot(snap) : {};
|
|
193443
193991
|
}
|
|
193444
193992
|
});
|
|
193445
193993
|
console.log(`[serve] execution-continuity mode=${continuityMode}\uFF08record=\u5BF9\u7167\u7EC4\uFF0Cresume=\u6062\u590D\u5305\u5B9E\u9A8C\u7EC4\uFF09`);
|
|
@@ -193585,7 +194133,7 @@ async function startServe(opts) {
|
|
|
193585
194133
|
const selector = typeof raw === "string" && raw ? raw : void 0;
|
|
193586
194134
|
const me = await companies.currentUser({ actor }, headers);
|
|
193587
194135
|
const ctx = await companies.service.resolveCurrentCompany(me.accountId, selector);
|
|
193588
|
-
if (!ctx) return { kind: "not-found" };
|
|
194136
|
+
if (!ctx) return { kind: "not-found", ...selector !== void 0 ? { selector } : {} };
|
|
193589
194137
|
return ctx.isMember ? { kind: "ok", companyId: ctx.company.id } : { kind: "forbidden" };
|
|
193590
194138
|
};
|
|
193591
194139
|
const resolveActor = (token) => issuer.resolve(token) ?? (opts.allowSimpleTokens && token.startsWith("token:") ? token.slice("token:".length) : null);
|
|
@@ -193913,7 +194461,8 @@ async function startServe(opts) {
|
|
|
193913
194461
|
const artifactId = `artifact:planner:${(0, import_node_crypto41.randomUUID)()}`;
|
|
193914
194462
|
const handle = await chatRemoteAdapter.spawn({
|
|
193915
194463
|
actor: planner.id,
|
|
193916
|
-
|
|
194464
|
+
// companyId 同协调者:planner 用的就是默认公司的 actors 服务,漏签会让它一调公司域端点就 404。
|
|
194465
|
+
actorToken: issueSessionToken(planner.id, { artifactId, action: "plan-workorder", limits, binding, companyId: defaultCompanyId }),
|
|
193917
194466
|
artifactId,
|
|
193918
194467
|
bundle: { files },
|
|
193919
194468
|
...actorCtx?.config?.prompt ? { systemPrompt: actorCtx.config.prompt } : {},
|
|
@@ -194926,7 +195475,7 @@ async function startServe(opts) {
|
|
|
194926
195475
|
if (engStore) void rebuildDispatchMappings(engStore);
|
|
194927
195476
|
}
|
|
194928
195477
|
newKernel.setEngineIO({
|
|
194929
|
-
async dispatchWork(workId) {
|
|
195478
|
+
async dispatchWork(workId, opts2) {
|
|
194930
195479
|
if (!newEngineProduce) return;
|
|
194931
195480
|
if (dispatchedWorks.has(workId)) return;
|
|
194932
195481
|
const kernel2 = newKernel;
|
|
@@ -194955,7 +195504,7 @@ async function startServe(opts) {
|
|
|
194955
195504
|
}
|
|
194956
195505
|
let result;
|
|
194957
195506
|
try {
|
|
194958
|
-
result = replyIssueId && newEngineReplyWork ? await newEngineReplyWork(rev.artifactId, rev.author, replyIssueId, ledgerId) : await newEngineProduce(rev.artifactId, rev.author, workId, ledgerId);
|
|
195507
|
+
result = replyIssueId && newEngineReplyWork ? await newEngineReplyWork(rev.artifactId, rev.author, replyIssueId, ledgerId) : await newEngineProduce(rev.artifactId, rev.author, workId, ledgerId, opts2?.resumeEligible);
|
|
194959
195508
|
} catch (err) {
|
|
194960
195509
|
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch error: ${err instanceof Error ? err.message : String(err)}` }).catch(ledgerWarn("close(error)"));
|
|
194961
195510
|
console.log(`[new-engine] dispatchWork ${workId} \u6D3E\u53D1\u5F02\u5E38\uFF1A${String(err)} \u2192 \u7F6E work failed`);
|
|
@@ -195375,14 +195924,16 @@ async function startServe(opts) {
|
|
|
195375
195924
|
});
|
|
195376
195925
|
const slot = { dispatcher: d, logged: 0, ticking: false };
|
|
195377
195926
|
dispatchers.set(companyId, slot);
|
|
195378
|
-
newEngineProduce = async (artifactId, actorId, workId, dispatchId) => {
|
|
195927
|
+
newEngineProduce = async (artifactId, actorId, workId, dispatchId, resumeEligible) => {
|
|
195379
195928
|
const result = await d.requestProduce({
|
|
195380
195929
|
artifactId,
|
|
195381
195930
|
actor: actorId,
|
|
195382
195931
|
bypassScheduling: true,
|
|
195383
195932
|
engineWorkId: workId,
|
|
195384
195933
|
engineCompanyId: companyId,
|
|
195385
|
-
...dispatchId !== void 0 ? { dispatchId } : {}
|
|
195934
|
+
...dispatchId !== void 0 ? { dispatchId } : {},
|
|
195935
|
+
// 工单 e4a43a42:返工(已收口节点被重激活)→ resumeEligible=false → 派发层起全新会话。
|
|
195936
|
+
...resumeEligible !== void 0 ? { resumeEligible } : {}
|
|
195386
195937
|
});
|
|
195387
195938
|
return { kind: result.kind, dispatchId: result.kind === "dispatched" ? result.dispatchId : void 0 };
|
|
195388
195939
|
};
|
|
@@ -195876,6 +196427,12 @@ ${st.detail}
|
|
|
195876
196427
|
artifactId: ctx?.artifactId ?? "artifact:coordinator",
|
|
195877
196428
|
action: ctx?.action ?? "coordinate",
|
|
195878
196429
|
limits: ctx?.limits ?? { wallClockMs: wallClockForKind("coordinate") },
|
|
196430
|
+
// CO-101 门禁要这个 claim 才认得出「这次请求属于哪家公司」。漏签的后果不是报错这么简单:
|
|
196431
|
+
// 管理者的 roles / actors / workorders / inbox / session-log 会**全部** 404,而 status / ls /
|
|
196432
|
+
// graph / content 照常(热路径回退默认公司),于是它只瞎一半——够它误诊成「平台名册服务挂了」,
|
|
196433
|
+
// 再上报给人;未关的上报又会关掉自动重唤,把本可自愈的节点永久悬挂。2026-08-14 至少 3 张单
|
|
196434
|
+
// 栽在这上面。协调者与 apply 同用默认公司 kernel(见下方 structuralBaselines 注释),故取默认公司。
|
|
196435
|
+
companyId: defaultCompanyId,
|
|
195879
196436
|
...ctx?.binding !== void 0 ? { binding: ctx.binding } : {}
|
|
195880
196437
|
});
|
|
195881
196438
|
const ws = ctx?.artifactId ? kernel.model.artifacts.get(ctx.artifactId)?.workspace : void 0;
|
|
@@ -196846,6 +197403,33 @@ function reapClaudeProjects(workdir, runtimeKind) {
|
|
|
196846
197403
|
} catch {
|
|
196847
197404
|
}
|
|
196848
197405
|
}
|
|
197406
|
+
function measureClaudeProjects(workdir, runtimeKind) {
|
|
197407
|
+
if (runtimeKind !== "claude" && runtimeKind !== "claude-code") return 0;
|
|
197408
|
+
const dir = import_node_path24.default.join(claudeProjectsRoot(), claudeProjectSlug(workdir));
|
|
197409
|
+
return dirSizeBytes2(dir);
|
|
197410
|
+
}
|
|
197411
|
+
function dirSizeBytes2(dir) {
|
|
197412
|
+
let total = 0;
|
|
197413
|
+
let entries;
|
|
197414
|
+
try {
|
|
197415
|
+
entries = import_node_fs20.default.readdirSync(dir, { withFileTypes: true });
|
|
197416
|
+
} catch {
|
|
197417
|
+
return 0;
|
|
197418
|
+
}
|
|
197419
|
+
for (const e of entries) {
|
|
197420
|
+
const p2 = import_node_path24.default.join(dir, e.name);
|
|
197421
|
+
if (e.isSymbolicLink()) continue;
|
|
197422
|
+
if (e.isDirectory()) {
|
|
197423
|
+
total += dirSizeBytes2(p2);
|
|
197424
|
+
continue;
|
|
197425
|
+
}
|
|
197426
|
+
try {
|
|
197427
|
+
total += import_node_fs20.default.statSync(p2).size;
|
|
197428
|
+
} catch {
|
|
197429
|
+
}
|
|
197430
|
+
}
|
|
197431
|
+
return total;
|
|
197432
|
+
}
|
|
196849
197433
|
|
|
196850
197434
|
// ../cli/src/node.ts
|
|
196851
197435
|
init_src6();
|
|
@@ -196928,6 +197512,8 @@ async function startNode(opts) {
|
|
|
196928
197512
|
...process.env["OASIS_GC_INTERVAL_MS"] ? { intervalMs: Number(process.env["OASIS_GC_INTERVAL_MS"]) } : {},
|
|
196929
197513
|
queryArtifactStates: (ids2) => client.queryArtifactStates(ids2),
|
|
196930
197514
|
reapRuntimeState: reapClaudeProjects,
|
|
197515
|
+
// 工单 e4a43a42:把连带的 claude 会话记录占用一并量进保留预算(比工作目录还大的那一坨)。
|
|
197516
|
+
measureRuntimeState: measureClaudeProjects,
|
|
196931
197517
|
log: (m2) => console.log(`[oasis node] ${m2}`)
|
|
196932
197518
|
});
|
|
196933
197519
|
if (opts.gcEnabled !== false) console.log(`[oasis node] \u5DE5\u4F5C\u533A\u56DE\u6536\u5668\uFF1A\u5DF2\u542F\u7528\uFF08\u6839 ${workRoot}\uFF09`);
|
|
@@ -201013,7 +201599,8 @@ ${res.warning}`);
|
|
|
201013
201599
|
r.id === v2.artifact.currentRev ? "\u2190 current head" : "",
|
|
201014
201600
|
r.id === v2.concludedHead ? "\u2190 \u5DF2\u4EA4\u4ED8" : ""
|
|
201015
201601
|
].filter(Boolean).join(" ");
|
|
201016
|
-
|
|
201602
|
+
const stateLabel = r.state === "superseded" && r.contentKind === "empty" ? "\u672A\u4EA7\u51FA" : r.state;
|
|
201603
|
+
println(` ${r.id} ${stateLabel} by ${r.author} "${r.reason}"${marks ? ` ${marks}` : ""}`);
|
|
201017
201604
|
}
|
|
201018
201605
|
const NOTE_ABSTRACT = 150;
|
|
201019
201606
|
const voteRows = [];
|
|
@@ -201302,7 +201889,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
|
|
|
201302
201889
|
}
|
|
201303
201890
|
|
|
201304
201891
|
// src/index.ts
|
|
201305
|
-
var PKG_VERSION = true ? "0.1.
|
|
201892
|
+
var PKG_VERSION = true ? "0.1.104" : "dev";
|
|
201306
201893
|
var OASIS_DIR = path26.join(os10.homedir(), ".oasis");
|
|
201307
201894
|
var CONFIG_FILE = path26.join(OASIS_DIR, "node-config.json");
|
|
201308
201895
|
var PID_FILE = path26.join(OASIS_DIR, "node.pid");
|