oasis_test 0.1.97 → 0.1.99
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 +1456 -726
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -209,8 +209,13 @@ var init_content_type_handler = __esm({
|
|
|
209
209
|
const repoLines = repos.map((r) => ` - \`${r.id}\`\uFF1A${r.remote ?? "<remote>"}`);
|
|
210
210
|
const short = (s2) => s2.slice(0, 12);
|
|
211
211
|
const versionLine = (() => {
|
|
212
|
-
if (!
|
|
212
|
+
if (!ctx.hasHead) return [];
|
|
213
213
|
const ref2 = ctx.headExternalRef;
|
|
214
|
+
if (!ref2) {
|
|
215
|
+
return [
|
|
216
|
+
`> **\u4F60\u4E0A\u4E00\u7248\u6CA1\u6709\u53EF\u8FFD\u6EAF\u7684\u63D0\u4EA4\u53F7**\uFF08\u4E0A\u4E00\u7248\u662F\u7A7A\u8DD1 / \u5D29\u5728\u4EA4\u4ED8\u524D / \u53EA\u7559\u7ED3\u8BBA\u6CA1\u4EA7\u6B63\u6587\uFF0C\u8D26\u672C\u91CC\u8FD9\u7248\u662F**\u4EA4\u4ED8\u90A3\u4E00\u523B\u7684\u5FEB\u7167**\u3001\u6070\u597D\u4E3A\u7A7A\uFF09\u2014\u2014\u4F46**\u8D26\u672C\u4E3A\u7A7A\u4E0D\u4EE3\u8868 git \u91CC\u6CA1\u4E1C\u897F**\uFF08\u4E0A\u4E00\u8F6E\u82E5\u8DD1\u8FC7\uFF0C\u53EF\u80FD\u5DF2 push \u5206\u652F\uFF09\uFF1B**\u4EE5\u4F60 \`git fetch\` \u5230\u7684\u5B9E\u9645\u72B6\u6001\u4E3A\u51C6**\uFF08\u89C1\u4E0B\u65B9\u7B2C 1 \u6B65\uFF09\uFF0C\u522B\u636E\u6B64\u4ECE\u96F6\u91CD\u5199\u3002`
|
|
217
|
+
];
|
|
218
|
+
}
|
|
214
219
|
if (ref2.trim().startsWith("{")) {
|
|
215
220
|
try {
|
|
216
221
|
const m2 = JSON.parse(ref2);
|
|
@@ -808,11 +813,6 @@ function fold(model, op) {
|
|
|
808
813
|
});
|
|
809
814
|
break;
|
|
810
815
|
}
|
|
811
|
-
case "reopen": {
|
|
812
|
-
if (!model.seals.has(op.artifactId)) throw new CorruptLogError(op, "reopen on unsealed");
|
|
813
|
-
model.seals.delete(op.artifactId);
|
|
814
|
-
break;
|
|
815
|
-
}
|
|
816
816
|
case "hold": {
|
|
817
817
|
const p2 = op.payload;
|
|
818
818
|
if (!model.artifacts.has(op.artifactId)) throw new CorruptLogError(op, "hold on missing artifact");
|
|
@@ -1086,7 +1086,9 @@ function repliedAfter(a, who, sinceSeq) {
|
|
|
1086
1086
|
}
|
|
1087
1087
|
function hasUnansweredMention(model, a, who) {
|
|
1088
1088
|
const seq = latestMentionSeq(model, a, who);
|
|
1089
|
-
|
|
1089
|
+
if (seq < 0) return false;
|
|
1090
|
+
if (seq <= (a.resolvedAtSeq ?? Number.NEGATIVE_INFINITY)) return false;
|
|
1091
|
+
return !repliedAfter(a, who, seq);
|
|
1090
1092
|
}
|
|
1091
1093
|
function defaultRecipientOf(model, a, owner) {
|
|
1092
1094
|
if (a.mentions.length === 1) {
|
|
@@ -1377,6 +1379,39 @@ function unresolvedEscalationsOf(model, id) {
|
|
|
1377
1379
|
...e.gapId !== void 0 ? { gapId: e.gapId } : {}
|
|
1378
1380
|
}));
|
|
1379
1381
|
}
|
|
1382
|
+
function escalationsOf(model, id) {
|
|
1383
|
+
return (model.escalations.get(id) ?? []).map((e) => ({
|
|
1384
|
+
escalationId: e.escalationId,
|
|
1385
|
+
part: e.part,
|
|
1386
|
+
reason: e.reason,
|
|
1387
|
+
by: e.by,
|
|
1388
|
+
...e.hand !== void 0 ? { hand: e.hand } : {},
|
|
1389
|
+
at: e.at,
|
|
1390
|
+
resolved: e.resolved,
|
|
1391
|
+
...e.gapId !== void 0 ? { gapId: e.gapId } : {},
|
|
1392
|
+
...e.resolvedReason !== void 0 ? { resolvedReason: e.resolvedReason } : {}
|
|
1393
|
+
}));
|
|
1394
|
+
}
|
|
1395
|
+
function escalationById(model, escalationId) {
|
|
1396
|
+
for (const [artifactId, list] of model.escalations) {
|
|
1397
|
+
const e = list.find((x2) => x2.escalationId === escalationId);
|
|
1398
|
+
if (e) {
|
|
1399
|
+
return {
|
|
1400
|
+
artifactId,
|
|
1401
|
+
escalationId: e.escalationId,
|
|
1402
|
+
part: e.part,
|
|
1403
|
+
reason: e.reason,
|
|
1404
|
+
by: e.by,
|
|
1405
|
+
...e.hand !== void 0 ? { hand: e.hand } : {},
|
|
1406
|
+
at: e.at,
|
|
1407
|
+
resolved: e.resolved,
|
|
1408
|
+
...e.gapId !== void 0 ? { gapId: e.gapId } : {},
|
|
1409
|
+
...e.resolvedReason !== void 0 ? { resolvedReason: e.resolvedReason } : {}
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1380
1415
|
function listPendingReviews(model, workspace) {
|
|
1381
1416
|
const all = [...model.pendingReviews.values()];
|
|
1382
1417
|
return workspace ? all.filter((r) => r.workspace === workspace) : all;
|
|
@@ -1869,9 +1904,14 @@ var init_artifact = __esm({
|
|
|
1869
1904
|
});
|
|
1870
1905
|
|
|
1871
1906
|
// ../contract/src/revision.ts
|
|
1907
|
+
function isEmptyContent(contentKind) {
|
|
1908
|
+
return contentKind === EMPTY_CONTENT_KIND;
|
|
1909
|
+
}
|
|
1910
|
+
var EMPTY_CONTENT_KIND;
|
|
1872
1911
|
var init_revision = __esm({
|
|
1873
1912
|
"../contract/src/revision.ts"() {
|
|
1874
1913
|
"use strict";
|
|
1914
|
+
EMPTY_CONTENT_KIND = "empty";
|
|
1875
1915
|
}
|
|
1876
1916
|
});
|
|
1877
1917
|
|
|
@@ -3116,6 +3156,9 @@ function truncateReviewNote(note) {
|
|
|
3116
3156
|
\u2026\uFF08\u8BC4\u8BED\u8FC7\u957F\u5DF2\u622A\u65AD\uFF0C\u539F\u6587\u5171 ${note.length} \u5B57\uFF09`;
|
|
3117
3157
|
}
|
|
3118
3158
|
async function spreadRevisionContent(blobs, rev, out, prefix) {
|
|
3159
|
+
if (isEmptyContent(rev.contentKind)) {
|
|
3160
|
+
return { inlineText: null, listing: "\uFF08\u8FD9\u4E00\u8F6E\u6CA1\u6709\u4EA4\u4ED8\u5185\u5BB9\uFF09" };
|
|
3161
|
+
}
|
|
3119
3162
|
if (rev.contentKind === "external-pin") {
|
|
3120
3163
|
return { inlineText: null, listing: `external-pin \u2192 ${rev.contentRef}\uFF08\u672C\u4F53\u5728\u5916\u90E8\u7CFB\u7EDF\uFF0C\u6863\u6848\u67DC\u672A\u5B58\u6B63\u6587\uFF09` };
|
|
3121
3164
|
}
|
|
@@ -3263,8 +3306,9 @@ async function assembleContext(args) {
|
|
|
3263
3306
|
}
|
|
3264
3307
|
if (args.materializeSkills) Object.assign(files, await args.materializeSkills().catch(() => ({})));
|
|
3265
3308
|
const replyMemories = args.resolveActorMemoryIndex && replyMemoryEnabled ? await args.resolveActorMemoryIndex(memoryQueryTerms([a.body, artifact.title, artifact.description])).catch(() => []) : [];
|
|
3309
|
+
const ownerReply = args.actorId !== void 0 && args.actorId === artifact.owner;
|
|
3266
3310
|
files["TASK.md"] = [
|
|
3267
|
-
`# \u56DE\u590D\u8F6E\uFF1A\u4F60\u88AB @ \u8FDB\u4E00\u5C01\u4FE1\u91CC\u56DE\u8BDD`,
|
|
3311
|
+
ownerReply ? `# \u56DE\u4FE1\u8F6E\uFF1A\u4F60\u4EA4\u4ED8\u7684\u4EA7\u7269\u6536\u5230\u4E00\u6761\u8BC4\u8BBA\uFF0C\u7B49\u4F60\u7B54\u590D` : `# \u56DE\u590D\u8F6E\uFF1A\u4F60\u88AB @ \u8FDB\u4E00\u5C01\u4FE1\u91CC\u56DE\u8BDD`,
|
|
3268
3312
|
``,
|
|
3269
3313
|
`artifact_id: ${artifactId}`,
|
|
3270
3314
|
`annotation_id: ${a.id}`,
|
|
@@ -3272,8 +3316,13 @@ async function assembleContext(args) {
|
|
|
3272
3316
|
`your_actor_id: ${args.actorId ?? ""}`,
|
|
3273
3317
|
``,
|
|
3274
3318
|
`## \u672C\u6B21\u4E3A\u4EC0\u4E48\u5524\u8D77\u4F60`,
|
|
3275
|
-
|
|
3276
|
-
|
|
3319
|
+
...ownerReply ? [
|
|
3320
|
+
`\u4F60\u6B64\u524D\u5B8C\u6210\u5E76\u4EA4\u4ED8\u4E86\u4EA7\u7269 ${artifactId}\uFF08\u300C${artifact.title}\u300D\uFF09\u3002\u73B0\u5728\u6709\u4EBA\u5BF9\u5B83\u7559\u4E86\u4E00\u6761\u8BC4\u8BBA\uFF0C\u7B49\u4F60\u7B54\u590D\u3002`,
|
|
3321
|
+
`\u672C\u8F6E**\u53EA\u56DE\u8BDD\uFF0C\u4E0D\u6539\u4EA7\u7269**\uFF1A\u8BFB\u8BC4\u8BBA\u3001\u7ED3\u5408\u4F60\u4EA4\u4ED8\u65F6\u7684\u4E0A\u4E0B\u6587\uFF0C\u56DE\u4E00\u5E16\u628A\u95EE\u9898\u8BF4\u6E05\u695A\u3002`
|
|
3322
|
+
] : [
|
|
3323
|
+
`\u6709\u4EBA\u5728\u4EA7\u7269 ${artifactId} \u7684\u4E00\u5C01\u4FE1\u91CC @ \u4E86\u4F60\uFF0C\u7B49\u4F60\u56DE\u8BDD\u3002\u4F60**\u4E0D\u662F**\u8FD9\u4E2A\u4EA7\u7269\u7684 owner\u2014\u2014`,
|
|
3324
|
+
`\u672C\u8F6E\u4F60\u53EA\u6709\u53D1\u8A00\u6743\uFF1A\u8BFB\u4E0A\u4E0B\u6587\u3001\u56DE\u4E00\u5E16\uFF0C\u522B\u7684\u90FD\u4E0D\u7528\uFF08\u4E5F\u4E0D\u80FD\uFF09\u505A\u3002`
|
|
3325
|
+
],
|
|
3277
3326
|
``,
|
|
3278
3327
|
`## \u8981\u4F60\u56DE\u7684\u65B0\u8BDD\uFF08\u4F5C\u8005 ${actorLabel(a.author)}${a.state === "open" ? "" : `\uFF0C\u72B6\u6001 ${a.state}`}\uFF09`,
|
|
3279
3328
|
...newPostLines,
|
|
@@ -3287,12 +3336,17 @@ async function assembleContext(args) {
|
|
|
3287
3336
|
`- \`oasis reply ${a.id} --body "<\u4F60\u7684\u56DE\u8BDD>"\` \u2014\u2014 **\u672C\u8F6E\u552F\u4E00\u7684\u5199\u52A8\u4F5C**\u3002`,
|
|
3288
3337
|
``,
|
|
3289
3338
|
`\u6536\u5BB9\u8FB9\u754C\uFF08\u7CFB\u7EDF\u5728\u670D\u52A1\u7AEF\u786C\u62E6\uFF0C\u4E0D\u662F\u5BA2\u5957\uFF09\uFF1A\u4E0D\u80FD\u6539\u4EA7\u7269\uFF08propose/merge/conclude \u90FD\u4E0D\u53EF\u7528\uFF09\u3001`,
|
|
3290
|
-
`\u4E0D\u80FD\u518D @ \u4EFB\u4F55\u4EBA\uFF08@ \u6743\u53EA\u5C5E\u4E8E\u4EBA\uFF09\u3001\u4E0D\u80FD resolve \u5173\u4FE1\uFF08\u90A3\u662F owner/\u4F5C\u8005\u7684\u4E8B\uFF09\u3002\u56DE\u5B8C\u5E16\u5373\u6536\u5DE5\u3002`,
|
|
3339
|
+
ownerReply ? `\u4E0D\u80FD\u518D @ \u4EFB\u4F55\u4EBA\uFF08@ \u6743\u53EA\u5C5E\u4E8E\u4EBA\uFF09\u3001\u4E0D\u7528 resolve \u5173\u4FE1\uFF08\u56DE\u5E16\u540E\u7531\u7CFB\u7EDF\u81EA\u52A8\u5173\uFF09\u3002\u56DE\u5B8C\u5E16\u5373\u6536\u5DE5\u3002` : `\u4E0D\u80FD\u518D @ \u4EFB\u4F55\u4EBA\uFF08@ \u6743\u53EA\u5C5E\u4E8E\u4EBA\uFF09\u3001\u4E0D\u80FD resolve \u5173\u4FE1\uFF08\u90A3\u662F owner/\u4F5C\u8005\u7684\u4E8B\uFF09\u3002\u56DE\u5B8C\u5E16\u5373\u6536\u5DE5\u3002`,
|
|
3291
3340
|
``,
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3341
|
+
...ownerReply ? [
|
|
3342
|
+
`**\u672C\u8F6E\u4F60\u53EA\u80FD\u56DE\u8BDD\uFF0C\u6539\u4E0D\u4E86\u4EA7\u7269\u3002** \u5982\u679C\u8BC4\u8BBA\u610F\u5473\u7740\u4EA7\u7269\u9700\u8981\u6539\uFF08\u8865\u4E00\u6BB5\u3001\u6539\u4E00\u4E2A\u5224\u636E\u3001\u6362\u4E2A\u65B9\u6848\uFF09\uFF0C`,
|
|
3343
|
+
`**\u4E0D\u8981\u627F\u8BFA"\u6211\u8FD9\u8F6E\u53BB\u6539"**\u2014\u2014\u5728\u56DE\u5E16\u91CC\u8BF4\u6E05\u300C\u8981\u6539\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u300D\uFF0C\u4EBA\u770B\u5230\u540E\u4F1A\u51B3\u5B9A\u662F\u5426\u8BA9\u8282\u70B9\u91CD\u8DD1\uFF08\u91CD\u8DD1\u4F1A\u5E26\u4E0A\u8FD9\u6761\u8BC4\u8BBA\u7684\u4E0A\u4E0B\u6587\uFF09\u3002`
|
|
3344
|
+
] : [
|
|
3345
|
+
// 改 5:给「非 owner agent」一条明确出路——不能改产物时别硬扛/别空承诺,请提问人 @ owner。
|
|
3346
|
+
`**\u672C\u8F6E\u4F60\u53EA\u80FD\u56DE\u8BDD\uFF0C\u6539\u4E0D\u4E86\u4EA7\u7269\u3002** \u5982\u679C\u4F60\u7684\u7B54\u590D\u610F\u5473\u7740\u4EA7\u7269\u9700\u8981\u6539\uFF08\u8865\u4E00\u6BB5\u3001\u6539\u4E00\u4E2A\u5224\u636E\u3001\u6362\u4E2A\u65B9\u6848\uFF09\uFF0C`,
|
|
3347
|
+
`**\u4E0D\u8981\u81EA\u5DF1\u52A8\u624B\u3001\u4E5F\u4E0D\u8981\u627F\u8BFA"\u6211\u53BB\u6539"**\u2014\u2014\u5728\u56DE\u5E16\u91CC\u8BF4\u6E05\u300C\u8981\u6539\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u300D\uFF0C\u5E76\u8BF7\u63D0\u95EE\u7684\u4EBA`,
|
|
3348
|
+
`**@ \u672C\u4EA7\u7269\u7684\u8D1F\u8D23\u4EBA\uFF08owner\uFF09** \u6765\u5904\u7F6E\uFF1A\u53EA\u6709 owner \u88AB\u5524\u8D77\u624D\u80FD\u6539\u4EA7\u7269\uFF0C\u5B83\u80FD\u770B\u5230\u8FD9\u6761\u4FE1\u7684\u5B8C\u6574\u4E0A\u4E0B\u6587\u3002`
|
|
3349
|
+
],
|
|
3296
3350
|
...memoryIndexLines(replyMemories)
|
|
3297
3351
|
].join("\n");
|
|
3298
3352
|
return { files };
|
|
@@ -3341,6 +3395,7 @@ async function assembleContext(args) {
|
|
|
3341
3395
|
}
|
|
3342
3396
|
const contentMapOf = async (rev) => {
|
|
3343
3397
|
if (!rev) return /* @__PURE__ */ new Map();
|
|
3398
|
+
if (isEmptyContent(rev.contentKind)) return null;
|
|
3344
3399
|
if (rev.contentKind === "external-pin") return null;
|
|
3345
3400
|
if (rev.contentKind === "manifest") {
|
|
3346
3401
|
const m2 = /* @__PURE__ */ new Map();
|
|
@@ -4004,6 +4059,7 @@ ${manifestDiff(base, next)}`
|
|
|
4004
4059
|
}
|
|
4005
4060
|
async function revContentMap(blobs, rev) {
|
|
4006
4061
|
if (!rev) return /* @__PURE__ */ new Map();
|
|
4062
|
+
if (isEmptyContent(rev.contentKind)) return null;
|
|
4007
4063
|
if (rev.contentKind === "external-pin") return null;
|
|
4008
4064
|
if (rev.contentKind === "manifest") {
|
|
4009
4065
|
const m2 = /* @__PURE__ */ new Map();
|
|
@@ -5187,7 +5243,18 @@ var init_dispatcher = __esm({
|
|
|
5187
5243
|
actor: args.actor,
|
|
5188
5244
|
action: "produce",
|
|
5189
5245
|
...args.part !== void 0 ? { part: args.part } : {}
|
|
5190
|
-
}, args.bypassScheduling === true);
|
|
5246
|
+
}, args.bypassScheduling === true, args.dispatchId);
|
|
5247
|
+
}
|
|
5248
|
+
/** 新引擎回信 work(设计-最终 §回信轮)的 effect 调到此——派发收容回信会话(唯一写动作 `oasis reply`)。
|
|
5249
|
+
* jobKey 锚在 issue(annotationId)上,与既有回复轮(信箱 ④ 被 @)同一套装配与写围栏。 */
|
|
5250
|
+
async requestReplyWork(args) {
|
|
5251
|
+
const jobKey = `reply::${args.annotationId}::${args.actor}`;
|
|
5252
|
+
return this.spawnJob(jobKey, {
|
|
5253
|
+
artifactId: args.artifactId,
|
|
5254
|
+
actor: args.actor,
|
|
5255
|
+
action: "reply",
|
|
5256
|
+
annotationId: args.annotationId
|
|
5257
|
+
}, args.bypassScheduling === true, args.dispatchId);
|
|
5191
5258
|
}
|
|
5192
5259
|
/** 新引擎 review.create 的 effect 调到此——派发 AI 评审会话。 */
|
|
5193
5260
|
async requestReview(args) {
|
|
@@ -5196,7 +5263,7 @@ var init_dispatcher = __esm({
|
|
|
5196
5263
|
artifactId: args.artifactId,
|
|
5197
5264
|
actor: args.actor,
|
|
5198
5265
|
action: "review"
|
|
5199
|
-
}, args.bypassScheduling === true);
|
|
5266
|
+
}, args.bypassScheduling === true, args.dispatchId);
|
|
5200
5267
|
}
|
|
5201
5268
|
/** 该产物是否有在途**生产**会话(部件或整体)——集成须等所有 producer 退场后才抢。 */
|
|
5202
5269
|
hasInFlightProduce(artifactId) {
|
|
@@ -5220,9 +5287,9 @@ var init_dispatcher = __esm({
|
|
|
5220
5287
|
* 后者答「哪个运行时进程」(节点铸、spawn 才有)。二者一一对应,映射记在 run 上,不合并。
|
|
5221
5288
|
*/
|
|
5222
5289
|
spawnAttemptEpoch = /* @__PURE__ */ new Map();
|
|
5223
|
-
async spawnJob(jobKey, spec, bypassScheduling = false) {
|
|
5290
|
+
async spawnJob(jobKey, spec, bypassScheduling = false, dispatchId) {
|
|
5224
5291
|
if (this.spawning.has(jobKey)) return { kind: "already-in-flight", jobKey };
|
|
5225
|
-
const attempt = `dispatch:${(0, import_node_crypto2.randomUUID)()}`;
|
|
5292
|
+
const attempt = dispatchId ?? `dispatch:${(0, import_node_crypto2.randomUUID)()}`;
|
|
5226
5293
|
this.spawnAttemptEpoch.set(jobKey, attempt);
|
|
5227
5294
|
this.spawning.add(jobKey);
|
|
5228
5295
|
const work = this.spawnJobInner(jobKey, spec, attempt, bypassScheduling).then(
|
|
@@ -6079,19 +6146,18 @@ var init_dispatcher = __esm({
|
|
|
6079
6146
|
});
|
|
6080
6147
|
|
|
6081
6148
|
// ../engine/src/model.ts
|
|
6082
|
-
var DEFAULT_CONFIG, MAX_WORK_DURATION_MS, MAX_REVIEW_DURATION_MS, MAX_REVIEW_RETRIES, deriveWorkId, deriveReviewId, deriveReplyId;
|
|
6149
|
+
var DEFAULT_CONFIG, MAX_WORK_DURATION_MS, MAX_REVIEW_DURATION_MS, MAX_REVIEW_RETRIES, deriveWorkId, deriveReplyWorkId, deriveReviewId, deriveReplyId;
|
|
6083
6150
|
var init_model = __esm({
|
|
6084
6151
|
"../engine/src/model.ts"() {
|
|
6085
6152
|
"use strict";
|
|
6086
6153
|
DEFAULT_CONFIG = {
|
|
6087
|
-
maxRetries: 3
|
|
6088
|
-
dispatchWaitMs: 5 * 6e4,
|
|
6089
|
-
leaseMs: 2 * 6e4
|
|
6154
|
+
maxRetries: 3
|
|
6090
6155
|
};
|
|
6091
6156
|
MAX_WORK_DURATION_MS = 30 * 6e4;
|
|
6092
6157
|
MAX_REVIEW_DURATION_MS = 15 * 6e4;
|
|
6093
6158
|
MAX_REVIEW_RETRIES = 3;
|
|
6094
6159
|
deriveWorkId = (nodeId, version2) => `wk:${nodeId}:${version2}`;
|
|
6160
|
+
deriveReplyWorkId = (issueId, n) => `wk:reply:${issueId}:${n}`;
|
|
6095
6161
|
deriveReviewId = (workId, reviewer, group) => `rv:${workId}:${reviewer}:${group}`;
|
|
6096
6162
|
deriveReplyId = (issueId, salt) => `ir:${issueId}:${salt}`;
|
|
6097
6163
|
}
|
|
@@ -6177,7 +6243,23 @@ function isReviewOpen(r) {
|
|
|
6177
6243
|
return !r.cancelledAt && !r.endedAt && !r.verdict;
|
|
6178
6244
|
}
|
|
6179
6245
|
function countFailedForNode(works) {
|
|
6180
|
-
const sorted = [...works].sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
6246
|
+
const sorted = [...works].filter((w2) => !w2.replyToIssueId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
6247
|
+
let count2 = 0;
|
|
6248
|
+
for (const w2 of sorted) {
|
|
6249
|
+
if (w2.retryAt) break;
|
|
6250
|
+
const s2 = w2.status ?? (w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
6251
|
+
if (s2 === "running") continue;
|
|
6252
|
+
if (s2 === "dead" && !w2.endedAt) continue;
|
|
6253
|
+
if (s2 === "failed" || s2 === "dead" && w2.endedAt) {
|
|
6254
|
+
count2++;
|
|
6255
|
+
continue;
|
|
6256
|
+
}
|
|
6257
|
+
break;
|
|
6258
|
+
}
|
|
6259
|
+
return count2;
|
|
6260
|
+
}
|
|
6261
|
+
function countFailedForReplyIssue(issueId, works) {
|
|
6262
|
+
const sorted = works.filter((w2) => w2.replyToIssueId === issueId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
6181
6263
|
let count2 = 0;
|
|
6182
6264
|
for (const w2 of sorted) {
|
|
6183
6265
|
if (w2.retryAt) break;
|
|
@@ -6468,15 +6550,7 @@ var init_state = __esm({
|
|
|
6468
6550
|
function scan(state, ctx) {
|
|
6469
6551
|
const events = [];
|
|
6470
6552
|
const now = ctx.at;
|
|
6471
|
-
const resumeMarker = state.workorder.resumeMarker;
|
|
6472
6553
|
for (const node of state.liveNodes()) {
|
|
6473
|
-
if (node.lastResumeMarker < resumeMarker) {
|
|
6474
|
-
const rlw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
6475
|
-
if (rlw && rlw.deadAt !== null && rlw.endedAt === null) {
|
|
6476
|
-
tryToRun(state, node.id, ctx, events);
|
|
6477
|
-
continue;
|
|
6478
|
-
}
|
|
6479
|
-
}
|
|
6480
6554
|
const lw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
6481
6555
|
if (!lw) {
|
|
6482
6556
|
tryToRun(state, node.id, ctx, events);
|
|
@@ -6496,6 +6570,11 @@ function scan(state, ctx) {
|
|
|
6496
6570
|
continue;
|
|
6497
6571
|
}
|
|
6498
6572
|
if (wstate === "running") {
|
|
6573
|
+
const runningBlocking = findOpenBlockingIssueFromOthers(node.id, state.allIssues());
|
|
6574
|
+
if (runningBlocking && runningBlocking.createdAt > (lw.startedAt ?? lw.createdAt)) {
|
|
6575
|
+
tryToRun(state, node.id, ctx, events);
|
|
6576
|
+
continue;
|
|
6577
|
+
}
|
|
6499
6578
|
const start = lw.startedAt ?? lw.createdAt;
|
|
6500
6579
|
const elapsed = new Date(now).getTime() - new Date(start).getTime();
|
|
6501
6580
|
if (elapsed > MAX_WORK_DURATION_MS) {
|
|
@@ -6545,6 +6624,10 @@ function scan(state, ctx) {
|
|
|
6545
6624
|
}
|
|
6546
6625
|
continue;
|
|
6547
6626
|
}
|
|
6627
|
+
if (wstate === "success" && node.latestAcceptId === lw.id) {
|
|
6628
|
+
processCommentReplies(state, node.id, ctx, events);
|
|
6629
|
+
continue;
|
|
6630
|
+
}
|
|
6548
6631
|
}
|
|
6549
6632
|
const terminated = events.length === 0 && checkTerminated(state);
|
|
6550
6633
|
return { events, terminated };
|
|
@@ -6552,7 +6635,6 @@ function scan(state, ctx) {
|
|
|
6552
6635
|
function tryToRun(state, nodeId, ctx, events) {
|
|
6553
6636
|
const node = state.node(nodeId);
|
|
6554
6637
|
if (!node) return;
|
|
6555
|
-
if (state.workorder.dispatchPaused) return;
|
|
6556
6638
|
if (!node.assigneeActorId) return;
|
|
6557
6639
|
const inbound = state.edgesInto(nodeId);
|
|
6558
6640
|
for (const e of inbound) {
|
|
@@ -6669,6 +6751,92 @@ function processReviews(state, nodeId, workId, ctx, events) {
|
|
|
6669
6751
|
});
|
|
6670
6752
|
}
|
|
6671
6753
|
}
|
|
6754
|
+
function processCommentReplies(state, nodeId, ctx, events) {
|
|
6755
|
+
const node = state.node(nodeId);
|
|
6756
|
+
if (!node?.assigneeActorId) return;
|
|
6757
|
+
const comments = state.allIssues().filter((i) => i.kind === "comment" && i.state === "open" && i.aboutNodeId === nodeId).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
6758
|
+
if (comments.length === 0) return;
|
|
6759
|
+
if (state.hasQueuedWorkFor(nodeId)) return;
|
|
6760
|
+
const replyWorks = state.worksOf(nodeId).filter((w2) => w2.replyToIssueId);
|
|
6761
|
+
const inflight = replyWorks.find((w2) => !w2.endedAt && !w2.deadAt);
|
|
6762
|
+
if (inflight) {
|
|
6763
|
+
const start = inflight.startedAt ?? inflight.createdAt;
|
|
6764
|
+
const elapsed = new Date(ctx.at).getTime() - new Date(start).getTime();
|
|
6765
|
+
if (elapsed > MAX_WORK_DURATION_MS) {
|
|
6766
|
+
events.push({ kind: "work.timeout", workorderId: state.workorder.id, workId: inflight.id });
|
|
6767
|
+
}
|
|
6768
|
+
return;
|
|
6769
|
+
}
|
|
6770
|
+
for (const issue2 of comments) {
|
|
6771
|
+
const mine = replyWorks.filter((w2) => w2.replyToIssueId === issue2.id).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
6772
|
+
const latest = mine[mine.length - 1];
|
|
6773
|
+
if (latest) {
|
|
6774
|
+
const s2 = workState(latest, hasOutput(latest, 0));
|
|
6775
|
+
if (s2 === "success" && latest.acceptanceState !== "accepted") {
|
|
6776
|
+
events.push({
|
|
6777
|
+
kind: "work.accept",
|
|
6778
|
+
workorderId: state.workorder.id,
|
|
6779
|
+
workId: latest.id,
|
|
6780
|
+
by: "actor:system:engine",
|
|
6781
|
+
override: null
|
|
6782
|
+
});
|
|
6783
|
+
return;
|
|
6784
|
+
}
|
|
6785
|
+
if (s2 === "failed") {
|
|
6786
|
+
if (countFailedForReplyIssue(issue2.id, replyWorks) > (ctx.config?.maxRetries ?? DEFAULT_CONFIG.maxRetries)) {
|
|
6787
|
+
events.push({
|
|
6788
|
+
kind: "work.kill",
|
|
6789
|
+
workorderId: state.workorder.id,
|
|
6790
|
+
nodeId,
|
|
6791
|
+
workId: latest.id,
|
|
6792
|
+
reason: "reply failed exhausted"
|
|
6793
|
+
});
|
|
6794
|
+
} else {
|
|
6795
|
+
events.push({
|
|
6796
|
+
kind: "work.create",
|
|
6797
|
+
workorderId: state.workorder.id,
|
|
6798
|
+
nodeId,
|
|
6799
|
+
workId: deriveReplyWorkId(issue2.id, mine.length + 1),
|
|
6800
|
+
reasons: [],
|
|
6801
|
+
continuesWorkId: node.latestAcceptId,
|
|
6802
|
+
replyToIssueId: issue2.id
|
|
6803
|
+
});
|
|
6804
|
+
}
|
|
6805
|
+
return;
|
|
6806
|
+
}
|
|
6807
|
+
if (s2 === "retry") {
|
|
6808
|
+
events.push({
|
|
6809
|
+
kind: "work.create",
|
|
6810
|
+
workorderId: state.workorder.id,
|
|
6811
|
+
nodeId,
|
|
6812
|
+
workId: deriveReplyWorkId(issue2.id, mine.length + 1),
|
|
6813
|
+
reasons: [],
|
|
6814
|
+
continuesWorkId: node.latestAcceptId,
|
|
6815
|
+
replyToIssueId: issue2.id
|
|
6816
|
+
});
|
|
6817
|
+
return;
|
|
6818
|
+
}
|
|
6819
|
+
if (s2 === "dead") {
|
|
6820
|
+
const acceptedWork = node.latestAcceptId ? state.work(node.latestAcceptId) : null;
|
|
6821
|
+
const reFinished = !!acceptedWork?.acceptedAt && latest.createdAt < acceptedWork.acceptedAt;
|
|
6822
|
+
if (!reFinished) continue;
|
|
6823
|
+
}
|
|
6824
|
+
}
|
|
6825
|
+
const replies = state.repliesOf(issue2.id).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
6826
|
+
const lastAuthor = replies.length > 0 ? replies[replies.length - 1].authorActorId : issue2.authorActorId;
|
|
6827
|
+
if (lastAuthor === node.assigneeActorId) continue;
|
|
6828
|
+
events.push({
|
|
6829
|
+
kind: "work.create",
|
|
6830
|
+
workorderId: state.workorder.id,
|
|
6831
|
+
nodeId,
|
|
6832
|
+
workId: deriveReplyWorkId(issue2.id, mine.length + 1),
|
|
6833
|
+
reasons: [],
|
|
6834
|
+
continuesWorkId: node.latestAcceptId,
|
|
6835
|
+
replyToIssueId: issue2.id
|
|
6836
|
+
});
|
|
6837
|
+
return;
|
|
6838
|
+
}
|
|
6839
|
+
}
|
|
6672
6840
|
function nextReviewId(state, workId, reviewerActorId, group) {
|
|
6673
6841
|
const existing = state.reviewsOf(workId).filter(
|
|
6674
6842
|
(r) => r.reviewerActorId === reviewerActorId && r.reviewGroup === group
|
|
@@ -6677,6 +6845,7 @@ function nextReviewId(state, workId, reviewerActorId, group) {
|
|
|
6677
6845
|
return `rv:${workId}:${reviewerActorId}:${group}:${existing.length + 1}`;
|
|
6678
6846
|
}
|
|
6679
6847
|
function reviewStatus(r) {
|
|
6848
|
+
if (r.status) return r.status;
|
|
6680
6849
|
if (r.retryAt) return "retry";
|
|
6681
6850
|
if (r.cancelledAt) return "dead";
|
|
6682
6851
|
if (r.verdict === "approve") return "accept";
|
|
@@ -6689,15 +6858,15 @@ function checkTerminated(state) {
|
|
|
6689
6858
|
if (state.allWorks().some(isWorkOpen)) return false;
|
|
6690
6859
|
if (state.allIssues().some((i) => i.state === "open")) return false;
|
|
6691
6860
|
const live = state.liveNodes();
|
|
6692
|
-
if (live.length === 0) return
|
|
6861
|
+
if (live.length === 0) return true;
|
|
6693
6862
|
return live.every((n) => n.latestWorkId !== null && n.latestAcceptId === n.latestWorkId);
|
|
6694
6863
|
}
|
|
6695
6864
|
function activate(state, nodeId, _ctx) {
|
|
6696
6865
|
const node = state.node(nodeId);
|
|
6697
6866
|
if (!node) return "cancelled";
|
|
6698
|
-
if (node.cancelledAt
|
|
6867
|
+
if (node.cancelledAt) return "cancelled";
|
|
6699
6868
|
if (state.workorder.lifecycle === "draft") return "paused";
|
|
6700
|
-
if (
|
|
6869
|
+
if (state.workorder.dispatchPaused) return "paused";
|
|
6701
6870
|
if (!node.assigneeActorId) return "no_assignee";
|
|
6702
6871
|
if (state.openWorkOf(nodeId)) return "already_running";
|
|
6703
6872
|
if (state.hasQueuedWorkFor(nodeId)) return "already_running";
|
|
@@ -6744,12 +6913,11 @@ CREATE TABLE IF NOT EXISTS ${schema}.workorders (
|
|
|
6744
6913
|
owner_actor_id text NOT NULL,
|
|
6745
6914
|
manager_actor_id text,
|
|
6746
6915
|
root_node_id text,
|
|
6747
|
-
lifecycle text NOT NULL CHECK (lifecycle IN ('draft','active'
|
|
6748
|
-
seal_reason text CHECK (seal_reason IN ('accepted','cancelled','frozen','converged')),
|
|
6916
|
+
lifecycle text NOT NULL CHECK (lifecycle IN ('draft','active')),
|
|
6749
6917
|
dispatch_paused boolean NOT NULL DEFAULT false,
|
|
6750
6918
|
paused_by text,
|
|
6751
6919
|
paused_reason text,
|
|
6752
|
-
|
|
6920
|
+
done_at timestamptz,
|
|
6753
6921
|
structural_version integer NOT NULL DEFAULT 0,
|
|
6754
6922
|
row_version integer NOT NULL DEFAULT 0,
|
|
6755
6923
|
created_at timestamptz NOT NULL,
|
|
@@ -6765,19 +6933,11 @@ CREATE TABLE IF NOT EXISTS ${schema}.workflow_nodes (
|
|
|
6765
6933
|
title text NOT NULL,
|
|
6766
6934
|
spec text,
|
|
6767
6935
|
version integer NOT NULL DEFAULT 0,
|
|
6768
|
-
spec_version integer NOT NULL DEFAULT 0,
|
|
6769
|
-
consumed_spec_version integer NOT NULL DEFAULT 0,
|
|
6770
6936
|
assignee_actor_id text,
|
|
6771
6937
|
assignee_role text,
|
|
6772
|
-
current_work_id text,
|
|
6773
6938
|
latest_work_id text,
|
|
6774
6939
|
latest_accept_id text,
|
|
6775
|
-
last_resume_marker integer NOT NULL DEFAULT 0,
|
|
6776
6940
|
is_final_output boolean NOT NULL DEFAULT false,
|
|
6777
|
-
retry_count integer NOT NULL DEFAULT 0,
|
|
6778
|
-
paused boolean NOT NULL DEFAULT false,
|
|
6779
|
-
paused_by text,
|
|
6780
|
-
paused_reason text,
|
|
6781
6941
|
fields jsonb,
|
|
6782
6942
|
cancelled_at timestamptz,
|
|
6783
6943
|
row_version integer NOT NULL DEFAULT 0,
|
|
@@ -6829,6 +6989,8 @@ CREATE TABLE IF NOT EXISTS ${schema}.works (
|
|
|
6829
6989
|
outcome_detail jsonb,
|
|
6830
6990
|
session_ref text,
|
|
6831
6991
|
continues_work_id text,
|
|
6992
|
+
-- \u56DE\u4FE1 work\uFF08\u8BBE\u8BA1-\u6700\u7EC8 \xA7\u56DE\u4FE1\u8F6E\uFF09\uFF1A\u975E\u7A7A = \u5BF9\u8BE5 comment issue \u7684\u56DE\u4FE1\u8F6E\uFF0C\u4E0D\u8FDB\u8282\u70B9\u4E3B\u94FE
|
|
6993
|
+
reply_to_issue_id text,
|
|
6832
6994
|
output_version_no integer,
|
|
6833
6995
|
conclusion text,
|
|
6834
6996
|
acceptance_state text CHECK (acceptance_state IN ('draft','accepted','rejected')),
|
|
@@ -6971,7 +7133,6 @@ CREATE TABLE IF NOT EXISTS ${schema}.events (
|
|
|
6971
7133
|
status text NOT NULL CHECK (status IN ('pending','applied','done','failed')),
|
|
6972
7134
|
attempts integer NOT NULL DEFAULT 0,
|
|
6973
7135
|
available_at timestamptz,
|
|
6974
|
-
effect_lease_until timestamptz,
|
|
6975
7136
|
failed_reason text,
|
|
6976
7137
|
created_at timestamptz NOT NULL,
|
|
6977
7138
|
applied_at timestamptz,
|
|
@@ -6995,7 +7156,46 @@ ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
|
6995
7156
|
ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS retry_at timestamptz;
|
|
6996
7157
|
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS status text;
|
|
6997
7158
|
ALTER TABLE ${schema}.reviews ADD COLUMN IF NOT EXISTS status text;
|
|
7159
|
+
-- \u56DE\u4FE1 work\uFF08\u8BBE\u8BA1-\u6700\u7EC8 \xA7\u56DE\u4FE1\u8F6E\uFF09\uFF1A\u975E\u7A7A = \u5BF9\u8BE5 comment issue \u7684\u56DE\u4FE1\u8F6E\uFF0C\u4E0D\u8FDB\u8282\u70B9\u4E3B\u94FE
|
|
7160
|
+
ALTER TABLE ${schema}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
|
|
6998
7161
|
ALTER TABLE ${schema}.workflow_nodes ADD COLUMN IF NOT EXISTS fields jsonb;
|
|
7162
|
+
-- \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
|
|
7163
|
+
-- DROP COLUMN \u4F1A\u62A5 "cannot drop column ... because other objects depend on it"\uFF0C\u4E14\u672C\u6BB5\u662F\u5355\u6761
|
|
7164
|
+
-- \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
|
|
7165
|
+
-- ENGINE_VIEWS \u91CD\u5EFA\uFF08migrate \u987A\u5E8F\uFF1AENGINE_DDL \u2192 ENGINE_VIEWS\uFF09\u3002
|
|
7166
|
+
DROP VIEW IF EXISTS ${schema}.work_view;
|
|
7167
|
+
DROP VIEW IF EXISTS ${schema}.review_view;
|
|
7168
|
+
DROP VIEW IF EXISTS ${schema}.node_view;
|
|
7169
|
+
DROP VIEW IF EXISTS ${schema}.workorder_view;
|
|
7170
|
+
-- \u8C03\u5EA6\u5668\u5B8C\u7ED3\u6807\u8BB0\uFF08mark_done/unmark_done \u843D\u70B9\uFF0C2026-08-12\uFF09\u3002
|
|
7171
|
+
ALTER TABLE ${schema}.workorders ADD COLUMN IF NOT EXISTS done_at timestamptz;
|
|
7172
|
+
-- \u2605 \u5DE5\u5355\u7EA7\u5C01\u5B58\u6574\u4F53\u79FB\u9664\uFF08ADR 0137\uFF09\uFF1Alifecycle \u53EA\u5269 draft/active\uFF0Cseal_reason \u5217\u5220\u9664\u3002
|
|
7173
|
+
-- \u5B58\u91CF sealed \u5DE5\u5355\u8FC1\u79FB\uFF1A\u5148\u628A\u5176\u5728\u518C\u8282\u70B9\u5168\u90E8\u4F5C\u5E9F\uFF08cancelled_at\u2014\u2014\u4FDD\u4F4F\u300C\u5DF2\u5F52\u6863/\u5DF2\u5E9F\u5F03\u300D\u7684
|
|
7174
|
+
-- \u4E1A\u52A1\u8BED\u4E49\uFF0Cper-node \u4F5C\u5E9F\u662F\u552F\u4E00\u4FDD\u7559\u7684\u4F5C\u5E9F\u673A\u5236\uFF09\uFF0C\u518D\u8FC1\u56DE active + done_at\uFF08\u79FB\u51FA\u8C03\u5EA6\u626B\u63CF\uFF09\u3002
|
|
7175
|
+
UPDATE ${schema}.workflow_nodes SET cancelled_at = COALESCE(cancelled_at, now())
|
|
7176
|
+
WHERE cancelled_at IS NULL
|
|
7177
|
+
AND workorder_id IN (SELECT id FROM ${schema}.workorders WHERE lifecycle = 'sealed');
|
|
7178
|
+
UPDATE ${schema}.workorders SET lifecycle = 'active', done_at = COALESCE(done_at, updated_at)
|
|
7179
|
+
WHERE lifecycle = 'sealed';
|
|
7180
|
+
-- CHECK \u7EA6\u675F\u6536\u7A84\u4E3A draft/active\uFF08\u5148\u5220\u540E\u52A0\uFF0C\u6210\u5BF9\u5E42\u7B49\uFF09\uFF1Bseal_reason \u968F\u540E\u5220\u5217\u3002
|
|
7181
|
+
ALTER TABLE ${schema}.workorders DROP CONSTRAINT IF EXISTS workorders_lifecycle_check;
|
|
7182
|
+
ALTER TABLE ${schema}.workorders ADD CONSTRAINT workorders_lifecycle_check
|
|
7183
|
+
CHECK (lifecycle IN ('draft','active'));
|
|
7184
|
+
ALTER TABLE ${schema}.workorders DROP COLUMN IF EXISTS seal_reason;
|
|
7185
|
+
-- \u65B0\u5F15\u64CE\u5DF2\u4E0D\u7528\u7684\u65E7\u5217\uFF08node.version \u7EDF\u4E00\u8BA1\u6570\u5668\u53D6\u4EE3 spec_version/consumed_spec_version\uFF1B
|
|
7186
|
+
-- latest_work_id/latest_accept_id \u53D6\u4EE3 current_work_id\uFF1B\u5DE5\u5355\u7EA7\u6682\u505C\u53D6\u4EE3\u8282\u70B9\u7EA7 paused*\uFF1B
|
|
7187
|
+
-- retry \u662F work \u72B6\u6001\u4E4B\u4E00\uFF0C\u4E0D\u662F\u8282\u70B9\u5217\uFF1BL2 \u79DF\u7EA6\u8F6E\u8BE2\u5DF2\u5220\uFF0Ceffect_lease_until \u6210\u6B7B\u5217\uFF1B
|
|
7188
|
+
-- resume_marker/last_resume_marker \u5DF2\u88AB\u300Cresume=\u6279\u91CF node_retry\u300D\u53D6\u4EE3\uFF0C2026-08-12\uFF09\u3002
|
|
7189
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS spec_version;
|
|
7190
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS consumed_spec_version;
|
|
7191
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS current_work_id;
|
|
7192
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS retry_count;
|
|
7193
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS paused;
|
|
7194
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS paused_by;
|
|
7195
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS paused_reason;
|
|
7196
|
+
ALTER TABLE ${schema}.workflow_nodes DROP COLUMN IF EXISTS last_resume_marker;
|
|
7197
|
+
ALTER TABLE ${schema}.workorders DROP COLUMN IF EXISTS resume_marker;
|
|
7198
|
+
ALTER TABLE ${schema}.events DROP COLUMN IF EXISTS effect_lease_until;
|
|
6999
7199
|
-- \u5B58\u91CF\u56DE\u586B\uFF1Astatus \u5217=null \u7684\u884C\u6309\u65F6\u95F4\u6233/\u4EA7\u51FA\u56DE\u586B\uFF08\u4E0E views.ts \u56DE\u9000\u6D3E\u751F\u540C\u8BED\u4E49\uFF0C\u56DE\u586B\u540E\u72B6\u6001\u5373\u5B58\u50A8\u6001\uFF09
|
|
7000
7200
|
UPDATE ${schema}.works SET status = CASE
|
|
7001
7201
|
WHEN retry_at IS NOT NULL THEN 'retry'
|
|
@@ -7020,43 +7220,29 @@ function ENGINE_VIEWS(schema) {
|
|
|
7020
7220
|
return `
|
|
7021
7221
|
DROP VIEW IF EXISTS ${schema}.work_view;
|
|
7022
7222
|
CREATE VIEW ${schema}.work_view AS
|
|
7023
|
-
SELECT w.*,
|
|
7024
|
-
CASE WHEN w.cancelled_at IS NOT NULL THEN 'cancelled'
|
|
7025
|
-
WHEN w.ended_at IS NOT NULL THEN 'finished'
|
|
7026
|
-
WHEN w.started_at IS NOT NULL THEN 'running'
|
|
7027
|
-
ELSE 'waiting' END AS state
|
|
7223
|
+
SELECT w.*, w.status AS state
|
|
7028
7224
|
FROM ${schema}.works w;
|
|
7029
7225
|
|
|
7030
7226
|
DROP VIEW IF EXISTS ${schema}.review_view;
|
|
7031
7227
|
CREATE VIEW ${schema}.review_view AS
|
|
7032
|
-
SELECT r.*,
|
|
7033
|
-
CASE WHEN r.cancelled_at IS NOT NULL THEN 'cancelled'
|
|
7034
|
-
WHEN r.ended_at IS NOT NULL THEN 'finished'
|
|
7035
|
-
WHEN r.started_at IS NOT NULL THEN 'running'
|
|
7036
|
-
ELSE 'waiting' END AS state
|
|
7228
|
+
SELECT r.*, r.status AS state
|
|
7037
7229
|
FROM ${schema}.reviews r;
|
|
7038
7230
|
|
|
7039
7231
|
DROP VIEW IF EXISTS ${schema}.node_view;
|
|
7040
7232
|
CREATE VIEW ${schema}.node_view AS
|
|
7041
7233
|
SELECT n.*,
|
|
7042
7234
|
CASE
|
|
7043
|
-
WHEN n.cancelled_at IS NOT NULL
|
|
7044
|
-
WHEN
|
|
7045
|
-
|
|
7046
|
-
WHEN
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
WHEN
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
OR n.assignee_actor_id IS NULL THEN 'failed'
|
|
7053
|
-
WHEN EXISTS (SELECT 1 FROM ${schema}.workflow_edges e
|
|
7054
|
-
WHERE e.to_node_id = n.id AND e.required AND e.kind = 'data'
|
|
7055
|
-
AND e.pinned_work_id IS NULL) THEN 'pending'
|
|
7056
|
-
WHEN n.current_work_id IS NOT NULL THEN 'done'
|
|
7057
|
-
ELSE 'pending' END AS state
|
|
7235
|
+
WHEN n.cancelled_at IS NOT NULL THEN 'dead' -- \u5DF2\u4F5C\u5E9F\uFF08\u79FB\u51FA\u8BA1\u5212\uFF09
|
|
7236
|
+
WHEN lw.id IS NULL THEN 'waiting' -- \u672A\u8FD0\u884C\uFF08latest_work \u4E3A\u7A7A\uFF09
|
|
7237
|
+
WHEN lw.status = 'retry' THEN 'waiting' -- \u672A\u8FD0\u884C\uFF08\u7528\u6237\u4ECB\u5165\u91CD\u8BD5\uFF0C\u7B49 try_to_run\uFF09
|
|
7238
|
+
WHEN lw.status = 'running' THEN 'running' -- \u5728\u8FDB\u884C
|
|
7239
|
+
WHEN lw.status = 'success' AND n.latest_accept_id IS DISTINCT FROM lw.id THEN 'reviewing' -- \u8BC4\u5BA1\u4E2D
|
|
7240
|
+
WHEN lw.status = 'success' AND n.latest_accept_id = lw.id THEN 'done' -- \u5DF2\u5B8C\u6210
|
|
7241
|
+
WHEN lw.status IN ('failed','dead') THEN 'dead' -- \u5361\u4F4F\u4E86
|
|
7242
|
+
ELSE 'waiting'
|
|
7243
|
+
END AS state
|
|
7058
7244
|
FROM ${schema}.workflow_nodes n
|
|
7059
|
-
JOIN ${schema}.
|
|
7245
|
+
LEFT JOIN ${schema}.works lw ON lw.id = n.latest_work_id;
|
|
7060
7246
|
|
|
7061
7247
|
DROP VIEW IF EXISTS ${schema}.workorder_view;
|
|
7062
7248
|
CREATE VIEW ${schema}.workorder_view AS
|
|
@@ -7064,7 +7250,7 @@ SELECT o.*,
|
|
|
7064
7250
|
(SELECT count(*) FROM ${schema}.workflow_nodes n
|
|
7065
7251
|
WHERE n.workorder_id = o.id AND n.cancelled_at IS NULL) AS total_count,
|
|
7066
7252
|
(SELECT count(*) FROM ${schema}.workflow_nodes n
|
|
7067
|
-
WHERE n.workorder_id = o.id AND n.cancelled_at IS NULL AND n.
|
|
7253
|
+
WHERE n.workorder_id = o.id AND n.cancelled_at IS NULL AND n.latest_accept_id IS NOT NULL) AS done_count
|
|
7068
7254
|
FROM ${schema}.workorders o;
|
|
7069
7255
|
`;
|
|
7070
7256
|
}
|
|
@@ -12212,7 +12398,6 @@ function toRecord(r) {
|
|
|
12212
12398
|
status: r.status,
|
|
12213
12399
|
attempts: Number(r.attempts ?? 0),
|
|
12214
12400
|
availableAt: iso(r.available_at),
|
|
12215
|
-
effectLeaseUntil: iso(r.effect_lease_until),
|
|
12216
12401
|
failedReason: r.failed_reason ?? null,
|
|
12217
12402
|
createdAt: iso(r.created_at),
|
|
12218
12403
|
appliedAt: iso(r.applied_at),
|
|
@@ -12317,11 +12502,10 @@ var init_store_postgres = __esm({
|
|
|
12317
12502
|
managerActorId: r.manager_actor_id,
|
|
12318
12503
|
rootNodeId: r.root_node_id,
|
|
12319
12504
|
lifecycle: r.lifecycle,
|
|
12320
|
-
sealReason: r.seal_reason,
|
|
12321
12505
|
dispatchPaused: r.dispatch_paused,
|
|
12322
12506
|
pausedBy: r.paused_by,
|
|
12323
12507
|
pausedReason: r.paused_reason,
|
|
12324
|
-
|
|
12508
|
+
doneAt: iso(r.done_at),
|
|
12325
12509
|
structuralVersion: r.structural_version,
|
|
12326
12510
|
rowVersion: r.row_version,
|
|
12327
12511
|
createdAt: iso(r.created_at),
|
|
@@ -12334,20 +12518,12 @@ var init_store_postgres = __esm({
|
|
|
12334
12518
|
type: n.type,
|
|
12335
12519
|
title: n.title,
|
|
12336
12520
|
spec: n.spec,
|
|
12337
|
-
version: n.version ??
|
|
12338
|
-
specVersion: n.spec_version,
|
|
12339
|
-
consumedSpecVersion: n.consumed_spec_version,
|
|
12521
|
+
version: n.version ?? 0,
|
|
12340
12522
|
assigneeActorId: n.assignee_actor_id,
|
|
12341
12523
|
assigneeRole: n.assignee_role,
|
|
12342
|
-
currentWorkId: n.current_work_id,
|
|
12343
12524
|
latestWorkId: n.latest_work_id,
|
|
12344
|
-
latestAcceptId: n.latest_accept_id
|
|
12345
|
-
lastResumeMarker: n.last_resume_marker ?? 0,
|
|
12525
|
+
latestAcceptId: n.latest_accept_id,
|
|
12346
12526
|
isFinalOutput: n.is_final_output,
|
|
12347
|
-
retryCount: n.retry_count,
|
|
12348
|
-
paused: n.paused,
|
|
12349
|
-
pausedBy: n.paused_by,
|
|
12350
|
-
pausedReason: n.paused_reason,
|
|
12351
12527
|
cancelledAt: iso(n.cancelled_at),
|
|
12352
12528
|
fields: n.fields ?? void 0,
|
|
12353
12529
|
rowVersion: n.row_version,
|
|
@@ -12389,6 +12565,7 @@ var init_store_postgres = __esm({
|
|
|
12389
12565
|
outcomeDetail: w2.outcome_detail,
|
|
12390
12566
|
sessionRef: w2.session_ref,
|
|
12391
12567
|
continuesWorkId: w2.continues_work_id,
|
|
12568
|
+
replyToIssueId: w2.reply_to_issue_id ?? null,
|
|
12392
12569
|
outputVersionNo: w2.output_version_no,
|
|
12393
12570
|
conclusion: w2.conclusion,
|
|
12394
12571
|
acceptanceState: w2.acceptance_state,
|
|
@@ -12481,9 +12658,9 @@ var init_store_postgres = __esm({
|
|
|
12481
12658
|
await this.c.query(
|
|
12482
12659
|
`INSERT INTO ${this.s}.workorders
|
|
12483
12660
|
(id,company_id,project_id,title,description,goal,acceptance_criteria,owner_actor_id,
|
|
12484
|
-
manager_actor_id,root_node_id,lifecycle,
|
|
12485
|
-
|
|
12486
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20
|
|
12661
|
+
manager_actor_id,root_node_id,lifecycle,dispatch_paused,paused_by,paused_reason,
|
|
12662
|
+
done_at,structural_version,row_version,created_at,created_by,updated_at)
|
|
12663
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20)
|
|
12487
12664
|
ON CONFLICT (id) DO NOTHING`,
|
|
12488
12665
|
[
|
|
12489
12666
|
r.id,
|
|
@@ -12497,11 +12674,10 @@ var init_store_postgres = __esm({
|
|
|
12497
12674
|
r.managerActorId,
|
|
12498
12675
|
r.rootNodeId,
|
|
12499
12676
|
r.lifecycle,
|
|
12500
|
-
r.sealReason,
|
|
12501
12677
|
r.dispatchPaused,
|
|
12502
12678
|
r.pausedBy,
|
|
12503
12679
|
r.pausedReason,
|
|
12504
|
-
r.
|
|
12680
|
+
r.doneAt,
|
|
12505
12681
|
r.structuralVersion,
|
|
12506
12682
|
r.rowVersion,
|
|
12507
12683
|
r.createdAt,
|
|
@@ -12518,10 +12694,10 @@ var init_store_postgres = __esm({
|
|
|
12518
12694
|
const n = m2.row;
|
|
12519
12695
|
await this.c.query(
|
|
12520
12696
|
`INSERT INTO ${this.s}.workflow_nodes
|
|
12521
|
-
(id,workorder_id,type,title,spec,version,
|
|
12522
|
-
|
|
12697
|
+
(id,workorder_id,type,title,spec,version,assignee_actor_id,assignee_role,
|
|
12698
|
+
latest_work_id,latest_accept_id,is_final_output,
|
|
12523
12699
|
fields,cancelled_at,row_version,created_at,updated_at)
|
|
12524
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16
|
|
12700
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16)
|
|
12525
12701
|
ON CONFLICT (id) DO NOTHING`,
|
|
12526
12702
|
[
|
|
12527
12703
|
n.id,
|
|
@@ -12530,19 +12706,11 @@ var init_store_postgres = __esm({
|
|
|
12530
12706
|
n.title,
|
|
12531
12707
|
n.spec,
|
|
12532
12708
|
n.version,
|
|
12533
|
-
n.specVersion ?? 0,
|
|
12534
|
-
n.consumedSpecVersion ?? 0,
|
|
12535
12709
|
n.assigneeActorId,
|
|
12536
12710
|
n.assigneeRole,
|
|
12537
|
-
n.currentWorkId ?? null,
|
|
12538
12711
|
n.latestWorkId,
|
|
12539
12712
|
n.latestAcceptId,
|
|
12540
|
-
n.lastResumeMarker,
|
|
12541
12713
|
n.isFinalOutput,
|
|
12542
|
-
n.retryCount ?? 0,
|
|
12543
|
-
n.paused ?? false,
|
|
12544
|
-
n.pausedBy ?? null,
|
|
12545
|
-
n.pausedReason ?? null,
|
|
12546
12714
|
n.fields ? JSON.stringify(n.fields) : null,
|
|
12547
12715
|
n.cancelledAt,
|
|
12548
12716
|
n.rowVersion,
|
|
@@ -12602,9 +12770,9 @@ var init_store_postgres = __esm({
|
|
|
12602
12770
|
await this.c.query(
|
|
12603
12771
|
`INSERT INTO ${this.s}.works
|
|
12604
12772
|
(id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
|
|
12605
|
-
outcome_detail,session_ref,continues_work_id,output_version_no,
|
|
12773
|
+
outcome_detail,session_ref,continues_work_id,reply_to_issue_id,output_version_no,
|
|
12606
12774
|
node_version,conclusion,acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason)
|
|
12607
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24)
|
|
12775
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25)
|
|
12608
12776
|
ON CONFLICT (id) DO NOTHING`,
|
|
12609
12777
|
[
|
|
12610
12778
|
w2.id,
|
|
@@ -12622,6 +12790,7 @@ var init_store_postgres = __esm({
|
|
|
12622
12790
|
w2.outcomeDetail ? JSON.stringify(w2.outcomeDetail) : null,
|
|
12623
12791
|
w2.sessionRef,
|
|
12624
12792
|
w2.continuesWorkId,
|
|
12793
|
+
w2.replyToIssueId ?? null,
|
|
12625
12794
|
w2.outputVersionNo,
|
|
12626
12795
|
w2.nodeVersion,
|
|
12627
12796
|
w2.conclusion,
|
|
@@ -12877,7 +13046,7 @@ var init_store_postgres = __esm({
|
|
|
12877
13046
|
}
|
|
12878
13047
|
async listActiveWorkorders() {
|
|
12879
13048
|
const res = await this.c.query(
|
|
12880
|
-
`SELECT id FROM ${this.s}.workorders WHERE
|
|
13049
|
+
`SELECT id FROM ${this.s}.workorders WHERE done_at IS NULL ORDER BY created_at`
|
|
12881
13050
|
);
|
|
12882
13051
|
return res.rows.map((r) => r.id);
|
|
12883
13052
|
}
|
|
@@ -12919,41 +13088,61 @@ var init_types = __esm({
|
|
|
12919
13088
|
});
|
|
12920
13089
|
|
|
12921
13090
|
// ../engine/src/handlers/kill.ts
|
|
13091
|
+
function isWorkKillable(w2) {
|
|
13092
|
+
if (w2.status === "retry") return true;
|
|
13093
|
+
if (w2.status === "dead") return false;
|
|
13094
|
+
if (w2.retryAt && w2.deadAt) return true;
|
|
13095
|
+
return isWorkOpen(w2) || isWorkFailed(w2);
|
|
13096
|
+
}
|
|
13097
|
+
function isReviewKillable(r) {
|
|
13098
|
+
if (r.verdict) return false;
|
|
13099
|
+
if (r.status === "retry") return true;
|
|
13100
|
+
if (r.status === "dead") return false;
|
|
13101
|
+
if (r.retryAt && r.cancelledAt) return true;
|
|
13102
|
+
if (r.cancelledAt) return false;
|
|
13103
|
+
return true;
|
|
13104
|
+
}
|
|
13105
|
+
function killWorkRow(state, w2, at) {
|
|
13106
|
+
state.updateWork(w2.id, {
|
|
13107
|
+
deadAt: w2.deadAt ?? at,
|
|
13108
|
+
cancelledAt: w2.cancelledAt ?? at,
|
|
13109
|
+
status: "dead"
|
|
13110
|
+
});
|
|
13111
|
+
}
|
|
13112
|
+
function killReviewRow(state, r, at) {
|
|
13113
|
+
state.updateReview(r.id, { cancelledAt: r.cancelledAt ?? at, status: "dead" });
|
|
13114
|
+
}
|
|
12922
13115
|
function killLatestWork(state, nodeId, at) {
|
|
12923
13116
|
const node = state.node(nodeId);
|
|
12924
13117
|
if (!node) return;
|
|
12925
13118
|
const lw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
12926
|
-
if (!lw
|
|
12927
|
-
if (
|
|
12928
|
-
state.updateWork(lw.id, { deadAt: at, cancelledAt: at, status: "dead" });
|
|
12929
|
-
} else if (isWorkFailed(lw)) {
|
|
12930
|
-
state.updateWork(lw.id, { deadAt: at, cancelledAt: at, status: "dead" });
|
|
12931
|
-
}
|
|
13119
|
+
if (!lw) return;
|
|
13120
|
+
if (isWorkKillable(lw)) killWorkRow(state, lw, at);
|
|
12932
13121
|
}
|
|
12933
13122
|
function killNodeReviews(state, nodeId, at) {
|
|
12934
13123
|
for (const r of state.allReviews()) {
|
|
12935
|
-
if (r.nodeId === nodeId &&
|
|
12936
|
-
state
|
|
13124
|
+
if (r.nodeId === nodeId && isReviewKillable(r)) {
|
|
13125
|
+
killReviewRow(state, r, at);
|
|
12937
13126
|
}
|
|
12938
13127
|
}
|
|
12939
13128
|
}
|
|
12940
13129
|
function killReview(state, reviewId, at) {
|
|
12941
13130
|
const r = state.review(reviewId);
|
|
12942
|
-
if (!r || r
|
|
12943
|
-
state
|
|
13131
|
+
if (!r || !isReviewKillable(r)) return;
|
|
13132
|
+
killReviewRow(state, r, at);
|
|
12944
13133
|
}
|
|
12945
13134
|
function killAllWorks(state, at) {
|
|
12946
13135
|
for (const w2 of state.allWorks()) {
|
|
12947
|
-
if (!w2.endedAt && !w2.deadAt)
|
|
12948
|
-
|
|
12949
|
-
|
|
13136
|
+
if (!w2.endedAt && !w2.deadAt) killWorkRow(state, w2, at);
|
|
13137
|
+
}
|
|
13138
|
+
for (const n of state.liveNodes()) {
|
|
13139
|
+
const lw = n.latestWorkId ? state.work(n.latestWorkId) : null;
|
|
13140
|
+
if (lw && isWorkKillable(lw)) killWorkRow(state, lw, at);
|
|
12950
13141
|
}
|
|
12951
13142
|
}
|
|
12952
13143
|
function killAllReviews(state, at) {
|
|
12953
13144
|
for (const r of state.allReviews()) {
|
|
12954
|
-
if (
|
|
12955
|
-
state.updateReview(r.id, { cancelledAt: at, status: "dead" });
|
|
12956
|
-
}
|
|
13145
|
+
if (isReviewKillable(r)) killReviewRow(state, r, at);
|
|
12957
13146
|
}
|
|
12958
13147
|
}
|
|
12959
13148
|
var init_kill = __esm({
|
|
@@ -12963,101 +13152,6 @@ var init_kill = __esm({
|
|
|
12963
13152
|
}
|
|
12964
13153
|
});
|
|
12965
13154
|
|
|
12966
|
-
// ../engine/src/handlers/workorder.ts
|
|
12967
|
-
var workorderCreate, workorderMeta, workorderRoot, workorderSeal, workorderPause, workorderResume, workorderPing;
|
|
12968
|
-
var init_workorder = __esm({
|
|
12969
|
-
"../engine/src/handlers/workorder.ts"() {
|
|
12970
|
-
"use strict";
|
|
12971
|
-
init_kill();
|
|
12972
|
-
workorderCreate = {
|
|
12973
|
-
name: "workorder/create",
|
|
12974
|
-
kind: "workorder.created",
|
|
12975
|
-
apply(e, state, ctx) {
|
|
12976
|
-
if (state.workorder.id === e.workorderId && state.workorder.createdAt) return;
|
|
12977
|
-
state.insertWorkorder({
|
|
12978
|
-
id: e.workorderId,
|
|
12979
|
-
companyId: state.workorder.companyId,
|
|
12980
|
-
projectId: e.projectId,
|
|
12981
|
-
title: e.title,
|
|
12982
|
-
description: e.description,
|
|
12983
|
-
goal: e.goal,
|
|
12984
|
-
acceptanceCriteria: e.acceptanceCriteria,
|
|
12985
|
-
ownerActorId: e.ownerActorId || ctx.actorId || "actor:system:engine",
|
|
12986
|
-
managerActorId: e.managerActorId,
|
|
12987
|
-
rootNodeId: null,
|
|
12988
|
-
lifecycle: "draft",
|
|
12989
|
-
sealReason: null,
|
|
12990
|
-
dispatchPaused: false,
|
|
12991
|
-
pausedBy: null,
|
|
12992
|
-
pausedReason: null,
|
|
12993
|
-
resumeMarker: 0,
|
|
12994
|
-
structuralVersion: 0,
|
|
12995
|
-
rowVersion: 0,
|
|
12996
|
-
createdAt: ctx.at,
|
|
12997
|
-
createdBy: ctx.actorId,
|
|
12998
|
-
updatedAt: ctx.at
|
|
12999
|
-
});
|
|
13000
|
-
}
|
|
13001
|
-
};
|
|
13002
|
-
workorderMeta = {
|
|
13003
|
-
name: "workorder/meta",
|
|
13004
|
-
kind: "workorder.meta_changed",
|
|
13005
|
-
apply(e, state, ctx) {
|
|
13006
|
-
state.updateWorkorder({ ...e.patch, updatedAt: ctx.at, rowVersion: state.workorder.rowVersion + 1 });
|
|
13007
|
-
}
|
|
13008
|
-
};
|
|
13009
|
-
workorderRoot = {
|
|
13010
|
-
name: "workorder/root",
|
|
13011
|
-
kind: "workorder.root_set",
|
|
13012
|
-
apply(e, state, ctx) {
|
|
13013
|
-
if (!state.node(e.nodeId)) throw new Error(`root node ${e.nodeId} not in workorder ${e.workorderId}`);
|
|
13014
|
-
state.updateWorkorder({ rootNodeId: e.nodeId, updatedAt: ctx.at });
|
|
13015
|
-
}
|
|
13016
|
-
};
|
|
13017
|
-
workorderSeal = {
|
|
13018
|
-
name: "workorder/seal",
|
|
13019
|
-
kind: "workorder.sealed",
|
|
13020
|
-
apply(e, state, ctx) {
|
|
13021
|
-
if (state.workorder.lifecycle === "sealed") return;
|
|
13022
|
-
state.updateWorkorder({ lifecycle: "sealed", sealReason: e.reason, updatedAt: ctx.at });
|
|
13023
|
-
killAllWorks(state, ctx.at);
|
|
13024
|
-
killAllReviews(state, ctx.at);
|
|
13025
|
-
},
|
|
13026
|
-
// ★ 设计 §workorder.sealed「按 work.kill 逻辑杀」:apply 只置状态,这里取消被杀会话
|
|
13027
|
-
async effect(e, ctx) {
|
|
13028
|
-
await ctx.cancelKilledSessions(e.workorderId);
|
|
13029
|
-
}
|
|
13030
|
-
};
|
|
13031
|
-
workorderPause = {
|
|
13032
|
-
name: "workorder/pause",
|
|
13033
|
-
kind: "workorder.paused",
|
|
13034
|
-
apply(e, state, ctx) {
|
|
13035
|
-
state.updateWorkorder({ dispatchPaused: true, pausedBy: e.by, pausedReason: e.reason, updatedAt: ctx.at });
|
|
13036
|
-
killAllWorks(state, ctx.at);
|
|
13037
|
-
killAllReviews(state, ctx.at);
|
|
13038
|
-
},
|
|
13039
|
-
// ★ 设计 §workorder.paused「按 work.kill 逻辑杀」:apply 只置状态,这里取消被杀会话
|
|
13040
|
-
async effect(e, ctx) {
|
|
13041
|
-
await ctx.cancelKilledSessions(e.workorderId);
|
|
13042
|
-
}
|
|
13043
|
-
};
|
|
13044
|
-
workorderResume = {
|
|
13045
|
-
name: "workorder/resume",
|
|
13046
|
-
kind: "workorder.resumed",
|
|
13047
|
-
apply(e, state, ctx) {
|
|
13048
|
-
const newMarker = state.workorder.resumeMarker + 1;
|
|
13049
|
-
state.updateWorkorder({ dispatchPaused: false, pausedBy: null, pausedReason: null, resumeMarker: newMarker, updatedAt: ctx.at });
|
|
13050
|
-
}
|
|
13051
|
-
};
|
|
13052
|
-
workorderPing = {
|
|
13053
|
-
name: "workorder/ping",
|
|
13054
|
-
kind: "workorder.ping",
|
|
13055
|
-
apply(_e, _state, _ctx) {
|
|
13056
|
-
}
|
|
13057
|
-
};
|
|
13058
|
-
}
|
|
13059
|
-
});
|
|
13060
|
-
|
|
13061
13155
|
// ../engine/src/handlers/plan.ts
|
|
13062
13156
|
function newNode(id, workorderId, at, version2) {
|
|
13063
13157
|
return {
|
|
@@ -13071,11 +13165,6 @@ function newNode(id, workorderId, at, version2) {
|
|
|
13071
13165
|
assigneeRole: null,
|
|
13072
13166
|
latestWorkId: null,
|
|
13073
13167
|
latestAcceptId: null,
|
|
13074
|
-
lastResumeMarker: 0,
|
|
13075
|
-
retryCount: 0,
|
|
13076
|
-
paused: false,
|
|
13077
|
-
pausedBy: null,
|
|
13078
|
-
pausedReason: null,
|
|
13079
13168
|
isFinalOutput: false,
|
|
13080
13169
|
cancelledAt: null,
|
|
13081
13170
|
rowVersion: 0,
|
|
@@ -13083,6 +13172,31 @@ function newNode(id, workorderId, at, version2) {
|
|
|
13083
13172
|
updatedAt: at
|
|
13084
13173
|
};
|
|
13085
13174
|
}
|
|
13175
|
+
function markNodeForRetry(state, nodeId, at) {
|
|
13176
|
+
const node = state.node(nodeId);
|
|
13177
|
+
if (!node) return;
|
|
13178
|
+
const lw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
13179
|
+
if (lw && lw.deadAt && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "dead") {
|
|
13180
|
+
state.updateWork(lw.id, { retryAt: at, status: "retry" });
|
|
13181
|
+
}
|
|
13182
|
+
if (lw && !lw.deadAt && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success") {
|
|
13183
|
+
for (const req of state.requirementsOf(node.id)) {
|
|
13184
|
+
if (!req.latestReviewId) continue;
|
|
13185
|
+
const r = state.review(req.latestReviewId);
|
|
13186
|
+
if (r && r.cancelledAt && reviewState(r) === "dead") {
|
|
13187
|
+
state.updateReview(r.id, { retryAt: at, status: "retry" });
|
|
13188
|
+
}
|
|
13189
|
+
}
|
|
13190
|
+
}
|
|
13191
|
+
for (const issue2 of state.allIssues()) {
|
|
13192
|
+
if (issue2.kind !== "comment" || issue2.state !== "open" || issue2.aboutNodeId !== nodeId) continue;
|
|
13193
|
+
const replies = state.worksOf(nodeId).filter((w2) => w2.replyToIssueId === issue2.id).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
13194
|
+
const latest = replies[replies.length - 1];
|
|
13195
|
+
if (latest && latest.deadAt && workState(latest, hasOutput(latest, 0)) === "dead") {
|
|
13196
|
+
state.updateWork(latest.id, { retryAt: at, status: "retry" });
|
|
13197
|
+
}
|
|
13198
|
+
}
|
|
13199
|
+
}
|
|
13086
13200
|
var planUpdateSpec, planAddNode, planDeleteNode, planAddEdge, planDeleteEdge, planAssignActor, planUpdateReviewRequirements, planNodeRetry, planUpdateFields, planApply;
|
|
13087
13201
|
var init_plan = __esm({
|
|
13088
13202
|
"../engine/src/handlers/plan.ts"() {
|
|
@@ -13184,8 +13298,17 @@ var init_plan = __esm({
|
|
|
13184
13298
|
name: "plan/assign-actor",
|
|
13185
13299
|
kind: "plan.assign_actor",
|
|
13186
13300
|
apply(e, state, ctx) {
|
|
13187
|
-
|
|
13188
|
-
|
|
13301
|
+
const node = state.node(e.nodeId);
|
|
13302
|
+
if (!node) return;
|
|
13303
|
+
const changed = node.assigneeActorId !== e.assigneeActorId;
|
|
13304
|
+
const lw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
13305
|
+
const settled = lw !== null && lw !== void 0 && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success";
|
|
13306
|
+
state.updateNode(e.nodeId, {
|
|
13307
|
+
assigneeActorId: e.assigneeActorId,
|
|
13308
|
+
assigneeRole: e.assigneeRole,
|
|
13309
|
+
...changed && !settled ? { version: node.version + 1 } : {},
|
|
13310
|
+
updatedAt: ctx.at
|
|
13311
|
+
});
|
|
13189
13312
|
}
|
|
13190
13313
|
};
|
|
13191
13314
|
planUpdateReviewRequirements = {
|
|
@@ -13209,21 +13332,7 @@ var init_plan = __esm({
|
|
|
13209
13332
|
name: "plan/node-retry",
|
|
13210
13333
|
kind: "plan.node_retry",
|
|
13211
13334
|
apply(e, state, ctx) {
|
|
13212
|
-
|
|
13213
|
-
if (!node) return;
|
|
13214
|
-
const lw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
13215
|
-
if (lw && lw.deadAt && !lw.retryAt) {
|
|
13216
|
-
state.updateWork(lw.id, { retryAt: ctx.at, status: "retry" });
|
|
13217
|
-
}
|
|
13218
|
-
if (lw && !lw.deadAt && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success") {
|
|
13219
|
-
for (const req of state.requirementsOf(node.id)) {
|
|
13220
|
-
if (!req.latestReviewId) continue;
|
|
13221
|
-
const r = state.review(req.latestReviewId);
|
|
13222
|
-
if (r && r.cancelledAt && !r.retryAt) {
|
|
13223
|
-
state.updateReview(r.id, { retryAt: ctx.at, status: "retry" });
|
|
13224
|
-
}
|
|
13225
|
-
}
|
|
13226
|
-
}
|
|
13335
|
+
markNodeForRetry(state, e.nodeId, ctx.at);
|
|
13227
13336
|
}
|
|
13228
13337
|
};
|
|
13229
13338
|
planUpdateFields = {
|
|
@@ -13296,6 +13405,89 @@ var init_plan = __esm({
|
|
|
13296
13405
|
}
|
|
13297
13406
|
});
|
|
13298
13407
|
|
|
13408
|
+
// ../engine/src/handlers/workorder.ts
|
|
13409
|
+
var workorderCreate, workorderMeta, workorderRoot, workorderPause, workorderResume, workorderPing;
|
|
13410
|
+
var init_workorder = __esm({
|
|
13411
|
+
"../engine/src/handlers/workorder.ts"() {
|
|
13412
|
+
"use strict";
|
|
13413
|
+
init_kill();
|
|
13414
|
+
init_plan();
|
|
13415
|
+
workorderCreate = {
|
|
13416
|
+
name: "workorder/create",
|
|
13417
|
+
kind: "workorder.created",
|
|
13418
|
+
apply(e, state, ctx) {
|
|
13419
|
+
if (state.workorder.id === e.workorderId && state.workorder.createdAt) return;
|
|
13420
|
+
state.insertWorkorder({
|
|
13421
|
+
id: e.workorderId,
|
|
13422
|
+
companyId: state.workorder.companyId,
|
|
13423
|
+
projectId: e.projectId,
|
|
13424
|
+
title: e.title,
|
|
13425
|
+
description: e.description,
|
|
13426
|
+
goal: e.goal,
|
|
13427
|
+
acceptanceCriteria: e.acceptanceCriteria,
|
|
13428
|
+
ownerActorId: e.ownerActorId || ctx.actorId || "actor:system:engine",
|
|
13429
|
+
managerActorId: e.managerActorId,
|
|
13430
|
+
rootNodeId: null,
|
|
13431
|
+
lifecycle: "draft",
|
|
13432
|
+
dispatchPaused: false,
|
|
13433
|
+
pausedBy: null,
|
|
13434
|
+
pausedReason: null,
|
|
13435
|
+
doneAt: null,
|
|
13436
|
+
structuralVersion: 0,
|
|
13437
|
+
rowVersion: 0,
|
|
13438
|
+
createdAt: ctx.at,
|
|
13439
|
+
createdBy: ctx.actorId,
|
|
13440
|
+
updatedAt: ctx.at
|
|
13441
|
+
});
|
|
13442
|
+
}
|
|
13443
|
+
};
|
|
13444
|
+
workorderMeta = {
|
|
13445
|
+
name: "workorder/meta",
|
|
13446
|
+
kind: "workorder.meta_changed",
|
|
13447
|
+
apply(e, state, ctx) {
|
|
13448
|
+
state.updateWorkorder({ ...e.patch, updatedAt: ctx.at, rowVersion: state.workorder.rowVersion + 1 });
|
|
13449
|
+
}
|
|
13450
|
+
};
|
|
13451
|
+
workorderRoot = {
|
|
13452
|
+
name: "workorder/root",
|
|
13453
|
+
kind: "workorder.root_set",
|
|
13454
|
+
apply(e, state, ctx) {
|
|
13455
|
+
if (!state.node(e.nodeId)) throw new Error(`root node ${e.nodeId} not in workorder ${e.workorderId}`);
|
|
13456
|
+
state.updateWorkorder({ rootNodeId: e.nodeId, updatedAt: ctx.at });
|
|
13457
|
+
}
|
|
13458
|
+
};
|
|
13459
|
+
workorderPause = {
|
|
13460
|
+
name: "workorder/pause",
|
|
13461
|
+
kind: "workorder.paused",
|
|
13462
|
+
apply(e, state, ctx) {
|
|
13463
|
+
state.updateWorkorder({ dispatchPaused: true, pausedBy: e.by, pausedReason: e.reason, updatedAt: ctx.at });
|
|
13464
|
+
killAllWorks(state, ctx.at);
|
|
13465
|
+
killAllReviews(state, ctx.at);
|
|
13466
|
+
},
|
|
13467
|
+
// ★ 设计 §workorder.paused「按 work.kill 逻辑杀」:apply 只置状态,这里取消被杀会话
|
|
13468
|
+
async effect(e, ctx) {
|
|
13469
|
+
await ctx.cancelKilledSessions(e.workorderId);
|
|
13470
|
+
}
|
|
13471
|
+
};
|
|
13472
|
+
workorderResume = {
|
|
13473
|
+
name: "workorder/resume",
|
|
13474
|
+
kind: "workorder.resumed",
|
|
13475
|
+
apply(e, state, ctx) {
|
|
13476
|
+
state.updateWorkorder({ dispatchPaused: false, pausedBy: null, pausedReason: null, updatedAt: ctx.at });
|
|
13477
|
+
for (const node of state.liveNodes()) {
|
|
13478
|
+
planNodeRetry.apply({ kind: "plan.node_retry", workorderId: e.workorderId, nodeId: node.id, by: e.by }, state, ctx);
|
|
13479
|
+
}
|
|
13480
|
+
}
|
|
13481
|
+
};
|
|
13482
|
+
workorderPing = {
|
|
13483
|
+
name: "workorder/ping",
|
|
13484
|
+
kind: "workorder.ping",
|
|
13485
|
+
apply(_e, _state, _ctx) {
|
|
13486
|
+
}
|
|
13487
|
+
};
|
|
13488
|
+
}
|
|
13489
|
+
});
|
|
13490
|
+
|
|
13299
13491
|
// ../engine/src/handlers/work.ts
|
|
13300
13492
|
var workCreate, workKill, workResponse, workStart, workSubmitOutput, workTimeout;
|
|
13301
13493
|
var init_work = __esm({
|
|
@@ -13310,15 +13502,41 @@ var init_work = __esm({
|
|
|
13310
13502
|
if (state.work(e.workId)) return;
|
|
13311
13503
|
const node = state.node(e.nodeId);
|
|
13312
13504
|
if (!node) return;
|
|
13313
|
-
|
|
13314
|
-
|
|
13315
|
-
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13505
|
+
if (e.replyToIssueId) {
|
|
13506
|
+
if (state.worksOf(e.nodeId).some((w2) => !w2.endedAt && !w2.deadAt)) return;
|
|
13507
|
+
state.insertWork({
|
|
13508
|
+
id: e.workId,
|
|
13509
|
+
workorderId: e.workorderId,
|
|
13510
|
+
nodeId: e.nodeId,
|
|
13511
|
+
assigneeActorId: node.assigneeActorId,
|
|
13512
|
+
createdAt: ctx.at,
|
|
13513
|
+
startedAt: null,
|
|
13514
|
+
endedAt: null,
|
|
13515
|
+
deadAt: null,
|
|
13516
|
+
cancelledAt: null,
|
|
13517
|
+
status: "running",
|
|
13518
|
+
outcome: null,
|
|
13519
|
+
outcomeDetail: null,
|
|
13520
|
+
sessionRef: null,
|
|
13521
|
+
continuesWorkId: e.continuesWorkId,
|
|
13522
|
+
replyToIssueId: e.replyToIssueId,
|
|
13523
|
+
outputVersionNo: null,
|
|
13524
|
+
conclusion: null,
|
|
13525
|
+
acceptanceState: null,
|
|
13526
|
+
acceptedAt: null,
|
|
13527
|
+
acceptedBy: null,
|
|
13528
|
+
rejectedReason: null,
|
|
13529
|
+
overrideBy: null,
|
|
13530
|
+
overrideReason: null,
|
|
13531
|
+
nodeVersion: node.version
|
|
13532
|
+
});
|
|
13533
|
+
return;
|
|
13320
13534
|
}
|
|
13535
|
+
killLatestWork(state, e.nodeId, ctx.at);
|
|
13321
13536
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
13537
|
+
for (const w2 of state.worksOf(e.nodeId)) {
|
|
13538
|
+
if (w2.replyToIssueId && !w2.endedAt && !w2.deadAt) killWorkRow(state, w2, ctx.at);
|
|
13539
|
+
}
|
|
13322
13540
|
state.insertWork({
|
|
13323
13541
|
id: e.workId,
|
|
13324
13542
|
workorderId: e.workorderId,
|
|
@@ -13344,10 +13562,7 @@ var init_work = __esm({
|
|
|
13344
13562
|
overrideReason: null,
|
|
13345
13563
|
nodeVersion: node.version
|
|
13346
13564
|
});
|
|
13347
|
-
state.updateNode(e.nodeId, {
|
|
13348
|
-
latestWorkId: e.workId,
|
|
13349
|
-
lastResumeMarker: state.workorder.resumeMarker
|
|
13350
|
-
});
|
|
13565
|
+
state.updateNode(e.nodeId, { latestWorkId: e.workId });
|
|
13351
13566
|
for (const req of state.requirementsOf(e.nodeId)) {
|
|
13352
13567
|
if (req.latestReviewId) {
|
|
13353
13568
|
state.replaceRequirements(e.nodeId, state.requirementsOf(e.nodeId).map((r) => ({
|
|
@@ -13368,8 +13583,9 @@ var init_work = __esm({
|
|
|
13368
13583
|
},
|
|
13369
13584
|
// ★ 设计 §work.create step 1「kill 逻辑与 work.kill 完全一致」——apply 置旧 work deadAt 后,
|
|
13370
13585
|
// 会话侧也要杀(旧 agent 可能还挂着)。cancelPreviousWork 找不到/会话已退则 no-op。
|
|
13586
|
+
// 回信 work 不杀前会话(apply 不 kill 主链,节点已完结、无在跑会话),只派发。
|
|
13371
13587
|
async effect(e, ctx) {
|
|
13372
|
-
await ctx.cancelPreviousWork(e.nodeId, e.workId);
|
|
13588
|
+
if (!e.replyToIssueId) await ctx.cancelPreviousWork(e.nodeId, e.workId);
|
|
13373
13589
|
await ctx.dispatchWork(e.workId);
|
|
13374
13590
|
}
|
|
13375
13591
|
};
|
|
@@ -13381,14 +13597,12 @@ var init_work = __esm({
|
|
|
13381
13597
|
if (!node) return;
|
|
13382
13598
|
const lw = node.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
13383
13599
|
const target = e.workId ? state.work(e.workId) : null;
|
|
13384
|
-
const targetKillable = target !== null && target !== void 0 && (
|
|
13385
|
-
if (lw
|
|
13386
|
-
if (
|
|
13387
|
-
state
|
|
13388
|
-
} else if (
|
|
13389
|
-
state
|
|
13390
|
-
} else if (node.latestAcceptId !== lw.id) {
|
|
13391
|
-
state.updateWork(lw.id, { deadAt: ctx.at, cancelledAt: ctx.at, status: "dead" });
|
|
13600
|
+
const targetKillable = target !== null && target !== void 0 && (isWorkKillable(target) || target.endedAt !== null && target.endedAt !== void 0 && !target.deadAt && state.node(target.nodeId)?.latestAcceptId !== target.id);
|
|
13601
|
+
if (lw) {
|
|
13602
|
+
if (isWorkKillable(lw)) {
|
|
13603
|
+
killWorkRow(state, lw, ctx.at);
|
|
13604
|
+
} else if (!lw.deadAt && lw.endedAt && node.latestAcceptId !== lw.id) {
|
|
13605
|
+
killWorkRow(state, lw, ctx.at);
|
|
13392
13606
|
}
|
|
13393
13607
|
}
|
|
13394
13608
|
if (targetKillable) {
|
|
@@ -13410,6 +13624,19 @@ var init_work = __esm({
|
|
|
13410
13624
|
apply(e, state, ctx) {
|
|
13411
13625
|
const w2 = state.work(e.workId);
|
|
13412
13626
|
if (!w2 || w2.endedAt || w2.deadAt) return;
|
|
13627
|
+
if (w2.replyToIssueId) {
|
|
13628
|
+
const myReplies = state.repliesOf(w2.replyToIssueId).filter((r) => r.authorActorId === w2.assigneeActorId && r.createdAt >= w2.createdAt).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
13629
|
+
const replied = myReplies.length > 0;
|
|
13630
|
+
const ok = e.outcome === "completed" && (replied || hasOutput(w2, 0));
|
|
13631
|
+
state.updateWork(e.workId, {
|
|
13632
|
+
endedAt: ctx.at,
|
|
13633
|
+
outcome: e.outcome,
|
|
13634
|
+
outcomeDetail: e.detail,
|
|
13635
|
+
status: ok ? "success" : "failed",
|
|
13636
|
+
...ok && !w2.conclusion && replied ? { conclusion: myReplies[myReplies.length - 1].body } : {}
|
|
13637
|
+
});
|
|
13638
|
+
return;
|
|
13639
|
+
}
|
|
13413
13640
|
const status = e.outcome === "completed" && hasOutput(w2, state.artifactsOf(e.workId).length) ? "success" : "failed";
|
|
13414
13641
|
state.updateWork(e.workId, { endedAt: ctx.at, outcome: e.outcome, outcomeDetail: e.detail, status });
|
|
13415
13642
|
}
|
|
@@ -13432,6 +13659,9 @@ var init_work = __esm({
|
|
|
13432
13659
|
apply(e, state, ctx) {
|
|
13433
13660
|
const w2 = state.work(e.workId);
|
|
13434
13661
|
if (!w2 || w2.deadAt) return;
|
|
13662
|
+
if (e.artifacts.length === 0 && w2.acceptanceState === "accepted" && state.artifactsOf(e.workId).length > 0) {
|
|
13663
|
+
return;
|
|
13664
|
+
}
|
|
13435
13665
|
state.updateWork(e.workId, { conclusion: e.conclusion });
|
|
13436
13666
|
state.replaceArtifacts(e.workId, e.artifacts.map((a) => ({
|
|
13437
13667
|
id: a.id,
|
|
@@ -13476,8 +13706,7 @@ var issueCreate, issueReply, issueResolve, issueProposeResolution, issueRejectRe
|
|
|
13476
13706
|
var init_issue = __esm({
|
|
13477
13707
|
"../engine/src/handlers/issue.ts"() {
|
|
13478
13708
|
"use strict";
|
|
13479
|
-
|
|
13480
|
-
init_kill();
|
|
13709
|
+
init_plan();
|
|
13481
13710
|
issueCreate = {
|
|
13482
13711
|
name: "issue/create",
|
|
13483
13712
|
kind: "issue.create",
|
|
@@ -13507,15 +13736,6 @@ var init_issue = __esm({
|
|
|
13507
13736
|
});
|
|
13508
13737
|
for (const actorId of e.recipients) state.addRecipient({ issueId: e.issueId, actorId });
|
|
13509
13738
|
for (const a of e.attachments) state.addAttachment({ issueId: e.issueId, ...a });
|
|
13510
|
-
const targetNodeId = e.aboutNodeId ?? (e.aboutWorkId ? state.work(e.aboutWorkId)?.nodeId : null);
|
|
13511
|
-
if (blocking && targetNodeId) {
|
|
13512
|
-
const node = state.node(targetNodeId);
|
|
13513
|
-
const lw = node?.latestWorkId ? state.work(node.latestWorkId) : null;
|
|
13514
|
-
if (lw && (isWorkOpen(lw) || isWorkFailed(lw))) {
|
|
13515
|
-
killLatestWork(state, targetNodeId, ctx.at);
|
|
13516
|
-
killNodeReviews(state, targetNodeId, ctx.at);
|
|
13517
|
-
}
|
|
13518
|
-
}
|
|
13519
13739
|
},
|
|
13520
13740
|
async effect(e, ctx) {
|
|
13521
13741
|
if (e.recipients.length) {
|
|
@@ -13571,6 +13791,10 @@ var init_issue = __esm({
|
|
|
13571
13791
|
resolvedNote: e.note,
|
|
13572
13792
|
resolvedViaWorkId: e.viaWorkId
|
|
13573
13793
|
});
|
|
13794
|
+
if (issue2.kind === "gap" || issue2.kind === "escalation") {
|
|
13795
|
+
const nodeId = issue2.raisedByNodeId ?? issue2.aboutNodeId;
|
|
13796
|
+
if (nodeId) markNodeForRetry(state, nodeId, ctx.at);
|
|
13797
|
+
}
|
|
13574
13798
|
}
|
|
13575
13799
|
};
|
|
13576
13800
|
issueProposeResolution = {
|
|
@@ -13613,12 +13837,7 @@ var init_review = __esm({
|
|
|
13613
13837
|
if (dup) return;
|
|
13614
13838
|
const reqs = state.requirementsOf(e.nodeId);
|
|
13615
13839
|
const lrId = reqs.find((r) => r.reviewGroup === e.reviewGroup)?.latestReviewId;
|
|
13616
|
-
if (lrId)
|
|
13617
|
-
const lr = state.review(lrId);
|
|
13618
|
-
if (lr && !lr.verdict && !lr.cancelledAt && (isReviewOpen(lr) || lr.endedAt)) {
|
|
13619
|
-
state.updateReview(lrId, { cancelledAt: ctx.at });
|
|
13620
|
-
}
|
|
13621
|
-
}
|
|
13840
|
+
if (lrId) killReview(state, lrId, ctx.at);
|
|
13622
13841
|
state.insertReview({
|
|
13623
13842
|
id: e.reviewId,
|
|
13624
13843
|
workorderId: e.workorderId,
|
|
@@ -13638,6 +13857,11 @@ var init_review = __esm({
|
|
|
13638
13857
|
decidedAt: null
|
|
13639
13858
|
});
|
|
13640
13859
|
state.replaceRequirements(e.nodeId, reqs.map((r) => r.reviewGroup === e.reviewGroup ? { ...r, latestReviewId: e.reviewId } : r));
|
|
13860
|
+
const node = state.node(e.nodeId);
|
|
13861
|
+
if (node && node.latestAcceptId === e.targetWorkId) {
|
|
13862
|
+
state.updateNode(e.nodeId, { latestAcceptId: null, updatedAt: ctx.at });
|
|
13863
|
+
state.updateWork(e.targetWorkId, { acceptanceState: null, acceptedAt: null, acceptedBy: null });
|
|
13864
|
+
}
|
|
13641
13865
|
},
|
|
13642
13866
|
async effect(e, ctx) {
|
|
13643
13867
|
if (e.reviewerIsAgent) await ctx.dispatchReview(e.reviewId, e.nodeId, e.reviewerActorId);
|
|
@@ -13739,6 +13963,48 @@ var init_review = __esm({
|
|
|
13739
13963
|
if (!w2 || w2.deadAt || w2.acceptanceState === "accepted") return;
|
|
13740
13964
|
const node = state.node(w2.nodeId);
|
|
13741
13965
|
if (!node) return;
|
|
13966
|
+
if (w2.replyToIssueId) {
|
|
13967
|
+
const issue2 = state.issue(w2.replyToIssueId);
|
|
13968
|
+
state.updateWork(e.workId, {
|
|
13969
|
+
acceptanceState: "accepted",
|
|
13970
|
+
acceptedAt: ctx.at,
|
|
13971
|
+
acceptedBy: e.by,
|
|
13972
|
+
overrideBy: e.override?.by ?? null,
|
|
13973
|
+
overrideReason: e.override?.reason ?? null,
|
|
13974
|
+
...w2.endedAt ? {} : { endedAt: ctx.at, outcome: "completed" }
|
|
13975
|
+
});
|
|
13976
|
+
if (issue2) {
|
|
13977
|
+
const already = state.repliesOf(issue2.id).some(
|
|
13978
|
+
(r) => r.authorActorId === w2.assigneeActorId && r.createdAt >= w2.createdAt
|
|
13979
|
+
);
|
|
13980
|
+
if (!already && w2.conclusion) {
|
|
13981
|
+
const replyId = deriveReplyId(issue2.id, e.workId);
|
|
13982
|
+
if (!state.repliesOf(issue2.id).some((r) => r.id === replyId)) {
|
|
13983
|
+
state.addReply({
|
|
13984
|
+
id: replyId,
|
|
13985
|
+
issueId: issue2.id,
|
|
13986
|
+
authorActorId: w2.assigneeActorId,
|
|
13987
|
+
handActorId: null,
|
|
13988
|
+
body: w2.conclusion,
|
|
13989
|
+
createdAt: ctx.at,
|
|
13990
|
+
mentions: null
|
|
13991
|
+
});
|
|
13992
|
+
}
|
|
13993
|
+
}
|
|
13994
|
+
if (issue2.state === "open") {
|
|
13995
|
+
issueResolve.apply({
|
|
13996
|
+
kind: "issue.resolve",
|
|
13997
|
+
workorderId: state.workorder.id,
|
|
13998
|
+
issueId: issue2.id,
|
|
13999
|
+
state: "resolved",
|
|
14000
|
+
by: e.by,
|
|
14001
|
+
note: null,
|
|
14002
|
+
viaWorkId: e.workId
|
|
14003
|
+
}, state, ctx);
|
|
14004
|
+
}
|
|
14005
|
+
}
|
|
14006
|
+
return;
|
|
14007
|
+
}
|
|
13742
14008
|
state.updateNode(w2.nodeId, { latestAcceptId: e.workId, updatedAt: ctx.at });
|
|
13743
14009
|
state.updateWork(e.workId, {
|
|
13744
14010
|
acceptanceState: "accepted",
|
|
@@ -13755,7 +14021,7 @@ var init_review = __esm({
|
|
|
13755
14021
|
state.updateWork(e.workId, { outputVersionNo: Math.max(0, ...versions) + 1 });
|
|
13756
14022
|
}
|
|
13757
14023
|
for (const r of state.openReviewsOf(e.workId)) {
|
|
13758
|
-
state.updateReview(r.id, { cancelledAt: ctx.at });
|
|
14024
|
+
state.updateReview(r.id, { cancelledAt: ctx.at, status: "dead" });
|
|
13759
14025
|
}
|
|
13760
14026
|
const inputs = state.workInputsOf(e.workId);
|
|
13761
14027
|
for (const edge of state.edgesInto(w2.nodeId)) {
|
|
@@ -13800,11 +14066,10 @@ var init_handlers = __esm({
|
|
|
13800
14066
|
init_issue();
|
|
13801
14067
|
init_types();
|
|
13802
14068
|
HANDLERS = [
|
|
13803
|
-
/* ── 工作区级(
|
|
14069
|
+
/* ── 工作区级(4)── */
|
|
13804
14070
|
erase(workorderCreate),
|
|
13805
14071
|
erase(workorderMeta),
|
|
13806
14072
|
erase(workorderRoot),
|
|
13807
|
-
erase(workorderSeal),
|
|
13808
14073
|
erase(workorderPause),
|
|
13809
14074
|
erase(workorderResume),
|
|
13810
14075
|
erase(workorderPing),
|
|
@@ -13859,11 +14124,10 @@ function emptySnapshotPlaceholder(record8) {
|
|
|
13859
14124
|
managerActorId: null,
|
|
13860
14125
|
rootNodeId: null,
|
|
13861
14126
|
lifecycle: "draft",
|
|
13862
|
-
sealReason: null,
|
|
13863
14127
|
dispatchPaused: false,
|
|
13864
14128
|
pausedBy: null,
|
|
13865
14129
|
pausedReason: null,
|
|
13866
|
-
|
|
14130
|
+
doneAt: null,
|
|
13867
14131
|
structuralVersion: 0,
|
|
13868
14132
|
rowVersion: 0,
|
|
13869
14133
|
createdAt: "",
|
|
@@ -13892,7 +14156,6 @@ var init_bus = __esm({
|
|
|
13892
14156
|
init_model();
|
|
13893
14157
|
init_handlers();
|
|
13894
14158
|
init_state();
|
|
13895
|
-
init_views2();
|
|
13896
14159
|
SYSTEM_ACTOR2 = "actor:system:engine";
|
|
13897
14160
|
NOOP_IO = {
|
|
13898
14161
|
async dispatchWork() {
|
|
@@ -14003,13 +14266,13 @@ var init_bus = __esm({
|
|
|
14003
14266
|
* batch = event_queue.drain() ← 一次取空全部 pending
|
|
14004
14267
|
* touched = set()
|
|
14005
14268
|
* for e in batch:
|
|
14006
|
-
* unmark_done(e.workorder_id) ← 事件到达 →
|
|
14269
|
+
* unmark_done(e.workorder_id) ← 事件到达 → 唤醒工单(清 done_at,重新纳入扫描集合)
|
|
14007
14270
|
* touched.add(e.workorder_id)
|
|
14008
14271
|
* handle(e) ← apply + effect
|
|
14009
14272
|
* for wo in touched:
|
|
14010
14273
|
* terminated = scan(wo) ← 全图遍历,emit 级联事件
|
|
14011
14274
|
* if terminated:
|
|
14012
|
-
* mark_done(wo) ←
|
|
14275
|
+
* mark_done(wo) ← 调度器记账(done_at),不是封存——sealed 仅人显式触发
|
|
14013
14276
|
* ```
|
|
14014
14277
|
*
|
|
14015
14278
|
* scan **只在循环内调用**——外部需要触发 scan 时发 workorder.ping 事件。
|
|
@@ -14054,8 +14317,8 @@ var init_bus = __esm({
|
|
|
14054
14317
|
try {
|
|
14055
14318
|
const result = await this.scan(woId);
|
|
14056
14319
|
if (result.terminated) {
|
|
14057
|
-
await this.markDone(woId);
|
|
14058
|
-
console.log(`[bus] \u2705 \u5DE5\u5355 ${woId} \u5DF2\u5B8C\u7ED3\uFF0C\
|
|
14320
|
+
const transitioned = await this.markDone(woId);
|
|
14321
|
+
if (transitioned) console.log(`[bus] \u2705 \u5DE5\u5355 ${woId} \u5DF2\u5B8C\u7ED3\uFF08mark done\uFF0C\u7B49\u5F85\u65B0\u4E8B\u4EF6\u5524\u9192\uFF1B\u4E0D\u5C01\u5B58\uFF09`);
|
|
14059
14322
|
}
|
|
14060
14323
|
} catch (err) {
|
|
14061
14324
|
console.error(`[bus] scan ${woId}:`, err);
|
|
@@ -14084,28 +14347,34 @@ var init_bus = __esm({
|
|
|
14084
14347
|
}
|
|
14085
14348
|
/* ═══════════════════════ mark_done / unmark_done ═══════════════════════ */
|
|
14086
14349
|
/**
|
|
14087
|
-
* 调度器基础设施:标记工单为"已完结"
|
|
14088
|
-
* ★ 不是业务事件,不走 handler
|
|
14350
|
+
* 调度器基础设施:标记工单为"已完结"——写 done_at(调度器记账字段),把工单移出空闲全扫集合。
|
|
14351
|
+
* ★ 不是业务事件,不走 handler。done_at 可逆:任何新事件经 unmarkDone 唤醒(工单级封存已移除,
|
|
14352
|
+
* ADR 0137——曾经的实现把这里写成 lifecycle=sealed:converged,「跑完即封存」是设计外行为)。
|
|
14353
|
+
* @returns 是否发生了状态迁移(已 done 的工单重复 markDone 返回 false,调用方据此避免重复刷日志)。
|
|
14089
14354
|
*/
|
|
14090
14355
|
async markDone(workorderId) {
|
|
14091
|
-
|
|
14356
|
+
return this.store.transaction(async (tx) => {
|
|
14357
|
+
const snap = await tx.loadWorkorder(workorderId);
|
|
14358
|
+
if (!snap || snap.workorder.doneAt) return false;
|
|
14092
14359
|
await tx.applyMutations(workorderId, [{
|
|
14093
14360
|
t: "workorder.update",
|
|
14094
|
-
patch: {
|
|
14361
|
+
patch: { doneAt: this.clock.now() }
|
|
14095
14362
|
}]);
|
|
14363
|
+
return true;
|
|
14096
14364
|
});
|
|
14097
14365
|
}
|
|
14098
14366
|
/**
|
|
14099
|
-
* 调度器基础设施:事件到达 →
|
|
14367
|
+
* 调度器基础设施:事件到达 → 唤醒工单(清 done_at,重新纳入扫描集合)。
|
|
14100
14368
|
* ★ 不是业务事件——这是调度器在循环内的统一入口,保证任何事件都能触发 scan。
|
|
14369
|
+
* 不碰 lifecycle(draft 的建图闸由 scan 门自己判)。
|
|
14101
14370
|
*/
|
|
14102
14371
|
async unmarkDone(workorderId) {
|
|
14103
14372
|
await this.store.transaction(async (tx) => {
|
|
14104
14373
|
const snap = await tx.loadWorkorder(workorderId);
|
|
14105
|
-
if (!snap || snap.workorder.
|
|
14374
|
+
if (!snap || !snap.workorder.doneAt) return;
|
|
14106
14375
|
await tx.applyMutations(workorderId, [{
|
|
14107
14376
|
t: "workorder.update",
|
|
14108
|
-
patch: {
|
|
14377
|
+
patch: { doneAt: null }
|
|
14109
14378
|
}]);
|
|
14110
14379
|
});
|
|
14111
14380
|
}
|
|
@@ -14332,10 +14601,10 @@ var init_bus = __esm({
|
|
|
14332
14601
|
const record8 = await tx.claimNextEvent(this.clock.now());
|
|
14333
14602
|
if (!record8) return null;
|
|
14334
14603
|
const snapshot = await tx.loadWorkorder(record8.workorderId);
|
|
14335
|
-
if (snapshot?.workorder.
|
|
14604
|
+
if (snapshot?.workorder.doneAt) {
|
|
14336
14605
|
await tx.applyMutations(record8.workorderId, [{
|
|
14337
14606
|
t: "workorder.update",
|
|
14338
|
-
patch: {
|
|
14607
|
+
patch: { doneAt: null }
|
|
14339
14608
|
}]);
|
|
14340
14609
|
}
|
|
14341
14610
|
const state = new WorkorderState(snapshot ?? emptySnapshotPlaceholder(record8));
|
|
@@ -14522,8 +14791,8 @@ function workToRevision(w2, workInputs, artifactFiles) {
|
|
|
14522
14791
|
contentRef = files[0].contentRef;
|
|
14523
14792
|
contentKind = files[0].contentKind === "manifest" ? "manifest" : files[0].contentKind === "external-pin" ? "external-pin" : "inline-blob";
|
|
14524
14793
|
} else {
|
|
14525
|
-
contentRef =
|
|
14526
|
-
contentKind = "
|
|
14794
|
+
contentRef = "";
|
|
14795
|
+
contentKind = "empty";
|
|
14527
14796
|
}
|
|
14528
14797
|
const revision = {
|
|
14529
14798
|
id: w2.id,
|
|
@@ -14545,6 +14814,7 @@ function workToRevision(w2, workInputs, artifactFiles) {
|
|
|
14545
14814
|
resolves: void 0
|
|
14546
14815
|
};
|
|
14547
14816
|
revision.rejectedReason = w2.rejectedReason;
|
|
14817
|
+
revision.replyToIssueId = w2.replyToIssueId ?? null;
|
|
14548
14818
|
return revision;
|
|
14549
14819
|
}
|
|
14550
14820
|
function issueToAnnotation(issue2, replies, recipients) {
|
|
@@ -14758,12 +15028,7 @@ function bridgeLoadReadModel(input) {
|
|
|
14758
15028
|
const seals = /* @__PURE__ */ new Map();
|
|
14759
15029
|
for (const snap of snapshots2) {
|
|
14760
15030
|
for (const node of snap.nodes) {
|
|
14761
|
-
if (
|
|
14762
|
-
seals.set(node.id, {
|
|
14763
|
-
reason: snap.workorder.sealReason ?? "cancelled",
|
|
14764
|
-
at: snap.workorder.updatedAt
|
|
14765
|
-
});
|
|
14766
|
-
} else if (node.cancelledAt) {
|
|
15031
|
+
if (node.cancelledAt) {
|
|
14767
15032
|
seals.set(node.id, { reason: "cancelled", at: node.cancelledAt });
|
|
14768
15033
|
}
|
|
14769
15034
|
}
|
|
@@ -15095,13 +15360,6 @@ function buildConstituentsRaw(model, plan, schema, roleIndex) {
|
|
|
15095
15360
|
target: op.artifactId,
|
|
15096
15361
|
payload: { reason: op.reason, ...op.note !== void 0 ? { note: op.note } : {} }
|
|
15097
15362
|
};
|
|
15098
|
-
case "reopen":
|
|
15099
|
-
return {
|
|
15100
|
-
artifactId: op.artifactId,
|
|
15101
|
-
kind: "reopen",
|
|
15102
|
-
target: op.artifactId,
|
|
15103
|
-
payload: op.note !== void 0 ? { note: op.note } : {}
|
|
15104
|
-
};
|
|
15105
15363
|
case "link":
|
|
15106
15364
|
return {
|
|
15107
15365
|
artifactId: op.artifactId,
|
|
@@ -15690,7 +15948,7 @@ var init_kernel_bridge = __esm({
|
|
|
15690
15948
|
await this.refreshTracked();
|
|
15691
15949
|
const wid = this.wo(args.artifactId);
|
|
15692
15950
|
let workId;
|
|
15693
|
-
const openWorks = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId && r.state === "working").sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
15951
|
+
const openWorks = [...this.model.revisions.values()].filter((r) => r.artifactId === args.artifactId && r.state === "working" && !r.replyToIssueId).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
15694
15952
|
workId = openWorks[0]?.id;
|
|
15695
15953
|
const artifact = this.model.artifacts.get(args.artifactId);
|
|
15696
15954
|
if (!workId) {
|
|
@@ -15846,8 +16104,10 @@ var init_kernel_bridge = __esm({
|
|
|
15846
16104
|
}
|
|
15847
16105
|
}
|
|
15848
16106
|
if (workId && args.note) {
|
|
15849
|
-
const
|
|
15850
|
-
|
|
16107
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16108
|
+
const w2 = snap?.works.find((x2) => x2.id === workId);
|
|
16109
|
+
const alreadyProduced = !w2 || w2.acceptanceState === "accepted" || (snap?.artifacts.some((a) => a.workId === workId) ?? false);
|
|
16110
|
+
if (w2 && !alreadyProduced) {
|
|
15851
16111
|
await this.commit({
|
|
15852
16112
|
companyId: "",
|
|
15853
16113
|
workorderId: wid,
|
|
@@ -16242,18 +16502,106 @@ var init_kernel_bridge = __esm({
|
|
|
16242
16502
|
}
|
|
16243
16503
|
async seal(args) {
|
|
16244
16504
|
const wid = this.wo(args.artifactId);
|
|
16505
|
+
await this.commit({
|
|
16506
|
+
companyId: "",
|
|
16507
|
+
workorderId: wid,
|
|
16508
|
+
actorId: args.actor,
|
|
16509
|
+
event: { kind: "plan.delete_node", workorderId: wid, nodeId: args.artifactId }
|
|
16510
|
+
});
|
|
16511
|
+
}
|
|
16512
|
+
/**
|
|
16513
|
+
* 强制重新运行节点(UI「重新运行」按钮,ADR 0137):直发 work.create——handler 自带
|
|
16514
|
+
* kill 旧 latest_work(running/failed/retry → dead)、清空各组评审指针、派发新会话;
|
|
16515
|
+
* 已完成节点重跑 = 产出新一版,新版被 accept 后下游经 pinnedWorkId 变更进入返工波。
|
|
16516
|
+
* 前置:节点在册且已指派执行人(work.create 落库 assigneeActorId 非空)。
|
|
16517
|
+
*/
|
|
16518
|
+
async rerunNode(args) {
|
|
16519
|
+
const wid = this.wo(args.artifactId);
|
|
16520
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16521
|
+
const node = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
16522
|
+
if (!node || node.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
|
|
16523
|
+
if (!node.assigneeActorId) throw new KernelError("\u8282\u70B9\u672A\u6307\u6D3E\u6267\u884C\u4EBA\u2014\u2014\u5148\u6307\u6D3E\u518D\u91CD\u65B0\u8FD0\u884C", "no-assignee");
|
|
16524
|
+
const count2 = snap.works.filter((w2) => w2.nodeId === args.artifactId).length;
|
|
16245
16525
|
await this.commit({
|
|
16246
16526
|
companyId: "",
|
|
16247
16527
|
workorderId: wid,
|
|
16248
16528
|
actorId: args.actor,
|
|
16249
16529
|
event: {
|
|
16250
|
-
kind: "
|
|
16530
|
+
kind: "work.create",
|
|
16251
16531
|
workorderId: wid,
|
|
16252
|
-
|
|
16253
|
-
|
|
16532
|
+
nodeId: args.artifactId,
|
|
16533
|
+
workId: deriveWorkId(args.artifactId, count2 + 1),
|
|
16534
|
+
reasons: [{ kind: "forced", actorId: args.actor }],
|
|
16535
|
+
continuesWorkId: null
|
|
16254
16536
|
}
|
|
16255
16537
|
});
|
|
16256
16538
|
}
|
|
16539
|
+
/** 停止评审(UI「停止评审」按钮):对节点各组未表态的 latest_review 发 review.kill(→ dead,杀会话)。 */
|
|
16540
|
+
async killNodeReviews(args) {
|
|
16541
|
+
const wid = this.wo(args.artifactId);
|
|
16542
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16543
|
+
const reqs = snap?.requirements.filter((r) => r.nodeId === args.artifactId) ?? [];
|
|
16544
|
+
for (const req of reqs) {
|
|
16545
|
+
if (!req.latestReviewId) continue;
|
|
16546
|
+
const r = snap.reviews.find((x2) => x2.id === req.latestReviewId);
|
|
16547
|
+
if (!r || r.verdict || r.status === "dead" || r.cancelledAt && r.status !== "retry") continue;
|
|
16548
|
+
await this.commit({
|
|
16549
|
+
companyId: "",
|
|
16550
|
+
workorderId: wid,
|
|
16551
|
+
actorId: args.actor,
|
|
16552
|
+
event: { kind: "review.kill", workorderId: wid, reviewId: r.id, reason: args.reason ?? "\u624B\u52A8\u505C\u6B62\u8BC4\u5BA1" }
|
|
16553
|
+
});
|
|
16554
|
+
}
|
|
16555
|
+
}
|
|
16556
|
+
/**
|
|
16557
|
+
* 重新评审(UI「重新评审」按钮,ADR 0137):对 success 的 latest_work 逐组直发 review.create。
|
|
16558
|
+
* 在跑的 latest_review 先 review.kill(create 的幂等守卫会跳过同 reviewer+group 的 open review,
|
|
16559
|
+
* 同工单事件严格有序保证 kill 先落)。已收口节点:review.create handler 会撤销收口
|
|
16560
|
+
*(清 latestAcceptId + 复位 acceptance 字段)→ 节点回「评审中」,scan 恢复评审判定。
|
|
16561
|
+
*/
|
|
16562
|
+
async rereviewNode(args) {
|
|
16563
|
+
const wid = this.wo(args.artifactId);
|
|
16564
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
16565
|
+
const node = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
16566
|
+
if (!node || node.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
|
|
16567
|
+
const lw = node.latestWorkId ? snap.works.find((w2) => w2.id === node.latestWorkId) : null;
|
|
16568
|
+
const lwState = lw ? workState(lw, hasOutput(lw, snap.artifacts.filter((a) => a.workId === lw.id).length)) : null;
|
|
16569
|
+
if (!lw || lwState !== "success") {
|
|
16570
|
+
throw new KernelError("\u8282\u70B9\u5C1A\u65E0\u6210\u529F\u4EA7\u51FA\uFF08latest_work \u975E success\uFF09\u2014\u2014\u8BC4\u5BA1\u4E2D/\u5DF2\u5B8C\u6210\u72B6\u6001\u624D\u80FD\u91CD\u65B0\u8BC4\u5BA1", "not-reviewable");
|
|
16571
|
+
}
|
|
16572
|
+
const reqs = snap.requirements.filter((r) => r.nodeId === args.artifactId);
|
|
16573
|
+
if (reqs.length === 0) throw new KernelError("\u8BE5\u8282\u70B9\u672A\u914D\u7F6E\u8BC4\u5BA1\u7EC4\u2014\u2014\u65E0\u8BC4\u5BA1\u53EF\u91CD\u5F00", "no-reviewers");
|
|
16574
|
+
for (const req of reqs) {
|
|
16575
|
+
if (req.latestReviewId) {
|
|
16576
|
+
const r = snap.reviews.find((x2) => x2.id === req.latestReviewId);
|
|
16577
|
+
if (r && !r.verdict && !r.cancelledAt && !r.endedAt) {
|
|
16578
|
+
await this.commit({
|
|
16579
|
+
companyId: "",
|
|
16580
|
+
workorderId: wid,
|
|
16581
|
+
actorId: args.actor,
|
|
16582
|
+
event: { kind: "review.kill", workorderId: wid, reviewId: r.id, reason: "\u91CD\u65B0\u8BC4\u5BA1" }
|
|
16583
|
+
});
|
|
16584
|
+
}
|
|
16585
|
+
}
|
|
16586
|
+
const existing = snap.reviews.filter((x2) => x2.targetWorkId === lw.id && x2.reviewerActorId === req.reviewerActorId && x2.reviewGroup === req.reviewGroup);
|
|
16587
|
+
const reviewId = existing.length === 0 ? deriveReviewId(lw.id, req.reviewerActorId, req.reviewGroup) : `rv:${lw.id}:${req.reviewerActorId}:${req.reviewGroup}:${existing.length + 1}`;
|
|
16588
|
+
await this.commit({
|
|
16589
|
+
companyId: "",
|
|
16590
|
+
workorderId: wid,
|
|
16591
|
+
actorId: args.actor,
|
|
16592
|
+
event: {
|
|
16593
|
+
kind: "review.create",
|
|
16594
|
+
workorderId: wid,
|
|
16595
|
+
nodeId: args.artifactId,
|
|
16596
|
+
reviewId,
|
|
16597
|
+
targetWorkId: lw.id,
|
|
16598
|
+
reviewerActorId: req.reviewerActorId,
|
|
16599
|
+
reviewGroup: req.reviewGroup,
|
|
16600
|
+
reviewerIsAgent: req.reviewerActorId.startsWith("actor:agent:")
|
|
16601
|
+
}
|
|
16602
|
+
});
|
|
16603
|
+
}
|
|
16604
|
+
}
|
|
16257
16605
|
/** 工单级暂停:workorder.paused handler 杀全部在途会话 + 挡新派发。 */
|
|
16258
16606
|
async pauseWorkorder(workorderId, actor) {
|
|
16259
16607
|
this.trackedWorkorders.add(workorderId);
|
|
@@ -16378,6 +16726,18 @@ var init_kernel_bridge = __esm({
|
|
|
16378
16726
|
actor: args.actor,
|
|
16379
16727
|
reason: typeof card.commandArgs?.["reason"] === "string" ? card.commandArgs["reason"] : "\u7BA1\u7406\u8005\u8BF7\u6C42\u3001\u4EBA\u5DF2\u6388\u6743"
|
|
16380
16728
|
});
|
|
16729
|
+
} else if (card.command === "resolveEscalation") {
|
|
16730
|
+
const escalationId = typeof card.commandArgs?.["escalationId"] === "string" ? card.commandArgs["escalationId"] : void 0;
|
|
16731
|
+
if (!escalationId) {
|
|
16732
|
+
throw new Error(`resolveEscalation \u6388\u6743\u5361\u7F3A escalationId\uFF1A${args.draftId}`);
|
|
16733
|
+
}
|
|
16734
|
+
const reason = typeof card.commandArgs?.["reason"] === "string" ? card.commandArgs["reason"] : void 0;
|
|
16735
|
+
await this.resolveEscalation({
|
|
16736
|
+
artifactId,
|
|
16737
|
+
actor: args.actor,
|
|
16738
|
+
escalationId,
|
|
16739
|
+
...reason !== void 0 ? { reason } : {}
|
|
16740
|
+
});
|
|
16381
16741
|
} else {
|
|
16382
16742
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u4E0D\u652F\u6301\u6267\u884C ${card.command}`);
|
|
16383
16743
|
}
|
|
@@ -16526,6 +16886,20 @@ var init_kernel_bridge = __esm({
|
|
|
16526
16886
|
applied++;
|
|
16527
16887
|
break;
|
|
16528
16888
|
}
|
|
16889
|
+
case "resolveEscalation": {
|
|
16890
|
+
const escOp = op;
|
|
16891
|
+
if (!escOp.escalationId) {
|
|
16892
|
+
throw new Error(`resolveEscalation op \u7F3A escalationId\uFF08${escOp.artifactId}\uFF09\u2014\u2014\u4E0D\u652F\u6301\u6574\u8282\u70B9\u5168\u6E05\uFF0C\u8BF7\u6307\u660E\u5173\u54EA\u4E00\u6761`);
|
|
16893
|
+
}
|
|
16894
|
+
await this.resolveEscalation({
|
|
16895
|
+
artifactId: escOp.artifactId,
|
|
16896
|
+
actor: _actor,
|
|
16897
|
+
escalationId: escOp.escalationId,
|
|
16898
|
+
...escOp.reason !== void 0 ? { reason: escOp.reason } : {}
|
|
16899
|
+
});
|
|
16900
|
+
applied++;
|
|
16901
|
+
break;
|
|
16902
|
+
}
|
|
16529
16903
|
default:
|
|
16530
16904
|
break;
|
|
16531
16905
|
}
|
|
@@ -16663,6 +17037,14 @@ var init_command_policy = __esm({
|
|
|
16663
17037
|
idempotent: true,
|
|
16664
17038
|
effect: (a) => `\u628A\u8282\u70B9 ${shortId(a.artifactId)} \u8F6C\u4E3A\u6301\u4E45\uFF08temp \u2192 persistent\uFF09`
|
|
16665
17039
|
},
|
|
17040
|
+
resolveEscalation: {
|
|
17041
|
+
risk: "authorize",
|
|
17042
|
+
idempotent: true,
|
|
17043
|
+
// 重复关同一条是幂等的(kernel.resolveEscalation 内部按 escalationId 去重)
|
|
17044
|
+
// 「关错=静默停摆」的唯一防线是人在卡上一眼看出「关的是哪一条」——escalationId 与 reason 摘要必须上卡。
|
|
17045
|
+
// a 里 escalationId/artifactId 由 server.ts 起草时冻进 commandArgs(已四级解析),保证是真值、非 `?`。
|
|
17046
|
+
effect: (a) => `\u5173\u95ED\u4E0A\u62A5 ${shortId(a.escalationId)}\uFF08\u8282\u70B9 ${shortId(a.artifactId)}${typeof a.reason === "string" ? `\uFF0C\u7406\u7531\uFF1A${a.reason.slice(0, 60)}` : ""}\uFF09`
|
|
17047
|
+
},
|
|
16666
17048
|
gc: {
|
|
16667
17049
|
risk: "forbidden",
|
|
16668
17050
|
forbiddenReason: "gc \u662F\u56DE\u6536\u5B64\u513F blob \u7684\u57FA\u7840\u8BBE\u65BD\u7EF4\u62A4\uFF0C\u7531 serve \u8FDB\u7A0B/\u4EBA\u7C7B operator \u6267\u884C\uFF0C\u4E0D\u5C5E\u4E8E\u7BA1\u7406\u8005\u80FD\u529B\u8303\u56F4\u3002"
|
|
@@ -129259,11 +129641,15 @@ function verifyOneSha(repos, sha2, opts) {
|
|
|
129259
129641
|
}
|
|
129260
129642
|
const reachable = repos.filter((r) => r.remote);
|
|
129261
129643
|
if (reachable.length === 0) return { ok: true };
|
|
129644
|
+
const attempts = [];
|
|
129262
129645
|
for (const r of reachable) {
|
|
129263
129646
|
try {
|
|
129264
129647
|
const ls = (0, import_node_child_process2.execFileSync)("git", ["ls-remote", r.remote], { encoding: "utf8", ...opts?.env ? { env: opts.env } : {} });
|
|
129265
129648
|
if (ls.split("\n").some((l) => l.split(" ")[0]?.toLowerCase() === sha2.toLowerCase())) return { ok: true };
|
|
129266
|
-
|
|
129649
|
+
attempts.push({ repo: r, kind: "not-found" });
|
|
129650
|
+
} catch (e) {
|
|
129651
|
+
const err = e;
|
|
129652
|
+
attempts.push({ repo: r, kind: "access-error", err: String(err.stderr ?? err.message ?? e).trim() });
|
|
129267
129653
|
}
|
|
129268
129654
|
}
|
|
129269
129655
|
if (opts?.mirrorDirOf) {
|
|
@@ -129278,6 +129664,15 @@ function verifyOneSha(repos, sha2, opts) {
|
|
|
129278
129664
|
}
|
|
129279
129665
|
}
|
|
129280
129666
|
}
|
|
129667
|
+
if (attempts.length > 0 && attempts.every((a) => a.kind === "access-error")) {
|
|
129668
|
+
const lines = attempts.map((a) => ` - \u4ED3\u5E93 \`${a.repo.id}\`\uFF08${a.repo.remote}\uFF09\uFF1A${(a.err || "\u672A\u77E5\u9519\u8BEF").split("\n").slice(0, 3).join(" ")}`).join("\n");
|
|
129669
|
+
return {
|
|
129670
|
+
ok: false,
|
|
129671
|
+
reason: `\u65E0\u6CD5\u8BBF\u95EE\u7528\u4E8E\u6838\u5BF9\u8BE5 sha \u7684 git \u4ED3\u5E93\u2014\u2014**\u8FD9\u662F\u670D\u52A1\u7AEF\u73AF\u5883\u95EE\u9898\uFF08\u51ED\u636E / \u7F51\u7EDC / \u6743\u9650\uFF09\uFF0C\u4E0D\u662F sha \u7684\u95EE\u9898**\uFF1A
|
|
129672
|
+
${lines}
|
|
129673
|
+
\u5904\u7406\u65B9\u5F0F\uFF1A\u5148\u8BA9\u8FD0\u7EF4\u6392\u67E5\uFF08GIT_ASKPASS / credential helper / DNS / \u4ED3\u5E93\u6743\u9650\uFF09\uFF0C\u6062\u590D\u540E\u518D\u91CD\u8BD5 propose\uFF1B\u5728\u4ED3\u5E93\u80FD\u8BBF\u95EE\u4E4B\u524D\uFF0C**\u4E0D\u8981\u6539 sha\u3001\u4E5F\u4E0D\u7528\u91CD\u53D6 sha**\u3002`
|
|
129674
|
+
};
|
|
129675
|
+
}
|
|
129281
129676
|
return {
|
|
129282
129677
|
ok: false,
|
|
129283
129678
|
reason: `commit \`${sha2.slice(0, 12)}\u2026\` \u5728\u672C\u9879\u76EE\u7684\u4EFB\u4F55\u4ED3\u5E93\u91CC\u90FD\u627E\u4E0D\u5230\uFF08\u65E2\u4E0D\u662F\u4EFB\u4F55\u5206\u652F\u7684\u6700\u65B0\u63D0\u4EA4\uFF0C\u672C\u5730\u955C\u50CF\u91CC\u4E5F\u6CA1\u6709\uFF09\u3002**\u522B\u624B\u586B sha**\u2014\u2014push \u5230\u5DE5\u5355\u5206\u652F\u540E\u76F4\u63A5 \`oasis propose <id> --reason "\u2026"\`\uFF0C\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u6536\u96C6\uFF1B\u786E\u9700\u624B\u52A8\u6307\u5B9A\u65F6\uFF0Csha \u5FC5\u987B\u7528 \`git rev-parse HEAD\` \u53D6\u3001\u4E14\u5DF2\u7ECF push \u4E0A\u53BB\u3002`
|
|
@@ -129346,6 +129741,190 @@ var init_ephemeral_project = __esm({
|
|
|
129346
129741
|
}
|
|
129347
129742
|
});
|
|
129348
129743
|
|
|
129744
|
+
// ../server/src/domains/collab/timeline.ts
|
|
129745
|
+
function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
129746
|
+
if (!snap) return { artifactId, items: [] };
|
|
129747
|
+
const runIdOf = (kind, targetId) => runIdByTarget?.get(`${kind}:${targetId}`);
|
|
129748
|
+
const items = [];
|
|
129749
|
+
const artifactCountByWork = /* @__PURE__ */ new Map();
|
|
129750
|
+
for (const a of snap.artifacts) {
|
|
129751
|
+
artifactCountByWork.set(a.workId, (artifactCountByWork.get(a.workId) ?? 0) + 1);
|
|
129752
|
+
}
|
|
129753
|
+
const nodeWorkIds = /* @__PURE__ */ new Set();
|
|
129754
|
+
for (const w2 of snap.works) {
|
|
129755
|
+
if (w2.nodeId !== artifactId) continue;
|
|
129756
|
+
if (w2.replyToIssueId) continue;
|
|
129757
|
+
nodeWorkIds.add(w2.id);
|
|
129758
|
+
const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
129759
|
+
items.push({
|
|
129760
|
+
type: "work",
|
|
129761
|
+
id: w2.id,
|
|
129762
|
+
createdAt: w2.createdAt,
|
|
129763
|
+
status,
|
|
129764
|
+
assignee: w2.assigneeActorId,
|
|
129765
|
+
...w2.startedAt ? { startedAt: w2.startedAt } : {},
|
|
129766
|
+
...w2.endedAt ? { endedAt: w2.endedAt } : {},
|
|
129767
|
+
...w2.conclusion ? { conclusion: w2.conclusion } : {},
|
|
129768
|
+
...w2.outcome ? { outcome: w2.outcome } : {},
|
|
129769
|
+
...w2.rejectedReason ? { rejectedReason: w2.rejectedReason } : {},
|
|
129770
|
+
...w2.acceptedAt ? { acceptedAt: w2.acceptedAt } : {},
|
|
129771
|
+
...w2.outputVersionNo !== null ? { outputVersionNo: w2.outputVersionNo } : {},
|
|
129772
|
+
.../* @__PURE__ */ ((r0) => r0 ? { runId: r0 } : {})(runIdOf("work", w2.id)),
|
|
129773
|
+
...w2.sessionRef ? { sessionRef: w2.sessionRef } : {},
|
|
129774
|
+
artifactCount: artifactCountByWork.get(w2.id) ?? 0
|
|
129775
|
+
});
|
|
129776
|
+
}
|
|
129777
|
+
for (const r of snap.reviews) {
|
|
129778
|
+
if (r.nodeId !== artifactId) continue;
|
|
129779
|
+
const status = r.status ?? (r.retryAt ? "retry" : r.cancelledAt ? "dead" : r.verdict === "approve" ? "accept" : r.verdict === "request_changes" ? "reject" : r.endedAt ? "failed" : "running");
|
|
129780
|
+
items.push({
|
|
129781
|
+
type: "review",
|
|
129782
|
+
id: r.id,
|
|
129783
|
+
createdAt: r.createdAt,
|
|
129784
|
+
status,
|
|
129785
|
+
reviewer: r.reviewerActorId,
|
|
129786
|
+
reviewGroup: r.reviewGroup,
|
|
129787
|
+
targetWorkId: r.targetWorkId,
|
|
129788
|
+
...r.verdict ? { verdict: r.verdict } : {},
|
|
129789
|
+
...r.note ? { note: r.note } : {},
|
|
129790
|
+
...r.decidedAt ? { decidedAt: r.decidedAt } : {},
|
|
129791
|
+
.../* @__PURE__ */ ((r0) => r0 ? { runId: r0 } : {})(runIdOf("review", r.id)),
|
|
129792
|
+
...r.sessionRef ? { sessionRef: r.sessionRef } : {}
|
|
129793
|
+
});
|
|
129794
|
+
}
|
|
129795
|
+
for (const i of snap.issues) {
|
|
129796
|
+
const related = i.aboutNodeId === artifactId || i.aboutWorkId !== null && nodeWorkIds.has(i.aboutWorkId) || i.aboutNodeId === null && i.aboutWorkId === null && i.raisedByNodeId === artifactId;
|
|
129797
|
+
if (!related) continue;
|
|
129798
|
+
const replies = snap.issueReplies.filter((r) => r.issueId === i.id).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt)).map((r) => ({ author: r.authorActorId, body: r.body, createdAt: r.createdAt }));
|
|
129799
|
+
items.push({
|
|
129800
|
+
type: "issue",
|
|
129801
|
+
id: i.id,
|
|
129802
|
+
createdAt: i.createdAt,
|
|
129803
|
+
kind: i.kind,
|
|
129804
|
+
state: i.state,
|
|
129805
|
+
blocking: i.blocking,
|
|
129806
|
+
body: i.body,
|
|
129807
|
+
author: i.authorActorId,
|
|
129808
|
+
...i.raisedByNodeId ? { raisedByNodeId: i.raisedByNodeId } : {},
|
|
129809
|
+
...i.aboutWorkId ? { aboutWorkId: i.aboutWorkId } : {},
|
|
129810
|
+
...i.resolvedAt ? { resolvedAt: i.resolvedAt } : {},
|
|
129811
|
+
...i.resolvedNote ? { resolvedNote: i.resolvedNote } : {},
|
|
129812
|
+
...i.proposedResolution ? { proposedResolution: i.proposedResolution } : {},
|
|
129813
|
+
replies
|
|
129814
|
+
});
|
|
129815
|
+
}
|
|
129816
|
+
items.sort((a, b2) => a.createdAt < b2.createdAt ? -1 : a.createdAt > b2.createdAt ? 1 : TYPE_ORDER[a.type] - TYPE_ORDER[b2.type]);
|
|
129817
|
+
return { artifactId, items };
|
|
129818
|
+
}
|
|
129819
|
+
var TYPE_ORDER;
|
|
129820
|
+
var init_timeline = __esm({
|
|
129821
|
+
"../server/src/domains/collab/timeline.ts"() {
|
|
129822
|
+
"use strict";
|
|
129823
|
+
TYPE_ORDER = { work: 0, review: 1, issue: 2 };
|
|
129824
|
+
}
|
|
129825
|
+
});
|
|
129826
|
+
|
|
129827
|
+
// ../server/src/domains/collab/node-state.ts
|
|
129828
|
+
function nodeLabel(a) {
|
|
129829
|
+
if (a.title) return a.title;
|
|
129830
|
+
if (a.description) return a.description;
|
|
129831
|
+
const tail = a.id.split(":").slice(2).join(":");
|
|
129832
|
+
return tail ? `\u672A\u547D\u540D ${a.type}\uFF08${tail}\uFF09` : `\u672A\u547D\u540D ${a.type}`;
|
|
129833
|
+
}
|
|
129834
|
+
function workspaceTitleMap(model) {
|
|
129835
|
+
const titleOf = /* @__PURE__ */ new Map();
|
|
129836
|
+
for (const a of model.artifacts.values()) {
|
|
129837
|
+
if (a.type === "brief") {
|
|
129838
|
+
const title = a.title ?? a.description;
|
|
129839
|
+
if (title) titleOf.set(a.workspace, title);
|
|
129840
|
+
}
|
|
129841
|
+
}
|
|
129842
|
+
return titleOf;
|
|
129843
|
+
}
|
|
129844
|
+
function isCurrentHeadConcluded2(model, a) {
|
|
129845
|
+
return a.currentRev !== null && concludedHead(model, a.id) === a.currentRev;
|
|
129846
|
+
}
|
|
129847
|
+
function nodeInfo(model, a) {
|
|
129848
|
+
const lifecycle = lifecycleOf(model, a.id);
|
|
129849
|
+
const sealed = lifecycle !== "active";
|
|
129850
|
+
const concluded = isCurrentHeadConcluded2(model, a);
|
|
129851
|
+
const queueLen = queueOf(model, a.id).length;
|
|
129852
|
+
const revisions = revisionsOf(model, a.id);
|
|
129853
|
+
const hasWorking = revisions.some((r) => r.state === "working");
|
|
129854
|
+
const producedParts = new Set(revisions.filter((r) => r.part !== void 0 && r.state !== "rejected").map((r) => r.part));
|
|
129855
|
+
const hasUnproducedParts = (a.parts ?? []).some((p2) => !producedParts.has(p2.name));
|
|
129856
|
+
let mark;
|
|
129857
|
+
if (a.latestWorkState !== void 0) {
|
|
129858
|
+
const st = a.latestWorkState;
|
|
129859
|
+
if (sealed) {
|
|
129860
|
+
mark = lifecycle;
|
|
129861
|
+
} else if (st === null) {
|
|
129862
|
+
mark = "embryo";
|
|
129863
|
+
} else if (st === "running") {
|
|
129864
|
+
mark = "active";
|
|
129865
|
+
} else if (st === "success") {
|
|
129866
|
+
mark = a.latestAccepted ? "concluded" : "gate-pending";
|
|
129867
|
+
} else {
|
|
129868
|
+
mark = "blocked";
|
|
129869
|
+
}
|
|
129870
|
+
} else {
|
|
129871
|
+
mark = sealed ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : concluded ? "concluded" : a.currentRev || queueLen > 0 ? "active" : "embryo";
|
|
129872
|
+
}
|
|
129873
|
+
return { mark, queueLen, concluded, sealed, hasWorking, hasUnproducedParts, latestWorkState: a.latestWorkState ?? null, latestAccepted: a.latestAccepted ?? false };
|
|
129874
|
+
}
|
|
129875
|
+
function deriveStage(members) {
|
|
129876
|
+
if (members.length > 0 && members.every((m2) => m2.sealed)) return "sealed";
|
|
129877
|
+
const live = members.filter((m2) => !m2.sealed);
|
|
129878
|
+
if (live.length > 0 && live.every((m2) => m2.concluded)) return "concluded";
|
|
129879
|
+
const reviewing = live.some((m2) => m2.mark === "gate-pending" || m2.mark === "active" && m2.queueLen > 0 && !m2.hasUnproducedParts);
|
|
129880
|
+
if (reviewing) return "review";
|
|
129881
|
+
const progressing = live.some((m2) => m2.mark === "active" || m2.mark === "embryo" || m2.hasUnproducedParts);
|
|
129882
|
+
const blocked = live.some((m2) => m2.mark === "blocked");
|
|
129883
|
+
if (blocked && !progressing) return "blocked";
|
|
129884
|
+
return "executing";
|
|
129885
|
+
}
|
|
129886
|
+
function nodeRunState(info) {
|
|
129887
|
+
if (info.sealed) return void 0;
|
|
129888
|
+
switch (info.mark) {
|
|
129889
|
+
case "embryo":
|
|
129890
|
+
return "not_run";
|
|
129891
|
+
case "active":
|
|
129892
|
+
return "running";
|
|
129893
|
+
case "gate-pending":
|
|
129894
|
+
return "reviewing";
|
|
129895
|
+
case "concluded":
|
|
129896
|
+
return "done";
|
|
129897
|
+
case "blocked":
|
|
129898
|
+
return "stuck";
|
|
129899
|
+
default:
|
|
129900
|
+
return void 0;
|
|
129901
|
+
}
|
|
129902
|
+
}
|
|
129903
|
+
function deriveNodeStage(info) {
|
|
129904
|
+
if (info.sealed) return "sealed";
|
|
129905
|
+
switch (info.mark) {
|
|
129906
|
+
case "concluded":
|
|
129907
|
+
return "concluded";
|
|
129908
|
+
case "gate-pending":
|
|
129909
|
+
return "review";
|
|
129910
|
+
case "blocked":
|
|
129911
|
+
return "blocked";
|
|
129912
|
+
case "embryo":
|
|
129913
|
+
return "embryo";
|
|
129914
|
+
default:
|
|
129915
|
+
if (info.latestWorkState === "running") return "editing";
|
|
129916
|
+
if (info.hasWorking || info.hasUnproducedParts) return "editing";
|
|
129917
|
+
if (info.queueLen > 0) return "review";
|
|
129918
|
+
return "merged";
|
|
129919
|
+
}
|
|
129920
|
+
}
|
|
129921
|
+
var init_node_state = __esm({
|
|
129922
|
+
"../server/src/domains/collab/node-state.ts"() {
|
|
129923
|
+
"use strict";
|
|
129924
|
+
init_src3();
|
|
129925
|
+
}
|
|
129926
|
+
});
|
|
129927
|
+
|
|
129349
129928
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
129350
129929
|
var util, objectUtil, ZodParsedType, getParsedType;
|
|
129351
129930
|
var init_util = __esm({
|
|
@@ -146190,6 +146769,7 @@ var init_mcp2 = __esm({
|
|
|
146190
146769
|
"../server/src/mcp.ts"() {
|
|
146191
146770
|
"use strict";
|
|
146192
146771
|
init_mcp();
|
|
146772
|
+
init_src();
|
|
146193
146773
|
init_src3();
|
|
146194
146774
|
}
|
|
146195
146775
|
});
|
|
@@ -147328,90 +147908,6 @@ var init_assistants = __esm({
|
|
|
147328
147908
|
}
|
|
147329
147909
|
});
|
|
147330
147910
|
|
|
147331
|
-
// ../server/src/domains/collab/node-state.ts
|
|
147332
|
-
function nodeLabel(a) {
|
|
147333
|
-
if (a.title) return a.title;
|
|
147334
|
-
if (a.description) return a.description;
|
|
147335
|
-
const tail = a.id.split(":").slice(2).join(":");
|
|
147336
|
-
return tail ? `\u672A\u547D\u540D ${a.type}\uFF08${tail}\uFF09` : `\u672A\u547D\u540D ${a.type}`;
|
|
147337
|
-
}
|
|
147338
|
-
function workspaceTitleMap(model) {
|
|
147339
|
-
const titleOf = /* @__PURE__ */ new Map();
|
|
147340
|
-
for (const a of model.artifacts.values()) {
|
|
147341
|
-
if (a.type === "brief") {
|
|
147342
|
-
const title = a.title ?? a.description;
|
|
147343
|
-
if (title) titleOf.set(a.workspace, title);
|
|
147344
|
-
}
|
|
147345
|
-
}
|
|
147346
|
-
return titleOf;
|
|
147347
|
-
}
|
|
147348
|
-
function isCurrentHeadConcluded2(model, a) {
|
|
147349
|
-
return a.currentRev !== null && concludedHead(model, a.id) === a.currentRev;
|
|
147350
|
-
}
|
|
147351
|
-
function nodeInfo(model, a) {
|
|
147352
|
-
const lifecycle = lifecycleOf(model, a.id);
|
|
147353
|
-
const sealed = lifecycle !== "active";
|
|
147354
|
-
const concluded = isCurrentHeadConcluded2(model, a);
|
|
147355
|
-
const queueLen = queueOf(model, a.id).length;
|
|
147356
|
-
const revisions = revisionsOf(model, a.id);
|
|
147357
|
-
const hasWorking = revisions.some((r) => r.state === "working");
|
|
147358
|
-
const producedParts = new Set(revisions.filter((r) => r.part !== void 0 && r.state !== "rejected").map((r) => r.part));
|
|
147359
|
-
const hasUnproducedParts = (a.parts ?? []).some((p2) => !producedParts.has(p2.name));
|
|
147360
|
-
let mark;
|
|
147361
|
-
if (a.latestWorkState !== void 0) {
|
|
147362
|
-
const st = a.latestWorkState;
|
|
147363
|
-
if (sealed) {
|
|
147364
|
-
mark = lifecycle;
|
|
147365
|
-
} else if (st === null) {
|
|
147366
|
-
mark = "embryo";
|
|
147367
|
-
} else if (st === "running") {
|
|
147368
|
-
mark = "active";
|
|
147369
|
-
} else if (st === "success") {
|
|
147370
|
-
mark = a.latestAccepted ? "concluded" : "gate-pending";
|
|
147371
|
-
} else {
|
|
147372
|
-
mark = "blocked";
|
|
147373
|
-
}
|
|
147374
|
-
} else {
|
|
147375
|
-
mark = sealed ? lifecycle : pendingConcludeAttempt(model, a.id) ? "gate-pending" : blockedOf(model, a.id).blocked ? "blocked" : concluded ? "concluded" : a.currentRev || queueLen > 0 ? "active" : "embryo";
|
|
147376
|
-
}
|
|
147377
|
-
return { mark, queueLen, concluded, sealed, hasWorking, hasUnproducedParts, latestWorkState: a.latestWorkState ?? null, latestAccepted: a.latestAccepted ?? false };
|
|
147378
|
-
}
|
|
147379
|
-
function deriveStage(members) {
|
|
147380
|
-
if (members.length > 0 && members.every((m2) => m2.sealed)) return "sealed";
|
|
147381
|
-
const live = members.filter((m2) => !m2.sealed);
|
|
147382
|
-
if (live.length > 0 && live.every((m2) => m2.concluded)) return "concluded";
|
|
147383
|
-
const reviewing = live.some((m2) => m2.mark === "gate-pending" || m2.mark === "active" && m2.queueLen > 0 && !m2.hasUnproducedParts);
|
|
147384
|
-
if (reviewing) return "review";
|
|
147385
|
-
const progressing = live.some((m2) => m2.mark === "active" || m2.mark === "embryo" || m2.hasUnproducedParts);
|
|
147386
|
-
const blocked = live.some((m2) => m2.mark === "blocked");
|
|
147387
|
-
if (blocked && !progressing) return "blocked";
|
|
147388
|
-
return "executing";
|
|
147389
|
-
}
|
|
147390
|
-
function deriveNodeStage(info) {
|
|
147391
|
-
if (info.sealed) return "sealed";
|
|
147392
|
-
switch (info.mark) {
|
|
147393
|
-
case "concluded":
|
|
147394
|
-
return "concluded";
|
|
147395
|
-
case "gate-pending":
|
|
147396
|
-
return "review";
|
|
147397
|
-
case "blocked":
|
|
147398
|
-
return "blocked";
|
|
147399
|
-
case "embryo":
|
|
147400
|
-
return "embryo";
|
|
147401
|
-
default:
|
|
147402
|
-
if (info.latestWorkState === "running") return "editing";
|
|
147403
|
-
if (info.hasWorking || info.hasUnproducedParts) return "editing";
|
|
147404
|
-
if (info.queueLen > 0) return "review";
|
|
147405
|
-
return "merged";
|
|
147406
|
-
}
|
|
147407
|
-
}
|
|
147408
|
-
var init_node_state = __esm({
|
|
147409
|
-
"../server/src/domains/collab/node-state.ts"() {
|
|
147410
|
-
"use strict";
|
|
147411
|
-
init_src3();
|
|
147412
|
-
}
|
|
147413
|
-
});
|
|
147414
|
-
|
|
147415
147911
|
// ../server/src/domains/collab/stall.ts
|
|
147416
147912
|
function shortId2(id) {
|
|
147417
147913
|
return id.length > 22 ? `${id.slice(0, 14)}\u2026` : id;
|
|
@@ -147420,16 +147916,16 @@ function stallOf(model, id, runtime, dispatcherSignals) {
|
|
|
147420
147916
|
const a = model.artifacts.get(id);
|
|
147421
147917
|
if (!a) return null;
|
|
147422
147918
|
const ownerIsAgent = a.owner.startsWith("actor:agent:");
|
|
147919
|
+
const escs = unresolvedEscalationsOf(model, id);
|
|
147920
|
+
if (escs.length > 0) {
|
|
147921
|
+
return { kind: "waiting_human", action: "discuss", ref: id, detail: `\u5DF2\u4E0A\u62A5\u5F85\u4EBA\u4ECB\u5165\uFF1A${escs.at(-1).reason}`, since: escs.at(-1).at };
|
|
147922
|
+
}
|
|
147423
147923
|
if (runtime?.status === "running") {
|
|
147424
147924
|
return { kind: "running", detail: `${runtime.action ?? "produce"} \u5728\u8DD1`, ...runtime.startedAt ? { since: runtime.startedAt } : {} };
|
|
147425
147925
|
}
|
|
147426
147926
|
if (hasOpenHumanChangeRequest(model, id) || headRejected(model, id)) {
|
|
147427
147927
|
return ownerIsAgent ? { kind: "waiting_agent", action: "rework", actorId: a.owner, detail: "\u6709\u672A\u5904\u7406\u7684\u53D8\u66F4\u8BF7\u6C42\uFF0C\u5F85\u8FD4\u5DE5" } : { kind: "waiting_human", action: "rework", actorId: a.owner, detail: "\u6709\u672A\u5904\u7406\u7684\u53D8\u66F4\u8BF7\u6C42\uFF0C\u5F85\u4F60\u8FD4\u5DE5" };
|
|
147428
147928
|
}
|
|
147429
|
-
const escs = unresolvedEscalationsOf(model, id);
|
|
147430
|
-
if (escs.length > 0) {
|
|
147431
|
-
return { kind: "waiting_human", action: "discuss", ref: id, detail: `\u5DF2\u4E0A\u62A5\u5F85\u4EBA\u4ECB\u5165\uFF1A${escs.at(-1).reason}`, since: escs.at(-1).at };
|
|
147432
|
-
}
|
|
147433
147929
|
const gaps = unresolvedGapsOf(model, id);
|
|
147434
147930
|
if (gaps.length > 0) {
|
|
147435
147931
|
return { kind: "waiting_human", action: "resolve_gap", detail: `\u5361\u5728\u7F3A\u53E3\u4E0A\u5F85\u4EBA\u8865\uFF1A${gaps[0].description}` };
|
|
@@ -148537,7 +149033,7 @@ function requiresMeReasonOf(model, artifact, viewer, facts) {
|
|
|
148537
149033
|
function computeRequiresMe(model, artifact, viewer, facts) {
|
|
148538
149034
|
return requiresMeReasonOf(model, artifact, viewer, facts) !== null;
|
|
148539
149035
|
}
|
|
148540
|
-
function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject, dispatchJournal = [], dispatchPausedOf,
|
|
149036
|
+
function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject, dispatchJournal = [], dispatchPausedOf, _ops = [], reviewerPreview, dispatcherSignalsOf, viewer, roleDefaultOwnerOf, roleLabelOf, resolveRoleExecutor, coordinatorEvents) {
|
|
148541
149037
|
const arts = [...model.artifacts.values()].filter((a) => a.workspace === workspaceId);
|
|
148542
149038
|
if (arts.length === 0) return null;
|
|
148543
149039
|
const ref2 = (id) => {
|
|
@@ -148596,16 +149092,23 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
|
|
|
148596
149092
|
const mail = lifecycle === "active" ? openMailOf(model, a.id) : [];
|
|
148597
149093
|
const requiresMeReason = lifecycle === "active" && a.id !== overviewRootId ? requiresMeReasonOf(model, a, viewer, requiresMeFacts) : null;
|
|
148598
149094
|
const requiresMe = requiresMeReason !== null;
|
|
149095
|
+
const runState = nodeRunState(info);
|
|
148599
149096
|
return {
|
|
148600
149097
|
id: a.id,
|
|
148601
149098
|
type: a.type,
|
|
148602
149099
|
label: nodeLabel(a),
|
|
148603
149100
|
stage,
|
|
149101
|
+
...runState ? { state: runState } : {},
|
|
148604
149102
|
currentRev: a.currentRev,
|
|
148605
149103
|
queue: info.queueLen,
|
|
148606
149104
|
// ★ 新引擎统一:node 状态唯一由 latest_work 派生(node-state.ts 五态)——mark==="blocked" 覆盖
|
|
148607
149105
|
// 旧 fold 的 blockedOf(原字段)与新引擎 latest_work failed/dead(blockedOf 恒 false、不可靠)。
|
|
148608
149106
|
blocked: info.mark === "blocked",
|
|
149107
|
+
// ADR 0079:未解上报数——分诊/巡检看整单时一眼看到「哪个节点挂着人还没处置的上报」。无则不带。
|
|
149108
|
+
...(() => {
|
|
149109
|
+
const n = unresolvedEscalationsOf(model, a.id).length;
|
|
149110
|
+
return n > 0 ? { unresolvedEscalations: n } : {};
|
|
149111
|
+
})(),
|
|
148609
149112
|
owner: ref2(a.owner),
|
|
148610
149113
|
...(hasDeliverySummary ? a.type === DELIVERY_SUMMARY_ARTIFACT_TYPE : a.isFinalOutput === true) ? { isFinalOutput: true } : {},
|
|
148611
149114
|
// 封存原因透出:stage 只到 "sealed" 粒度,原因(accepted/cancelled/frozen)另给,供前端区分展示。
|
|
@@ -148671,8 +149174,7 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
|
|
|
148671
149174
|
const total = counts2.running + counts2.held + counts2.failed + counts2.waitingHuman + counts2.waitingAgent + counts2.waitingSystem + counts2.waitingConvergence;
|
|
148672
149175
|
return total > 0 ? { counts: counts2, stuck } : void 0;
|
|
148673
149176
|
})();
|
|
148674
|
-
const
|
|
148675
|
-
const coordinatorActivity = coordinatorEvents ? buildCoordinatorActivityFromEvents(model, arts, coordinatorEvents, ref2) : buildCoordinatorActivity(arts, ops, ref2);
|
|
149177
|
+
const coordinatorActivity = buildCoordinatorActivityFromEvents(model, arts, coordinatorEvents ?? [], ref2);
|
|
148676
149178
|
const acceptance = buildAcceptance(model, arts);
|
|
148677
149179
|
const planning = planningStatusOf(model, arts, resolveRoleExecutor);
|
|
148678
149180
|
const spec = workorderSpecOf(model, arts);
|
|
@@ -148682,7 +149184,6 @@ function buildWorkorderDetail(model, workspaceId, resolveActor, resolveProject,
|
|
|
148682
149184
|
return {
|
|
148683
149185
|
summary: summaryWithRequiresMe,
|
|
148684
149186
|
graph: { nodes, edges },
|
|
148685
|
-
activity,
|
|
148686
149187
|
coordinatorActivity,
|
|
148687
149188
|
acceptance,
|
|
148688
149189
|
...managerActorId ? { managerActorId } : {},
|
|
@@ -148823,7 +149324,6 @@ function buildWorkorderDraftDetail(draft, resolveActor, typeReviewerPreview, res
|
|
|
148823
149324
|
return {
|
|
148824
149325
|
summary,
|
|
148825
149326
|
graph: { nodes, edges },
|
|
148826
|
-
activity: [],
|
|
148827
149327
|
coordinatorActivity: [],
|
|
148828
149328
|
acceptance: [],
|
|
148829
149329
|
...draft.seed.acceptanceCriteria ? { acceptanceCriteria: draft.seed.acceptanceCriteria } : {},
|
|
@@ -148970,93 +149470,6 @@ function buildRuntimeByArtifact(entries, ref2) {
|
|
|
148970
149470
|
return [id, runtime];
|
|
148971
149471
|
}));
|
|
148972
149472
|
}
|
|
148973
|
-
function activitySummaryNote(note) {
|
|
148974
|
-
return note.length <= ACTIVITY_NOTE_MAX_CHARS ? note : `${note.slice(0, ACTIVITY_NOTE_MAX_CHARS)}\u2026\uFF08\u5DF2\u622A\u65AD\uFF0C\u539F\u6587 ${note.length} \u5B57\uFF0C\u5168\u6587\u89C1 oplog\uFF09`;
|
|
148975
|
-
}
|
|
148976
|
-
function buildActivity(model, arts, ref2) {
|
|
148977
|
-
const events = [];
|
|
148978
|
-
for (const a of arts) {
|
|
148979
|
-
for (const r of revisionsOf(model, a.id)) {
|
|
148980
|
-
if (r.state !== "working") {
|
|
148981
|
-
events.push({
|
|
148982
|
-
seq: model.proposeSeq.get(r.id) ?? 0,
|
|
148983
|
-
at: r.createdAt,
|
|
148984
|
-
actor: ref2(r.author),
|
|
148985
|
-
kind: "propose",
|
|
148986
|
-
artifactId: a.id,
|
|
148987
|
-
revisionId: r.id,
|
|
148988
|
-
summary: `\u63D0\u51FA\u4FEE\u8BA2 ${r.id}\uFF08${r.reason}\uFF09`
|
|
148989
|
-
});
|
|
148990
|
-
}
|
|
148991
|
-
const latestVoteByAuthor = /* @__PURE__ */ new Map();
|
|
148992
|
-
for (const vote of model.reviews.get(r.id) ?? []) latestVoteByAuthor.set(vote.author, vote);
|
|
148993
|
-
for (const vote of latestVoteByAuthor.values()) {
|
|
148994
|
-
events.push({
|
|
148995
|
-
seq: vote.seq,
|
|
148996
|
-
at: vote.at,
|
|
148997
|
-
actor: ref2(vote.author),
|
|
148998
|
-
kind: vote.verdict === "approve" ? "review-approve" : "review-request-changes",
|
|
148999
|
-
artifactId: a.id,
|
|
149000
|
-
revisionId: r.id,
|
|
149001
|
-
summary: vote.verdict === "approve" ? "\u8BC4\u5BA1\u901A\u8FC7" : `\u8981\u6C42\u4FEE\u6539${vote.note ? `\uFF1A${activitySummaryNote(vote.note)}` : ""}`
|
|
149002
|
-
});
|
|
149003
|
-
}
|
|
149004
|
-
}
|
|
149005
|
-
for (const m2 of concludeMilestones(model, a.id)) {
|
|
149006
|
-
events.push({
|
|
149007
|
-
seq: m2.seq,
|
|
149008
|
-
at: m2.at,
|
|
149009
|
-
actor: ref2(m2.forced?.by ?? a.owner),
|
|
149010
|
-
kind: "conclude",
|
|
149011
|
-
artifactId: a.id,
|
|
149012
|
-
revisionId: m2.head,
|
|
149013
|
-
summary: m2.forced ? `\u5F3A\u5236\u5B9A\u7A3F\uFF08${m2.forced.reason}\uFF09` : "\u5B9A\u7A3F"
|
|
149014
|
-
});
|
|
149015
|
-
}
|
|
149016
|
-
for (const annotation of [...model.annotations.values()].filter((x2) => x2.anchor.target === a.id)) {
|
|
149017
|
-
events.push({
|
|
149018
|
-
seq: 0,
|
|
149019
|
-
// ★ 修复(实证 ws:wo-3945cc70):新引擎 bridge 的 annotation.thread 只含**回复**,无回复的 issue
|
|
149020
|
-
// thread=[] → thread[0]?.ts=undefined → at="" → buildActivity 排序时空串排最前、annotate 全堆顶,
|
|
149021
|
-
// 协作轨迹时间顺序错乱。fallback 到 issue 创建时间(createdAt 必有值)。
|
|
149022
|
-
at: annotation.thread[0]?.ts ?? annotation.createdAt ?? "",
|
|
149023
|
-
actor: ref2(annotation.author),
|
|
149024
|
-
kind: "annotate",
|
|
149025
|
-
artifactId: a.id,
|
|
149026
|
-
summary: annotation.body.split("\n").find((line) => line.trim() && !line.startsWith("OASIS_PLANNING_ISSUES ")) ?? "\u6279\u6CE8"
|
|
149027
|
-
});
|
|
149028
|
-
}
|
|
149029
|
-
}
|
|
149030
|
-
return events.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
|
|
149031
|
-
}
|
|
149032
|
-
function buildCoordinatorActivity(arts, ops, ref2) {
|
|
149033
|
-
const inWorkspace = new Set(arts.map((a) => a.id));
|
|
149034
|
-
const managerRaw = arts.find((a) => a.type === "brief")?.fields?.["manager"];
|
|
149035
|
-
const managerActor = typeof managerRaw === "string" ? managerRaw : null;
|
|
149036
|
-
const firstRevisionAt = ops.filter((op) => op.kind === "propose_revision" && inWorkspace.has(op.artifactId)).map((op) => op.timestamp).sort()[0];
|
|
149037
|
-
const GAP_FAMILY = /* @__PURE__ */ new Set(["report_gap", "resolve_gap", "escalate", "resolve_escalation"]);
|
|
149038
|
-
const edits = [];
|
|
149039
|
-
for (const op of ops) {
|
|
149040
|
-
if (!inWorkspace.has(op.artifactId)) continue;
|
|
149041
|
-
if (!COORDINATOR_EDIT_KINDS.has(op.kind)) continue;
|
|
149042
|
-
if (!GAP_FAMILY.has(op.kind) && (!managerActor || op.actor !== managerActor)) continue;
|
|
149043
|
-
const kind = op.kind;
|
|
149044
|
-
if ((kind === "spawn_artifact" || kind === "link_input") && (!firstRevisionAt || op.timestamp < firstRevisionAt)) {
|
|
149045
|
-
continue;
|
|
149046
|
-
}
|
|
149047
|
-
const detail = coordinatorEditDetail(kind, op.payload, ref2);
|
|
149048
|
-
edits.push({
|
|
149049
|
-
seq: op.seq,
|
|
149050
|
-
at: op.timestamp,
|
|
149051
|
-
actor: ref2(op.actor),
|
|
149052
|
-
kind,
|
|
149053
|
-
artifactId: op.artifactId,
|
|
149054
|
-
label: COORDINATOR_EDIT_LABEL[kind],
|
|
149055
|
-
...detail ? { detail } : {}
|
|
149056
|
-
});
|
|
149057
|
-
}
|
|
149058
|
-
return edits.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
|
|
149059
|
-
}
|
|
149060
149473
|
function buildCoordinatorActivityFromEvents(model, arts, events, ref2) {
|
|
149061
149474
|
const inWorkspace = new Set(arts.map((a) => a.id));
|
|
149062
149475
|
const managerRaw = arts.find((a) => a.type === "brief")?.fields?.["manager"];
|
|
@@ -149095,11 +149508,6 @@ function buildCoordinatorActivityFromEvents(model, arts, events, ref2) {
|
|
|
149095
149508
|
else push("resolve_gap", target, rec.seq, rec.createdAt, rec.actorId, "\u89E3\u9664\u7F3A\u53E3", str2(ev.note) ?? void 0);
|
|
149096
149509
|
continue;
|
|
149097
149510
|
}
|
|
149098
|
-
if (kind === "workorder.sealed") {
|
|
149099
|
-
const target = arts.find((a) => a.type === "brief")?.id ?? arts[0]?.id ?? "";
|
|
149100
|
-
push("seal", target, rec.seq, rec.createdAt, rec.actorId, "\u5C01\u5B58", [str2(ev.reason), str2(ev.note)].filter(Boolean).join("\uFF1A") || void 0);
|
|
149101
|
-
continue;
|
|
149102
|
-
}
|
|
149103
149511
|
if (managerActor && rec.actorId !== managerActor) continue;
|
|
149104
149512
|
if (kind === "plan.changed") {
|
|
149105
149513
|
const ev2 = ev;
|
|
@@ -149145,46 +149553,6 @@ function buildCoordinatorActivityFromEvents(model, arts, events, ref2) {
|
|
|
149145
149553
|
}
|
|
149146
149554
|
return edits.sort((x2, y) => x2.at < y.at ? -1 : x2.at > y.at ? 1 : x2.seq - y.seq);
|
|
149147
149555
|
}
|
|
149148
|
-
function coordinatorEditDetail(kind, payload, ref2) {
|
|
149149
|
-
const str2 = (v2) => typeof v2 === "string" && v2.trim() ? v2 : void 0;
|
|
149150
|
-
switch (kind) {
|
|
149151
|
-
case "link_input":
|
|
149152
|
-
case "unlink_input":
|
|
149153
|
-
return str2(payload.to);
|
|
149154
|
-
case "spawn_artifact":
|
|
149155
|
-
return str2(payload.title) ?? str2(payload.type);
|
|
149156
|
-
case "assign_owner": {
|
|
149157
|
-
const owner = str2(payload.owner);
|
|
149158
|
-
return owner ? ref2(owner).name ?? owner : void 0;
|
|
149159
|
-
}
|
|
149160
|
-
case "bump_pin": {
|
|
149161
|
-
const to = str2(payload.to);
|
|
149162
|
-
const pinned = str2(payload.pinned);
|
|
149163
|
-
return to && pinned ? `${to} \u2192 ${pinned}` : to ?? pinned;
|
|
149164
|
-
}
|
|
149165
|
-
case "report_gap":
|
|
149166
|
-
return str2(payload.description);
|
|
149167
|
-
case "resolve_gap":
|
|
149168
|
-
return str2(payload.note) ?? str2(payload.gapId);
|
|
149169
|
-
case "escalate":
|
|
149170
|
-
return str2(payload.reason);
|
|
149171
|
-
case "resolve_escalation":
|
|
149172
|
-
return str2(payload.note);
|
|
149173
|
-
case "edit_artifact": {
|
|
149174
|
-
const changed = ["title", "description", "docType", "fields", "reviewers"].filter((k2) => payload[k2] !== void 0);
|
|
149175
|
-
return changed.length ? changed.join("\u3001") : void 0;
|
|
149176
|
-
}
|
|
149177
|
-
case "seal": {
|
|
149178
|
-
const reason = str2(payload.reason);
|
|
149179
|
-
const note = str2(payload.note);
|
|
149180
|
-
return [reason, note].filter(Boolean).join("\uFF1A") || void 0;
|
|
149181
|
-
}
|
|
149182
|
-
case "reopen":
|
|
149183
|
-
return str2(payload.note);
|
|
149184
|
-
default:
|
|
149185
|
-
return void 0;
|
|
149186
|
-
}
|
|
149187
|
-
}
|
|
149188
149556
|
function buildAcceptance(model, arts) {
|
|
149189
149557
|
return arts.map((a) => ({
|
|
149190
149558
|
artifactId: a.id,
|
|
@@ -149227,7 +149595,7 @@ function buildNodeStatus(model, artifactId, dispatchJournal, resolve9, dispatche
|
|
|
149227
149595
|
})() : void 0;
|
|
149228
149596
|
return { ...runtime ? { runtime } : {}, ...stall ? { stall } : {} };
|
|
149229
149597
|
}
|
|
149230
|
-
var REQUIRES_ME_REASON_RANK
|
|
149598
|
+
var REQUIRES_ME_REASON_RANK;
|
|
149231
149599
|
var init_workorder_detail = __esm({
|
|
149232
149600
|
"../server/src/domains/collab/workorder-detail.ts"() {
|
|
149233
149601
|
"use strict";
|
|
@@ -149242,35 +149610,6 @@ var init_workorder_detail = __esm({
|
|
|
149242
149610
|
backpressure: 2,
|
|
149243
149611
|
"owner-mail": 3
|
|
149244
149612
|
};
|
|
149245
|
-
ACTIVITY_NOTE_MAX_CHARS = 500;
|
|
149246
|
-
COORDINATOR_EDIT_KINDS = /* @__PURE__ */ new Set([
|
|
149247
|
-
"link_input",
|
|
149248
|
-
"unlink_input",
|
|
149249
|
-
"spawn_artifact",
|
|
149250
|
-
"assign_owner",
|
|
149251
|
-
"bump_pin",
|
|
149252
|
-
"report_gap",
|
|
149253
|
-
"resolve_gap",
|
|
149254
|
-
"escalate",
|
|
149255
|
-
"resolve_escalation",
|
|
149256
|
-
"edit_artifact",
|
|
149257
|
-
"seal",
|
|
149258
|
-
"reopen"
|
|
149259
|
-
]);
|
|
149260
|
-
COORDINATOR_EDIT_LABEL = {
|
|
149261
|
-
link_input: "\u63A5\u5165\u4F9D\u8D56",
|
|
149262
|
-
unlink_input: "\u79FB\u9664\u4F9D\u8D56",
|
|
149263
|
-
spawn_artifact: "\u65B0\u5EFA\u4EA4\u4ED8\u7269",
|
|
149264
|
-
assign_owner: "\u6539\u6D3E",
|
|
149265
|
-
bump_pin: "\u89E6\u53D1\u8FD4\u5DE5",
|
|
149266
|
-
report_gap: "\u62A5\u544A\u7F3A\u53E3",
|
|
149267
|
-
resolve_gap: "\u89E3\u9664\u7F3A\u53E3",
|
|
149268
|
-
escalate: "\u4E0A\u62A5\u9700\u4EBA\u4ECB\u5165",
|
|
149269
|
-
resolve_escalation: "\u5173\u95ED\u4E0A\u62A5",
|
|
149270
|
-
edit_artifact: "\u6539\u4EA4\u4ED8\u7269\u4FE1\u606F",
|
|
149271
|
-
seal: "\u5C01\u5B58",
|
|
149272
|
-
reopen: "\u91CD\u542F"
|
|
149273
|
-
};
|
|
149274
149613
|
}
|
|
149275
149614
|
});
|
|
149276
149615
|
|
|
@@ -160384,6 +160723,8 @@ function authorizedCommandReceipt(command, args, actor) {
|
|
|
160384
160723
|
return { message: `force_conclude ${str(args, "artifactId")}\u2014\u2014\u91CC\u7A0B\u7891\u5E26 forced(by=${actor}) \u6807\u8BB0\uFF0C\u6D88\u8D39\u8005\u53EF\u89C1` };
|
|
160385
160724
|
case "promote":
|
|
160386
160725
|
return { message: "promoted to persistent" };
|
|
160726
|
+
case "resolveEscalation":
|
|
160727
|
+
return { message: `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
|
|
160387
160728
|
default:
|
|
160388
160729
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u65E0\u56DE\u6267\u6784\u9020 ${command}`);
|
|
160389
160730
|
}
|
|
@@ -160391,36 +160732,61 @@ function authorizedCommandReceipt(command, args, actor) {
|
|
|
160391
160732
|
async function runCommand(kernel, blobs, oplog, engineStore, actor, command, args, ctx = {}) {
|
|
160392
160733
|
if (isAgent(actor) && GOVERNANCE_COMMANDS.has(command)) {
|
|
160393
160734
|
if (command === "resolveEscalation") {
|
|
160394
|
-
const artifactId = str(args, "artifactId");
|
|
160395
160735
|
const why = optStr(args, "reason");
|
|
160396
|
-
|
|
160397
|
-
if (escalationId === void 0) {
|
|
160398
|
-
|
|
160399
|
-
|
|
160400
|
-
|
|
160736
|
+
let escalationId = optStr(args, "escalationId");
|
|
160737
|
+
if (escalationId === void 0 && ctx.sessionEscalationId !== void 0) {
|
|
160738
|
+
escalationId = ctx.sessionEscalationId;
|
|
160739
|
+
}
|
|
160740
|
+
let artifactId;
|
|
160741
|
+
if (escalationId !== void 0) {
|
|
160742
|
+
const found = escalationById(kernel.model, escalationId);
|
|
160743
|
+
if (!found) throw new Error(`\u4E0A\u62A5 ${escalationId} \u4E0D\u5B58\u5728\uFF08id \u89C1 \`oasis status <\u8282\u70B9>\` \u7684\u300C\u4E0A\u62A5\u300D\u6BB5\uFF09\u3002`);
|
|
160744
|
+
if (found.resolved) throw new Error(`\u4E0A\u62A5 ${escalationId} \u5DF2\u7ECF\u5173\u95ED\uFF0C\u65E0\u9700\u518D\u5173\u3002`);
|
|
160745
|
+
artifactId = found.artifactId;
|
|
160746
|
+
} else {
|
|
160747
|
+
const argArtifactId = optStr(args, "artifactId");
|
|
160748
|
+
if (argArtifactId === void 0) {
|
|
160749
|
+
throw new Error(
|
|
160750
|
+
`\u6CA1\u80FD\u786E\u5B9A\u8981\u5173\u54EA\u6761\u4E0A\u62A5\u2014\u2014\u8FD9\u4E0D\u662F\u4ECE\u67D0\u6761\u4E0A\u62A5\u5361\u70B9\u5F00\u7684\u4F1A\u8BDD\u3002\u8BF7\u6307\u660E\u5173\u54EA\u4E00\u6761\uFF1Aoasis resolve-escalation --escalation <id>\uFF08id \u89C1 \`oasis status <\u8282\u70B9>\` \u7684\u300C\u4E0A\u62A5\u300D\u6BB5\uFF09\u3002`
|
|
160751
|
+
);
|
|
160752
|
+
}
|
|
160753
|
+
const open2 = unresolvedEscalationsOf(kernel.model, argArtifactId);
|
|
160754
|
+
if (open2.length === 1) {
|
|
160755
|
+
escalationId = open2[0].escalationId;
|
|
160756
|
+
artifactId = argArtifactId;
|
|
160757
|
+
} else if (open2.length === 0) {
|
|
160758
|
+
throw new Error(`\u300C${argArtifactId}\u300D\u4E0A\u6CA1\u6709\u672A\u89E3\u4E0A\u62A5\u53EF\u5173\u3002`);
|
|
160759
|
+
} else {
|
|
160760
|
+
throw new Error(
|
|
160761
|
+
`\u300C${argArtifactId}\u300D\u4E0A\u6709 ${open2.length} \u6761\u672A\u89E3\u4E0A\u62A5\uFF0C\u8BF7\u6307\u660E\u5173\u54EA\u4E00\u6761\uFF08--escalation <id>\uFF09\uFF1A
|
|
160762
|
+
` + open2.map((e) => ` --escalation ${e.escalationId} \uFF08${e.reason.split("\n")[0]}\uFF09`).join("\n")
|
|
160763
|
+
);
|
|
160764
|
+
}
|
|
160401
160765
|
}
|
|
160402
|
-
const
|
|
160403
|
-
|
|
160404
|
-
|
|
160405
|
-
|
|
160406
|
-
|
|
160407
|
-
|
|
160408
|
-
|
|
160409
|
-
|
|
160410
|
-
|
|
160411
|
-
|
|
160412
|
-
|
|
160413
|
-
|
|
160766
|
+
const normalizedArgs = {
|
|
160767
|
+
escalationId,
|
|
160768
|
+
// ← 已四级解析出的具体一条(绝非 undefined、绝非「全清」)
|
|
160769
|
+
artifactId,
|
|
160770
|
+
// ← escalationById 反查出的所属节点
|
|
160771
|
+
...why !== void 0 ? { reason: why } : {}
|
|
160772
|
+
};
|
|
160773
|
+
const escPolicy = classifyCommand("resolveEscalation");
|
|
160774
|
+
const effectLabel = escPolicy.effect(normalizedArgs);
|
|
160775
|
+
const { draftId } = await kernel.proposeCommandAuthorization({
|
|
160776
|
+
command: "resolveEscalation",
|
|
160777
|
+
args: normalizedArgs,
|
|
160778
|
+
artifactId,
|
|
160779
|
+
effectLabel,
|
|
160414
160780
|
actor,
|
|
160415
160781
|
// 人正在对话里 → 卡当场弹在这段 chat;自主会话(没人在场)→ 缺省,落 owner inbox。
|
|
160416
|
-
ctx.chatOrigin !== void 0 ? { origin: ctx.chatOrigin } :
|
|
160417
|
-
);
|
|
160782
|
+
...ctx.chatOrigin !== void 0 ? { origin: ctx.chatOrigin } : {}
|
|
160783
|
+
});
|
|
160418
160784
|
return {
|
|
160419
|
-
message: `\u5DF2\u63D0\u4EA4\u300C\
|
|
160785
|
+
message: `\u5DF2\u63D0\u4EA4\u300C${effectLabel}\u300D\u7684\u5F85\u6388\u6743\u5361\uFF08${draftId}\uFF09\u2014\u2014**\u8FD8\u6CA1\u6267\u884C**\uFF0C\u7B49\u4EBA\u70B9\u786E\u8BA4\u3002
|
|
160420
160786
|
\u628A\u4F60\u7684\u5224\u65AD\u4F9D\u636E\u8BB2\u6E05\u695A\uFF08\u505A\u4E86\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u73B0\u5728\u53EF\u4EE5\u5173\uFF09\uFF0C\u4EBA\u5728\u5361\u4E0A\u770B\u5230\u7684\u5C31\u662F\u5B83\u3002`,
|
|
160421
|
-
// outcome
|
|
160422
|
-
//
|
|
160423
|
-
data: { outcome: "
|
|
160787
|
+
// outcome 结构化生效标记(与 forceConclude 同构):CLI api.cmd 内部据它接管同步等待 / 异步分流
|
|
160788
|
+
// (packages/cli/src/cli.ts:441-448),别让 agent 靠读中文判断生没生效。
|
|
160789
|
+
data: { outcome: "authorizing", draftId }
|
|
160424
160790
|
};
|
|
160425
160791
|
}
|
|
160426
160792
|
const policy = classifyCommand(command);
|
|
@@ -161046,8 +161412,9 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
161046
161412
|
}
|
|
161047
161413
|
case "resolveEscalation": {
|
|
161048
161414
|
const escId = optStr(args, "escalationId");
|
|
161415
|
+
const artId = optStr(args, "artifactId");
|
|
161049
161416
|
await kernel.resolveEscalation({
|
|
161050
|
-
artifactId:
|
|
161417
|
+
...artId !== void 0 ? { artifactId: artId } : {},
|
|
161051
161418
|
actor,
|
|
161052
161419
|
...optStr(args, "reason") !== void 0 ? { reason: optStr(args, "reason") } : {},
|
|
161053
161420
|
...escId !== void 0 ? { escalationId: escId } : {}
|
|
@@ -161110,10 +161477,6 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
161110
161477
|
await kernel.promote({ artifactId: str(args, "artifactId"), actor });
|
|
161111
161478
|
return authorizedCommandReceipt(command, args, actor);
|
|
161112
161479
|
}
|
|
161113
|
-
case "reopen": {
|
|
161114
|
-
await kernel.reopen({ artifactId: str(args, "artifactId"), actor, note: optStr(args, "note") });
|
|
161115
|
-
return { message: `reopened ${str(args, "artifactId")}\uFF08lifecycle \u2192 active\uFF09` };
|
|
161116
|
-
}
|
|
161117
161480
|
case "hold": {
|
|
161118
161481
|
await kernel.hold({
|
|
161119
161482
|
artifactId: str(args, "artifactId"),
|
|
@@ -161121,7 +161484,7 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
161121
161484
|
source: optStr(args, "source") ?? "human",
|
|
161122
161485
|
...optStr(args, "reason") !== void 0 ? { reason: optStr(args, "reason") } : {}
|
|
161123
161486
|
});
|
|
161124
|
-
return { message: `\u8282\u70B9\u5DF2\
|
|
161487
|
+
return { message: `\u8282\u70B9\u5DF2\u505C\u6B62\u8FD0\u884C\uFF08work.kill\uFF09\uFF1A${str(args, "artifactId")}\u3002\u91CD\u8DD1\u7528 rerun / release\u3002` };
|
|
161125
161488
|
}
|
|
161126
161489
|
case "release": {
|
|
161127
161490
|
await kernel.release({
|
|
@@ -161131,6 +161494,22 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
161131
161494
|
});
|
|
161132
161495
|
return { message: `\u8282\u70B9\u5DF2\u89E3\u963B\uFF08release\uFF09\uFF1A${str(args, "artifactId")}\u2014\u2014\u6295\u5F71\u4E0B\u4E00\u62CD\u4F1A\u91CD\u63A8\u8BE5\u8282\u70B9\u5F53\u4E0B\u6B20\u7684\u6D3B\u3002` };
|
|
161133
161496
|
}
|
|
161497
|
+
case "rerun": {
|
|
161498
|
+
await kernel.rerunNode({ artifactId: str(args, "artifactId"), actor });
|
|
161499
|
+
return { message: `\u8282\u70B9\u5DF2\u91CD\u65B0\u8FD0\u884C\uFF08work.create\uFF09\uFF1A${str(args, "artifactId")}` };
|
|
161500
|
+
}
|
|
161501
|
+
case "rereview": {
|
|
161502
|
+
await kernel.rereviewNode({ artifactId: str(args, "artifactId"), actor });
|
|
161503
|
+
return { message: `\u8282\u70B9\u5DF2\u91CD\u65B0\u8BC4\u5BA1\uFF08review.create\uFF09\uFF1A${str(args, "artifactId")}` };
|
|
161504
|
+
}
|
|
161505
|
+
case "killReview": {
|
|
161506
|
+
await kernel.killNodeReviews({
|
|
161507
|
+
artifactId: str(args, "artifactId"),
|
|
161508
|
+
actor,
|
|
161509
|
+
...optStr(args, "reason") !== void 0 ? { reason: optStr(args, "reason") } : {}
|
|
161510
|
+
});
|
|
161511
|
+
return { message: `\u8282\u70B9\u8BC4\u5BA1\u5DF2\u505C\u6B62\uFF08review.kill\uFF09\uFF1A${str(args, "artifactId")}` };
|
|
161512
|
+
}
|
|
161134
161513
|
case "forceConclude": {
|
|
161135
161514
|
await kernel.forceConclude({
|
|
161136
161515
|
artifactId: str(args, "artifactId"),
|
|
@@ -161203,13 +161582,14 @@ ${body}`);
|
|
|
161203
161582
|
}
|
|
161204
161583
|
return parts.join("\n\n");
|
|
161205
161584
|
}
|
|
161206
|
-
async function runView(kernel, oplog, engineStore, blobs, name, artifactId, params, ciGateStatus, reviewAssignee, nodeStatus, engineRecentEvents) {
|
|
161585
|
+
async function runView(kernel, oplog, engineStore, blobs, name, artifactId, params, ciGateStatus, reviewAssignee, nodeStatus, engineRecentEvents, dispatchesOfWorkorder) {
|
|
161207
161586
|
const model = kernel.model;
|
|
161208
161587
|
const need2 = () => {
|
|
161209
161588
|
if (!artifactId) throw new Error("\u8BE5\u89C6\u56FE\u9700\u8981 artifact \u53C2\u6570");
|
|
161210
161589
|
return artifactId;
|
|
161211
161590
|
};
|
|
161212
161591
|
const renderRevisionContent = async (rev) => {
|
|
161592
|
+
if (isEmptyContent(rev.contentKind)) return null;
|
|
161213
161593
|
if (rev.contentKind === "external-pin") return `external-pin \u2192 ${rev.contentRef}\uFF08\u5185\u5BB9\u5728\u5916\u90E8\u7CFB\u7EDF/git\uFF0C\u6863\u6848\u67DC\u672A\u5B58\u6B63\u6587\uFF09`;
|
|
161214
161594
|
if (rev.contentKind === "manifest") return renderManifestText(blobs, rev.contentRef);
|
|
161215
161595
|
return new TextDecoder().decode(await blobs.get(rev.contentRef));
|
|
@@ -161277,15 +161657,62 @@ async function runView(kernel, oplog, engineStore, blobs, name, artifactId, para
|
|
|
161277
161657
|
),
|
|
161278
161658
|
gaps: model.gaps.get(id) ?? [],
|
|
161279
161659
|
// 决策 0030 D1:未解的带 gapId(resolve_gap 定位用)、已解的带 note
|
|
161660
|
+
escalations: escalationsOf(model, id),
|
|
161661
|
+
// ADR 0079:上报全列(含已解)——status 的「上报」段读它
|
|
161280
161662
|
ciGate: ciGateStatus?.get(id) ?? null,
|
|
161281
161663
|
// B 批:CI 闸在途实时进度(旁账,无则 null)
|
|
161282
161664
|
// 「此刻在干嘛」:与工单图节点同源(buildNodeStatus)。缺 provider 时两字段一并缺省。
|
|
161283
|
-
...await nodeStatus?.(id) ?? {}
|
|
161665
|
+
...await nodeStatus?.(id) ?? {},
|
|
161666
|
+
// 节点运行五态(ADR 0137):与 WorkorderNode.state 同源同口径(nodeRunState),已作废节点缺省。
|
|
161667
|
+
...(() => {
|
|
161668
|
+
const s2 = nodeRunState(nodeInfo(model, artifact));
|
|
161669
|
+
return s2 ? { state: s2 } : {};
|
|
161670
|
+
})()
|
|
161284
161671
|
};
|
|
161285
161672
|
}
|
|
161673
|
+
case "timeline": {
|
|
161674
|
+
const id = need2();
|
|
161675
|
+
const artifact = model.artifacts.get(id);
|
|
161676
|
+
if (!artifact) throw new Error(`artifact \u4E0D\u5B58\u5728: ${id}`);
|
|
161677
|
+
if (!engineStore) return { artifactId: id, items: [] };
|
|
161678
|
+
const ws = artifact.workspace;
|
|
161679
|
+
const snap = await engineStore.transaction((tx) => tx.loadWorkorder(ws));
|
|
161680
|
+
let runIdByTarget;
|
|
161681
|
+
if (dispatchesOfWorkorder) {
|
|
161682
|
+
try {
|
|
161683
|
+
const rows = await dispatchesOfWorkorder(ws);
|
|
161684
|
+
runIdByTarget = /* @__PURE__ */ new Map();
|
|
161685
|
+
for (const d of [...rows].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt))) {
|
|
161686
|
+
runIdByTarget.set(`${d.targetKind}:${d.targetId}`, d.id);
|
|
161687
|
+
}
|
|
161688
|
+
} catch (err) {
|
|
161689
|
+
console.warn(`[timeline] \u6D3E\u53D1\u53F0\u8D26\u67E5\u8BE2\u5931\u8D25\uFF0C\u672C\u6B21\u4E0D\u51FA runId: ${String(err)}`);
|
|
161690
|
+
}
|
|
161691
|
+
}
|
|
161692
|
+
return buildNodeTimeline(id, snap, runIdByTarget);
|
|
161693
|
+
}
|
|
161286
161694
|
case "content": {
|
|
161287
161695
|
const id = need2();
|
|
161288
161696
|
const path26 = params?.get("path") ?? null;
|
|
161697
|
+
if (id.startsWith("esc:")) {
|
|
161698
|
+
const e = escalationById(model, id);
|
|
161699
|
+
if (!e) throw new Error(`\u4E0A\u62A5\u4E0D\u5B58\u5728: ${id}\uFF08\u4E0A\u62A5 id \u89C1 \`oasis status <artifactId>\` \u7684\u300C\u4E0A\u62A5\u300D\u6BB5\uFF09`);
|
|
161700
|
+
const node = model.artifacts.get(e.artifactId);
|
|
161701
|
+
return {
|
|
161702
|
+
kind: "escalation",
|
|
161703
|
+
escalationId: e.escalationId,
|
|
161704
|
+
artifactId: e.artifactId,
|
|
161705
|
+
nodeLabel: node?.title ?? node?.description ?? null,
|
|
161706
|
+
reason: e.reason,
|
|
161707
|
+
by: e.by,
|
|
161708
|
+
...e.hand !== void 0 ? { hand: e.hand } : {},
|
|
161709
|
+
at: e.at,
|
|
161710
|
+
...e.part != null ? { part: e.part } : {},
|
|
161711
|
+
...e.gapId !== void 0 ? { gapId: e.gapId } : {},
|
|
161712
|
+
resolved: e.resolved,
|
|
161713
|
+
...e.resolvedReason !== void 0 ? { resolvedReason: e.resolvedReason } : {}
|
|
161714
|
+
};
|
|
161715
|
+
}
|
|
161289
161716
|
if (id.startsWith("annotation:") || id.startsWith("ann:")) {
|
|
161290
161717
|
const aid = id.startsWith("annotation:") ? id.slice("annotation:".length) : id;
|
|
161291
161718
|
const a = model.annotations.get(aid);
|
|
@@ -161343,7 +161770,7 @@ async function runView(kernel, oplog, engineStore, blobs, name, artifactId, para
|
|
|
161343
161770
|
const targetId = pinnedRev ?? artifact.currentRev;
|
|
161344
161771
|
if (!targetId) return { kind: "empty" };
|
|
161345
161772
|
const headRev = model.revisions.get(targetId);
|
|
161346
|
-
if (headRev.contentKind
|
|
161773
|
+
if (isEmptyContent(headRev.contentKind)) {
|
|
161347
161774
|
return { kind: "empty" };
|
|
161348
161775
|
}
|
|
161349
161776
|
const at = {
|
|
@@ -161772,12 +162199,14 @@ async function startOasisServer(opts) {
|
|
|
161772
162199
|
let actingForHuman;
|
|
161773
162200
|
let deputyChatRt;
|
|
161774
162201
|
let chatOrigin;
|
|
162202
|
+
let sessionEscalationId;
|
|
161775
162203
|
if (tokenClaims?.action === "chat" && tokenClaims.chatSessionId && opts.chatSession) {
|
|
161776
162204
|
const cs = await opts.chatSession.getSession(tokenClaims.chatSessionId).catch(() => null);
|
|
161777
162205
|
if (cs && cs.aiActorId === actor && cs.humanActorId.startsWith("actor:human:")) {
|
|
161778
162206
|
actingForHuman = cs.humanActorId;
|
|
161779
162207
|
deputyChatRt = cs.runtimeSessionId ?? void 0;
|
|
161780
162208
|
chatOrigin = tokenClaims.chatSessionId;
|
|
162209
|
+
sessionEscalationId = cs.escalationId ?? void 0;
|
|
161781
162210
|
}
|
|
161782
162211
|
}
|
|
161783
162212
|
const result = await runCommand(
|
|
@@ -161794,6 +162223,7 @@ async function startOasisServer(opts) {
|
|
|
161794
162223
|
...tokenClaims?.artifactId !== void 0 ? { sessionArtifactId: tokenClaims.artifactId } : {},
|
|
161795
162224
|
...actingForHuman !== void 0 ? { actingForHuman } : {},
|
|
161796
162225
|
...chatOrigin !== void 0 ? { chatOrigin } : {},
|
|
162226
|
+
...sessionEscalationId !== void 0 ? { sessionEscalationId } : {},
|
|
161797
162227
|
...opts.projectState ? { projectState: opts.projectState } : {},
|
|
161798
162228
|
...opts.artifactState ? { artifactState: opts.artifactState } : {},
|
|
161799
162229
|
...opts.schema ? { schema: opts.schema } : {},
|
|
@@ -162173,7 +162603,7 @@ async function startOasisServer(opts) {
|
|
|
162173
162603
|
const body = JSON.parse(rawBody || "{}");
|
|
162174
162604
|
const ctx = resolveDiscussContext(engine.kernel.model, body, actor ?? void 0);
|
|
162175
162605
|
const chatStore = opts.chatSession;
|
|
162176
|
-
const baseItemKey = body.draftId ? `draft:${body.draftId}` : body.annotationId ? `ann:${body.annotationId}` : `esc:${body.artifactId}`;
|
|
162606
|
+
const baseItemKey = body.draftId ? `draft:${body.draftId}` : body.annotationId ? `ann:${body.annotationId}` : body.escalationId ? `esc:${body.escalationId}` : `esc:${body.artifactId}`;
|
|
162177
162607
|
const itemKey = `${baseItemKey}:${ctx.ownerId}`;
|
|
162178
162608
|
let chatSessionId;
|
|
162179
162609
|
if (chatStore) {
|
|
@@ -162188,7 +162618,7 @@ async function startOasisServer(opts) {
|
|
|
162188
162618
|
const mgrBinding = reg0 ? await reg0.getBinding(ctx.managerId).catch(() => null) : null;
|
|
162189
162619
|
const managerNodeId = mgrBinding?.status === "active" ? mgrBinding.nodeId : "";
|
|
162190
162620
|
const managerRuntimeKind = mgrBinding?.status === "active" ? mgrBinding.runtimeKind : void 0;
|
|
162191
|
-
await chatStore.createSession({ id: chatSessionId, humanActorId: ctx.ownerId, aiActorId: ctx.managerId, runtimeId: managerNodeId, ...managerRuntimeKind ? { runtimeKind: managerRuntimeKind } : {}, runtimeSessionId: null, title, touchedAt: now, createdAt: now, workspace: ctx.workspace }).catch(() => void 0);
|
|
162621
|
+
await chatStore.createSession({ id: chatSessionId, humanActorId: ctx.ownerId, aiActorId: ctx.managerId, runtimeId: managerNodeId, ...managerRuntimeKind ? { runtimeKind: managerRuntimeKind } : {}, runtimeSessionId: null, title, touchedAt: now, createdAt: now, workspace: ctx.workspace, ...body.escalationId ? { escalationId: body.escalationId } : {} }).catch(() => void 0);
|
|
162192
162622
|
await chatStore.linkWorkOrder(chatSessionId, ctx.workspace).catch(() => void 0);
|
|
162193
162623
|
}
|
|
162194
162624
|
discussSessions.set(itemKey, chatSessionId);
|
|
@@ -162494,7 +162924,8 @@ async function startOasisServer(opts) {
|
|
|
162494
162924
|
opts.ciGateStatus,
|
|
162495
162925
|
opts.reviewAssignee,
|
|
162496
162926
|
opts.nodeStatus,
|
|
162497
|
-
engineRecentEvents
|
|
162927
|
+
engineRecentEvents,
|
|
162928
|
+
opts.dispatchesOfWorkorder
|
|
162498
162929
|
);
|
|
162499
162930
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ data }));
|
|
162500
162931
|
} catch (err) {
|
|
@@ -163619,6 +164050,8 @@ var init_server3 = __esm({
|
|
|
163619
164050
|
init_collect();
|
|
163620
164051
|
init_remote_util();
|
|
163621
164052
|
init_ephemeral_project();
|
|
164053
|
+
init_timeline();
|
|
164054
|
+
init_node_state();
|
|
163622
164055
|
init_src3();
|
|
163623
164056
|
init_mcp2();
|
|
163624
164057
|
init_router();
|
|
@@ -163641,7 +164074,7 @@ var init_server3 = __esm({
|
|
|
163641
164074
|
CHAT_OUTPUT_LIMIT = 16e3;
|
|
163642
164075
|
STREAMING_PATHS = ["/api/events", "/api/chat", "/api/trajectory/events", "/api/intervention/wait"];
|
|
163643
164076
|
WILL_VERBS = /* @__PURE__ */ new Set(["annotate", "reply", "resolve", "review", "merge", "escalate", "hold", "release"]);
|
|
163644
|
-
GOVERNANCE_COMMANDS = /* @__PURE__ */ new Set(["seal", "assign", "promote", "
|
|
164077
|
+
GOVERNANCE_COMMANDS = /* @__PURE__ */ new Set(["seal", "assign", "promote", "forceConclude", "unlink", "gc", "requestChange", "resolveEscalation"]);
|
|
163645
164078
|
isAgent = (actor) => actor.startsWith("actor:agent:");
|
|
163646
164079
|
}
|
|
163647
164080
|
});
|
|
@@ -177505,6 +177938,8 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
|
|
|
177505
177938
|
actor: actorRef(gap.by),
|
|
177506
177939
|
...gapReportCount > 1 ? { impact: `\u5DF2\u91CD\u590D\u4E0A\u62A5 ${gapReportCount} \u6B21` } : {},
|
|
177507
177940
|
overdue: exactEscalation ? true : overdueSince(gapSince) ?? false,
|
|
177941
|
+
// ADR 0079 D1:把挂在这条 gap 上的那条上报 id 带到**顶层**(前端「沟通聊聊」据它绑定会话 → 关上报可省 id)。
|
|
177942
|
+
...exactEscalation?.escalationId ? { escalationId: exactEscalation.escalationId } : {},
|
|
177508
177943
|
gap: {
|
|
177509
177944
|
gapId: gap.gapId,
|
|
177510
177945
|
// 契约:reporter 永远是 GapView.by(原始上报人);诊断者另见 diagnosis 文案,不覆盖署名。
|
|
@@ -177513,27 +177948,31 @@ function buildInbox(model, me, leadOf, resolveActor, markers = [], slaMs, nowIso
|
|
|
177513
177948
|
problem: gap.description,
|
|
177514
177949
|
...exactEscalation ? { diagnosis: exactEscalation.reason } : {},
|
|
177515
177950
|
priority: exactEscalation ? "decision_required" : "blocked",
|
|
177516
|
-
reportCount: gap.reportCount ?? 1
|
|
177951
|
+
reportCount: gap.reportCount ?? 1,
|
|
177952
|
+
...exactEscalation?.escalationId ? { escalationId: exactEscalation.escalationId } : {}
|
|
177517
177953
|
}
|
|
177518
177954
|
});
|
|
177519
177955
|
}
|
|
177520
177956
|
if (gaps.length === 0 && escalations.length > 0) {
|
|
177521
|
-
const
|
|
177522
|
-
|
|
177523
|
-
|
|
177524
|
-
|
|
177525
|
-
|
|
177526
|
-
|
|
177527
|
-
|
|
177528
|
-
|
|
177529
|
-
|
|
177530
|
-
|
|
177531
|
-
|
|
177532
|
-
|
|
177533
|
-
|
|
177534
|
-
|
|
177535
|
-
|
|
177536
|
-
|
|
177957
|
+
for (const e of escalations) {
|
|
177958
|
+
gapItems.push({
|
|
177959
|
+
kind: "gap-escalation",
|
|
177960
|
+
artifactId: a.id,
|
|
177961
|
+
workspace: a.workspace,
|
|
177962
|
+
since: e.at,
|
|
177963
|
+
actionRequired: true,
|
|
177964
|
+
title: nodeLabel(a),
|
|
177965
|
+
actor: actorRef(e.by),
|
|
177966
|
+
overdue: true,
|
|
177967
|
+
// 无 gap 的熔断升级卡本就是「已卡住需人介入」,恒置顶催办
|
|
177968
|
+
// ADR 0079 D1:每张卡各代表一条上报 → 顶层带上它的 escalationId(会话绑定用);summary 不再拼「(共 N 条)」。
|
|
177969
|
+
escalationId: e.escalationId,
|
|
177970
|
+
// ADR-0108 D10:**不再截到 100 字**。熔断升级卡走的正是这条「无 gap」分支,而 D10 刚
|
|
177971
|
+
// 往里放进了真正有用的东西(末次失败原文、哪台机器死了多久、去哪改绑)——一刀切到 100 字
|
|
177972
|
+
// 会把它们全砍掉,只剩「连败 N 次」这种人早就知道的部分。前端按卡片排版自己收,不在这里截。
|
|
177973
|
+
summary: `\u300C${nodeLabel(a)}\u300D\u88AB\u4E0A\u62A5\u9700\u4F60\u4ECB\u5165${who}\uFF1A${e.reason}`
|
|
177974
|
+
});
|
|
177975
|
+
}
|
|
177537
177976
|
}
|
|
177538
177977
|
}
|
|
177539
177978
|
for (const r of listPendingReviews(model)) {
|
|
@@ -178715,14 +179154,6 @@ var init_letter_read_backfill = __esm({
|
|
|
178715
179154
|
}
|
|
178716
179155
|
});
|
|
178717
179156
|
|
|
178718
|
-
// ../server/src/domains/collab/workorder-seal.ts
|
|
178719
|
-
var init_workorder_seal = __esm({
|
|
178720
|
-
"../server/src/domains/collab/workorder-seal.ts"() {
|
|
178721
|
-
"use strict";
|
|
178722
|
-
init_src3();
|
|
178723
|
-
}
|
|
178724
|
-
});
|
|
178725
|
-
|
|
178726
179157
|
// ../server/src/domains/collab/index.ts
|
|
178727
179158
|
async function resolveLead(registry2, me) {
|
|
178728
179159
|
const rec = await registry2.getActor(me);
|
|
@@ -178784,6 +179215,7 @@ async function renderOverviewText(blobs, model, arts) {
|
|
|
178784
179215
|
if (!head) return void 0;
|
|
178785
179216
|
const rev = model.revisions.get(head);
|
|
178786
179217
|
if (!rev) return void 0;
|
|
179218
|
+
if (isEmptyContent(rev.contentKind)) return "";
|
|
178787
179219
|
if (rev.contentKind === "external-pin") return `external-pin \u2192 ${rev.contentRef}`;
|
|
178788
179220
|
if (rev.contentKind === "manifest") {
|
|
178789
179221
|
const entries = await readManifest(blobs, rev.contentRef);
|
|
@@ -178938,13 +179370,7 @@ function collabDomain(opts) {
|
|
|
178938
179370
|
const resolveProject = await buildProjectResolver(artifacts);
|
|
178939
179371
|
const dispatchPausedOf = await buildDispatchPausedResolver(kernel.model, artifacts);
|
|
178940
179372
|
const wsArts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === req.params.id);
|
|
178941
|
-
|
|
178942
|
-
let coordinatorEvents;
|
|
178943
|
-
if (engineStore) {
|
|
178944
|
-
coordinatorEvents = await engineStore.transaction((tx) => tx.listEvents(req.params.id));
|
|
178945
|
-
} else {
|
|
178946
|
-
ops = (await Promise.all(wsArts.map((a) => oplog.read(a.id)))).flat();
|
|
178947
|
-
}
|
|
179373
|
+
const coordinatorEvents = engineStore ? await engineStore.transaction((tx) => tx.listEvents(req.params.id)) : void 0;
|
|
178948
179374
|
const signalsForThisCompany = opts.dispatcherSignalsOf ? (id) => opts.dispatcherSignalsOf(req.auth.companyId, id) : void 0;
|
|
178949
179375
|
const roleDefsByName = opts.roles ? new Map((await opts.roles.list()).map((r) => [r.name, { label: r.label, defaultOwner: r.defaultOwner }])) : void 0;
|
|
178950
179376
|
const roleDefaultOwnerOf = roleDefsByName ? (role) => {
|
|
@@ -178959,7 +179385,7 @@ function collabDomain(opts) {
|
|
|
178959
179385
|
resolveProject,
|
|
178960
179386
|
opts.dispatchJournal?.() ?? [],
|
|
178961
179387
|
dispatchPausedOf,
|
|
178962
|
-
|
|
179388
|
+
[],
|
|
178963
179389
|
(id) => kernel.reviewerPreview(id),
|
|
178964
179390
|
signalsForThisCompany,
|
|
178965
179391
|
req.auth.actor,
|
|
@@ -179321,7 +179747,6 @@ var init_collab = __esm({
|
|
|
179321
179747
|
init_organization_usage();
|
|
179322
179748
|
init_planner();
|
|
179323
179749
|
init_closure_report();
|
|
179324
|
-
init_workorder_seal();
|
|
179325
179750
|
}
|
|
179326
179751
|
});
|
|
179327
179752
|
|
|
@@ -183552,7 +183977,7 @@ function humanExitCause(exit) {
|
|
|
183552
183977
|
const base = (exit.reason ? label[exit.reason] : void 0) ?? `\u4F1A\u8BDD\u5F02\u5E38\u7ED3\u675F\uFF08${exit.reason ?? "\u65E0\u9000\u51FA\u4FE1\u606F"}\uFF09`;
|
|
183553
183978
|
return exit.errorMessage ? `${base}\uFF1A${exit.errorMessage.slice(0, 200)}` : base;
|
|
183554
183979
|
}
|
|
183555
|
-
var import_node_crypto36, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
|
|
183980
|
+
var import_node_crypto36, AUTONOMOUS_ETHOS, CONVERSATIONAL_ETHOS, SHARED_GRAPH_BODY, AUTONOMOUS_RECOVERY_TOOLS, HIGH_RISK_COMMANDS, COORDINATOR_SYSTEM_PROMPT, CONVERSATIONAL_RECOVERY_TOOLS, CONVERSATIONAL_ESCALATION_TOOLS, CONVERSATIONAL_MANAGER_BODY, MACHINE_EXIT_CODES, CoordinatorWorker;
|
|
183556
183981
|
var init_worker = __esm({
|
|
183557
183982
|
"../server/src/coordinator/worker.ts"() {
|
|
183558
183983
|
"use strict";
|
|
@@ -183635,12 +184060,18 @@ ${HIGH_RISK_COMMANDS}`;
|
|
|
183635
184060
|
- \`oasis hold <id> --reason "<\u4E3A\u4EC0\u4E48\u5148\u51BB>"\` \u2014\u2014 \u6682\u505C\u8BE5\u8282\u70B9\u7684 agent \u6D3E\u53D1\u3002\u53CD\u590D\u5931\u8D25\u3001\u6216\u4EBA\u8981\u5148\u67E5/\u5148\u624B\u52A8\u6539\u73AF\u5883\uFF08\u6362 runtime\u3001\u8865\u51ED\u8BC1\uFF09\u65F6\uFF0C\u5148\u628A\u5B83\u51BB\u4F4F\u522B\u518D\u7A7A\u8F6C\u91CD\u6D3E\uFF1Bowner \u4E0D\u53D8\u3001\u5185\u5BB9\u4ECD\u53EF\u6539\u3002
|
|
183636
184061
|
- \`oasis release <id>\` \u2014\u2014 \u6062\u590D\u6D3E\u53D1\uFF1A\u89E3\u963B\u540E\u7CFB\u7EDF\u91CD\u63A8\u8BE5\u8282\u70B9**\u5F53\u4E0B\u771F\u6B63\u6B20\u7684\u6D3B**\uFF08\u5BF9\u88AB\u5347\u7EA7\u51BB\u7ED3\u7684\u8282\u70B9\uFF0C\u8FD9\u5C31\u662F"\u8BA9\u5B83\u91CD\u65B0\u8DD1"\uFF09\u3002**\u53EA\u5728\u786E\u8BA4\u6839\u56E0\u5DF2\u5904\u7406\u540E\u624D\u653E**\uFF08\u4EBA\u5DF2\u6362 runtime / \u8865\u4E86\u51ED\u8BC1 / \u6539\u4E86\u4EFB\u52A1\u4E66\uFF09\uFF0C\u5426\u5219\u4E00\u653E\u53C8\u683D\u540C\u4E00\u4E2A\u5751\u3002
|
|
183637
184062
|
\u5224\u65AD\u8FB9\u754C\uFF1A\u8FD9\u4FE9\u662F"\u6709\u4EE3\u4EF7\u3001\u9700\u5224\u65AD"\u7684\u52A8\u4F5C\u2014\u2014**\u4EBA\u6279\u51C6\u4E86\u518D\u505A**\uFF0C\u522B\u81EA\u4F5C\u4E3B\u5F20\u628A\u4EBA\u6B63\u5728\u67E5\u7684\u8282\u70B9\u653E\u51FA\u53BB\u3002`;
|
|
184063
|
+
CONVERSATIONAL_ESCALATION_TOOLS = `\u3010\u5173\u4E0A\u62A5\uFF08\u5BF9\u8BDD\u4E13\u5C5E\uFF09\uFF1A\u4ECE\u67D0\u6761\u4E0A\u62A5\u5361\u300C\u6C9F\u901A\u804A\u804A\u300D\u8FDB\u6765\u3001\u628A\u4E8B\u60C5\u529E\u5B8C\u540E\uFF0C\u4E3B\u52A8\u5173\u6389\u8FD9\u6761\u4E0A\u62A5\u2014\u2014\u522B\u628A\u5DF2\u7ECF\u89E3\u51B3\u7684\u4E8B\u4E00\u76F4\u6302\u5728\u4EBA\u7684\u6536\u4EF6\u7BB1\u91CC\u3011\uFF1A
|
|
184064
|
+
- \`oasis resolve-escalation --reason "<\u4F60\u505A\u4E86\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u73B0\u5728\u53EF\u4EE5\u5173>"\` \u2014\u2014 **\u4E0D\u7528\u5E26 id**\uFF1A\u8FD9\u573A\u4F1A\u8BDD\u5C31\u662F\u4E3A\u67D0\u6761\u4E0A\u62A5\u5F00\u7684\uFF0C\u7CFB\u7EDF\u77E5\u9053\u5173\u54EA\u4E00\u6761\u3002
|
|
184065
|
+
- \u5B83**\u4E0D\u4F1A\u7ACB\u523B\u751F\u6548**\uFF1A\u4F1A\u8F6C\u6210\u4E00\u5F20\u300C\u5F85\u786E\u8BA4\u300D\u5361\u7B49\u4EBA\u70B9\u786E\u8BA4\uFF08\u5173\u9519\u7684\u540E\u679C\u662F\u5361\u6D88\u5931\u3001\u8282\u70B9\u8FD8\u51BB\u7740 \u2192 \u9759\u9ED8\u6C38\u4E45\u505C\u6446\uFF0C\u6240\u4EE5\u8981\u4EBA\u70B9\u5934\uFF09\u3002\u7167\u53D1\u5373\u53EF\uFF0C\u522B\u56E0\u4E3A\u4E0D\u7ACB\u523B\u751F\u6548\u5C31\u8DF3\u8FC7\u3001\u4E5F\u522B\u5F53\u6210\u5DF2\u7ECF\u5173\u6389\u5F80\u4E0B\u8D70\u3002
|
|
184066
|
+
- \`--reason\` \u5199\u6E05\u4F9D\u636E\uFF08\u505A\u4E86\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u73B0\u5728\u53EF\u4EE5\u5173\uFF09\u2014\u2014\u5B83\u8FDB\u64CD\u4F5C\u65E5\u5FD7\u3001\u4E5F\u662F\u4EBA\u5728\u5361\u4E0A\u770B\u5230\u7684\u5224\u65AD\u6750\u6599\u3002`;
|
|
183638
184067
|
CONVERSATIONAL_MANAGER_BODY = `${CONVERSATIONAL_ETHOS}
|
|
183639
184068
|
|
|
183640
184069
|
${SHARED_GRAPH_BODY}
|
|
183641
184070
|
|
|
183642
184071
|
${CONVERSATIONAL_RECOVERY_TOOLS}
|
|
183643
184072
|
|
|
184073
|
+
${CONVERSATIONAL_ESCALATION_TOOLS}
|
|
184074
|
+
|
|
183644
184075
|
${HIGH_RISK_COMMANDS}`;
|
|
183645
184076
|
MACHINE_EXIT_CODES = /* @__PURE__ */ new Set([
|
|
183646
184077
|
"output-limit",
|
|
@@ -184112,7 +184543,8 @@ ${ctx.nodeFault}
|
|
|
184112
184543
|
...openEscalations.length > 0 ? [
|
|
184113
184544
|
`- \u8FD9\u4E2A\u8282\u70B9\u4E0A\u8FD8\u6302\u7740 **${openEscalations.length}** \u6761\u6CA1\u5173\u7684\u4E0A\u62A5\uFF08\u4EBA\u7684 inbox \u91CC\u8FD8\u770B\u5F97\u5230\uFF09\u3002**\u6839\u56E0\u771F\u9664\u4E86\u5C31\u987A\u624B\u5173\u6389**\uFF0C\u522B\u8BA9\u4EBA\u5BF9\u7740\u4E00\u4EF6\u5DF2\u7ECF\u4E0D\u5B58\u5728\u7684\u4E8B\u53D1\u6101\uFF1A`,
|
|
184114
184545
|
...openEscalations.map(
|
|
184115
|
-
|
|
184546
|
+
// ADR 0079 B:位置参 artifactId 已去掉——escalationId 自描述所属节点,`--escalation` 就够了。
|
|
184547
|
+
(e) => ` - \`oasis resolve-escalation --escalation ${e.escalationId} --reason "<\u4E3A\u4EC0\u4E48\u73B0\u5728\u53EF\u4EE5\u5173>"\``
|
|
184116
184548
|
),
|
|
184117
184549
|
` \u8FD9\u6761**\u4E0D\u4F1A\u7ACB\u523B\u751F\u6548**\uFF1A\u7CFB\u7EDF\u4F1A\u8F6C\u6210\u4E00\u5F20\u5361\u7247\u3001\u7B49\u4EBA\u70B9\u786E\u8BA4\uFF08\u5173\u9519\u4E86\u7684\u540E\u679C\u662F\u5361\u6D88\u5931\u3001\u8282\u70B9\u8FD8\u51BB\u7740\uFF0C\u6240\u4EE5\u8981\u4EBA\u70B9\u5934\uFF09\u3002\u7167\u53D1\u5373\u53EF\uFF0C\u522B\u56E0\u4E3A\u5B83\u4E0D\u7ACB\u523B\u751F\u6548\u5C31\u8DF3\u8FC7\u3002`
|
|
184118
184550
|
] : [],
|
|
@@ -184442,7 +184874,6 @@ var init_src8 = __esm({
|
|
|
184442
184874
|
init_tokens();
|
|
184443
184875
|
init_collab();
|
|
184444
184876
|
init_closure_report();
|
|
184445
|
-
init_workorder_seal();
|
|
184446
184877
|
init_workorders();
|
|
184447
184878
|
init_workorder_detail();
|
|
184448
184879
|
init_letter_read_backfill();
|
|
@@ -186683,6 +187114,14 @@ function createPgPool(dsn, opts) {
|
|
|
186683
187114
|
connectionTimeoutMillis: opts?.connectionTimeoutMillis ?? 5e3
|
|
186684
187115
|
});
|
|
186685
187116
|
}
|
|
187117
|
+
async function dropSchemaIfExists(dsn, schema) {
|
|
187118
|
+
const pool = createPgPool(dsn, { max: 1 });
|
|
187119
|
+
try {
|
|
187120
|
+
await pool.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
187121
|
+
} finally {
|
|
187122
|
+
await pool.end();
|
|
187123
|
+
}
|
|
187124
|
+
}
|
|
186686
187125
|
var init_pool = __esm({
|
|
186687
187126
|
"../storage/src/pool.ts"() {
|
|
186688
187127
|
"use strict";
|
|
@@ -189045,6 +189484,7 @@ var init_postgres_chat_sessions = __esm({
|
|
|
189045
189484
|
await pool.query(`CREATE INDEX IF NOT EXISTS chat_sessions_analyzed_run_idx ON "${s2}".chat_sessions (analyzed_run_id, touched_at DESC)`);
|
|
189046
189485
|
await pool.query(`ALTER TABLE "${s2}".chat_sessions ADD COLUMN IF NOT EXISTS runtime_kind text`);
|
|
189047
189486
|
await pool.query(`ALTER TABLE "${s2}".chat_sessions ADD COLUMN IF NOT EXISTS project_id text`);
|
|
189487
|
+
await pool.query(`ALTER TABLE "${s2}".chat_sessions ADD COLUMN IF NOT EXISTS escalation_id text`);
|
|
189048
189488
|
await pool.query(`
|
|
189049
189489
|
WITH ranked AS (
|
|
189050
189490
|
SELECT id, row_number() OVER (PARTITION BY session_id ORDER BY seq, created_at, id) AS rn
|
|
@@ -189063,9 +189503,9 @@ var init_postgres_chat_sessions = __esm({
|
|
|
189063
189503
|
}
|
|
189064
189504
|
async createSession(s2) {
|
|
189065
189505
|
await this.pool.query(
|
|
189066
|
-
`INSERT INTO ${this.s}.chat_sessions (id, human_actor_id, ai_actor_id, runtime_id, runtime_kind, runtime_session_id, title, touched_at, created_at, workspace, analyzed_run_id, project_id)
|
|
189067
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)`,
|
|
189068
|
-
[s2.id, s2.humanActorId, s2.aiActorId, s2.runtimeId, s2.runtimeKind ?? null, s2.runtimeSessionId ?? null, s2.title ?? null, s2.touchedAt, s2.createdAt, s2.workspace ?? null, s2.analyzedRunId ?? null, s2.projectId ?? null]
|
|
189506
|
+
`INSERT INTO ${this.s}.chat_sessions (id, human_actor_id, ai_actor_id, runtime_id, runtime_kind, runtime_session_id, title, touched_at, created_at, workspace, analyzed_run_id, project_id, escalation_id)
|
|
189507
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`,
|
|
189508
|
+
[s2.id, s2.humanActorId, s2.aiActorId, s2.runtimeId, s2.runtimeKind ?? null, s2.runtimeSessionId ?? null, s2.title ?? null, s2.touchedAt, s2.createdAt, s2.workspace ?? null, s2.analyzedRunId ?? null, s2.projectId ?? null, s2.escalationId ?? null]
|
|
189069
189509
|
);
|
|
189070
189510
|
}
|
|
189071
189511
|
async listSessions(humanActorId) {
|
|
@@ -189286,7 +189726,8 @@ var init_postgres_chat_sessions = __esm({
|
|
|
189286
189726
|
createdAt: new Date(row.created_at).toISOString(),
|
|
189287
189727
|
workspace: row.workspace ?? null,
|
|
189288
189728
|
analyzedRunId: row.analyzed_run_id ?? null,
|
|
189289
|
-
projectId: row.project_id ?? null
|
|
189729
|
+
projectId: row.project_id ?? null,
|
|
189730
|
+
escalationId: row.escalation_id ?? null
|
|
189290
189731
|
});
|
|
189291
189732
|
rowToSessionWithQuality = (row) => {
|
|
189292
189733
|
const quality = deriveLastTurnQuality({
|
|
@@ -190043,6 +190484,165 @@ var init_postgres_inbox_read = __esm({
|
|
|
190043
190484
|
}
|
|
190044
190485
|
});
|
|
190045
190486
|
|
|
190487
|
+
// ../storage/src/postgres-dispatches.ts
|
|
190488
|
+
function rowToRecord2(r) {
|
|
190489
|
+
return {
|
|
190490
|
+
id: r.id,
|
|
190491
|
+
targetKind: r.target_kind,
|
|
190492
|
+
targetId: r.target_id,
|
|
190493
|
+
workorderId: r.workorder_id,
|
|
190494
|
+
nodeId: r.node_id,
|
|
190495
|
+
actorId: r.actor_id,
|
|
190496
|
+
daemonId: r.daemon_id,
|
|
190497
|
+
jobKey: r.job_key,
|
|
190498
|
+
sessionRef: r.session_ref,
|
|
190499
|
+
createdAt: r.created_at instanceof Date ? r.created_at.toISOString() : String(r.created_at),
|
|
190500
|
+
startedAt: r.started_at ? r.started_at instanceof Date ? r.started_at.toISOString() : String(r.started_at) : null,
|
|
190501
|
+
endedAt: r.ended_at ? r.ended_at instanceof Date ? r.ended_at.toISOString() : String(r.ended_at) : null,
|
|
190502
|
+
outcome: r.outcome,
|
|
190503
|
+
exitCode: r.exit_code,
|
|
190504
|
+
reason: r.reason
|
|
190505
|
+
};
|
|
190506
|
+
}
|
|
190507
|
+
var ident17, PostgresDispatchStore;
|
|
190508
|
+
var init_postgres_dispatches = __esm({
|
|
190509
|
+
"../storage/src/postgres-dispatches.ts"() {
|
|
190510
|
+
"use strict";
|
|
190511
|
+
ident17 = (schema) => schema.replace(/[^a-zA-Z0-9_]/g, "");
|
|
190512
|
+
PostgresDispatchStore = class _PostgresDispatchStore {
|
|
190513
|
+
constructor(pool, s2) {
|
|
190514
|
+
this.pool = pool;
|
|
190515
|
+
this.s = s2;
|
|
190516
|
+
}
|
|
190517
|
+
static async open(pool, schema = "public") {
|
|
190518
|
+
const s2 = ident17(schema);
|
|
190519
|
+
await pool.query(`CREATE SCHEMA IF NOT EXISTS "${s2}"`);
|
|
190520
|
+
await pool.query(`
|
|
190521
|
+
CREATE TABLE IF NOT EXISTS "${s2}".dispatches (
|
|
190522
|
+
id text PRIMARY KEY,
|
|
190523
|
+
target_kind text NOT NULL CHECK (target_kind IN ('work','review','chat')),
|
|
190524
|
+
target_id text NOT NULL,
|
|
190525
|
+
workorder_id text,
|
|
190526
|
+
node_id text,
|
|
190527
|
+
actor_id text NOT NULL,
|
|
190528
|
+
daemon_id text,
|
|
190529
|
+
job_key text,
|
|
190530
|
+
session_ref text,
|
|
190531
|
+
created_at timestamptz NOT NULL,
|
|
190532
|
+
started_at timestamptz,
|
|
190533
|
+
ended_at timestamptz,
|
|
190534
|
+
outcome text CHECK (outcome IN ('completed','failed','refused','superseded','cancelled')),
|
|
190535
|
+
exit_code integer,
|
|
190536
|
+
reason text,
|
|
190537
|
+
-- \u5F62\u72B6\u81EA\u6821\u9A8C\uFF1Akind \u4E0E target id \u524D\u7F00\u4E00\u4E00\u5BF9\u5E94\uFF08chat \u6D3E\u53D1\u7684\u76EE\u6807\u662F artifact:chat:<sessionId>\uFF0C
|
|
190538
|
+
-- \u89C1\u63D0\u6848 \xA74\uFF1Bchat \u4E3A P0 \u7559\u4F4D\u3001\u6682\u4E0D\u5199\u5165\uFF09
|
|
190539
|
+
CONSTRAINT dispatches_target_shape CHECK (
|
|
190540
|
+
(target_kind = 'work' AND target_id LIKE 'wk:%') OR
|
|
190541
|
+
(target_kind = 'review' AND target_id LIKE 'rv:%') OR
|
|
190542
|
+
(target_kind = 'chat' AND target_id LIKE 'artifact:chat:%')
|
|
190543
|
+
),
|
|
190544
|
+
-- \u5173\u4E86\u884C\u5FC5\u6709\u8BF4\u6CD5\uFF1Aended_at \u4E0E outcome \u540C\u751F\u540C\u6B7B\uFF08\u540C works \u8868 I3 \u7684\u7EAA\u5F8B\uFF09
|
|
190545
|
+
CONSTRAINT dispatches_ended_needs_outcome CHECK ((ended_at IS NULL) = (outcome IS NULL))
|
|
190546
|
+
)`);
|
|
190547
|
+
await pool.query(`CREATE INDEX IF NOT EXISTS dispatches_target_idx ON "${s2}".dispatches (target_kind, target_id, created_at DESC)`);
|
|
190548
|
+
await pool.query(`CREATE INDEX IF NOT EXISTS dispatches_workorder_idx ON "${s2}".dispatches (workorder_id, created_at DESC)`);
|
|
190549
|
+
return new _PostgresDispatchStore(pool, s2);
|
|
190550
|
+
}
|
|
190551
|
+
/** write-ahead:派发意图产生的那一刻落 open 行(提案 I-D1)。同 id 重放安全(ON CONFLICT DO NOTHING)。 */
|
|
190552
|
+
async insertOpen(rec) {
|
|
190553
|
+
await this.pool.query(
|
|
190554
|
+
`INSERT INTO "${this.s}".dispatches (id, target_kind, target_id, workorder_id, node_id, actor_id, job_key, created_at)
|
|
190555
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT (id) DO NOTHING`,
|
|
190556
|
+
[
|
|
190557
|
+
rec.id,
|
|
190558
|
+
rec.targetKind,
|
|
190559
|
+
rec.targetId,
|
|
190560
|
+
rec.workorderId ?? null,
|
|
190561
|
+
rec.nodeId ?? null,
|
|
190562
|
+
rec.actorId,
|
|
190563
|
+
rec.jobKey ?? null,
|
|
190564
|
+
rec.at ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
190565
|
+
]
|
|
190566
|
+
);
|
|
190567
|
+
}
|
|
190568
|
+
/** session_started 帧:回填 started_at / session_ref / daemon_id(幂等:只填第一次)。 */
|
|
190569
|
+
async markStarted(id, opts) {
|
|
190570
|
+
await this.pool.query(
|
|
190571
|
+
`UPDATE "${this.s}".dispatches
|
|
190572
|
+
SET started_at = COALESCE(started_at, $2),
|
|
190573
|
+
session_ref = COALESCE(session_ref, $3),
|
|
190574
|
+
daemon_id = COALESCE(daemon_id, $4)
|
|
190575
|
+
WHERE id = $1`,
|
|
190576
|
+
[id, opts.at ?? (/* @__PURE__ */ new Date()).toISOString(), opts.sessionRef ?? null, opts.daemonId ?? null]
|
|
190577
|
+
);
|
|
190578
|
+
}
|
|
190579
|
+
/**
|
|
190580
|
+
* 关行(幂等):只有仍 open 的行会写 ended_at/outcome/reason;已关的行只补 exit_code
|
|
190581
|
+
* (提案 §6 注:kill 请求时刻先关行、晚到的 exit 帧只补退出码,不改 outcome)。
|
|
190582
|
+
*/
|
|
190583
|
+
async close(id, opts) {
|
|
190584
|
+
await this.pool.query(
|
|
190585
|
+
`UPDATE "${this.s}".dispatches
|
|
190586
|
+
SET ended_at = CASE WHEN ended_at IS NULL THEN $2::timestamptz ELSE ended_at END,
|
|
190587
|
+
outcome = CASE WHEN outcome IS NULL THEN $3 ELSE outcome END,
|
|
190588
|
+
reason = CASE WHEN ended_at IS NULL THEN $4 ELSE reason END,
|
|
190589
|
+
exit_code = COALESCE(exit_code, $5)
|
|
190590
|
+
WHERE id = $1`,
|
|
190591
|
+
[id, opts.at ?? (/* @__PURE__ */ new Date()).toISOString(), opts.outcome, opts.reason ?? null, opts.exitCode ?? null]
|
|
190592
|
+
);
|
|
190593
|
+
}
|
|
190594
|
+
/** cancel 漏斗兜底:只知道 target(workId/reviewId)时按 target 关掉在途行;返回被关的行。 */
|
|
190595
|
+
async closeOpenByTarget(targetKind, targetId, opts) {
|
|
190596
|
+
const res = await this.pool.query(
|
|
190597
|
+
`UPDATE "${this.s}".dispatches
|
|
190598
|
+
SET ended_at = $3::timestamptz, outcome = $4, reason = $5
|
|
190599
|
+
WHERE target_kind = $1 AND target_id = $2 AND ended_at IS NULL
|
|
190600
|
+
RETURNING *`,
|
|
190601
|
+
[targetKind, targetId, opts.at ?? (/* @__PURE__ */ new Date()).toISOString(), opts.outcome, opts.reason ?? null]
|
|
190602
|
+
);
|
|
190603
|
+
return res.rows.map(rowToRecord2);
|
|
190604
|
+
}
|
|
190605
|
+
/**
|
|
190606
|
+
* 按工单取全部派发行——timeline 视图一次捞整单,在内存里把 work/review 映射到 run
|
|
190607
|
+
* (`dispatches.id` = `agent_runs.id`),避免逐行查库。同一 target 多轮取**最新一条**由调用方决定。
|
|
190608
|
+
*/
|
|
190609
|
+
async listByWorkorder(workorderId) {
|
|
190610
|
+
const res = await this.pool.query(
|
|
190611
|
+
`SELECT * FROM "${this.s}".dispatches WHERE workorder_id = $1 ORDER BY created_at`,
|
|
190612
|
+
[workorderId]
|
|
190613
|
+
);
|
|
190614
|
+
return res.rows.map(rowToRecord2);
|
|
190615
|
+
}
|
|
190616
|
+
async getById(id) {
|
|
190617
|
+
const res = await this.pool.query(`SELECT * FROM "${this.s}".dispatches WHERE id = $1`, [id]);
|
|
190618
|
+
return res.rows[0] ? rowToRecord2(res.rows[0]) : null;
|
|
190619
|
+
}
|
|
190620
|
+
/** 从 target 找在途派发(P1 起 cancelSession 的映射兜底:内存 Map 丢了还能从表里找回 daemon_id 去杀)。 */
|
|
190621
|
+
async findOpenByTarget(targetKind, targetId) {
|
|
190622
|
+
const res = await this.pool.query(
|
|
190623
|
+
`SELECT * FROM "${this.s}".dispatches WHERE target_kind = $1 AND target_id = $2 AND ended_at IS NULL ORDER BY created_at`,
|
|
190624
|
+
[targetKind, targetId]
|
|
190625
|
+
);
|
|
190626
|
+
return res.rows.map(rowToRecord2);
|
|
190627
|
+
}
|
|
190628
|
+
/**
|
|
190629
|
+
* 对账兜底(提案 §6 末行):派发意图落了行、却从未 started 也从未关行的(如人类节点的派发——
|
|
190630
|
+
* 无 daemon 会话、exit 帧永不来,正是 dispatch-to-work.json 死账泄漏的形状)→ 关成 failed/never-started。
|
|
190631
|
+
* 返回关掉的行数,调用方决定要不要告警。
|
|
190632
|
+
*/
|
|
190633
|
+
async closeStaleNeverStarted(olderThanMs, at) {
|
|
190634
|
+
const res = await this.pool.query(
|
|
190635
|
+
`UPDATE "${this.s}".dispatches
|
|
190636
|
+
SET ended_at = $1::timestamptz, outcome = 'failed', reason = 'never-started'
|
|
190637
|
+
WHERE started_at IS NULL AND ended_at IS NULL AND created_at < $1::timestamptz - ($2 || ' milliseconds')::interval`,
|
|
190638
|
+
[at ?? (/* @__PURE__ */ new Date()).toISOString(), String(olderThanMs)]
|
|
190639
|
+
);
|
|
190640
|
+
return res.rowCount ?? 0;
|
|
190641
|
+
}
|
|
190642
|
+
};
|
|
190643
|
+
}
|
|
190644
|
+
});
|
|
190645
|
+
|
|
190046
190646
|
// ../storage/src/index.ts
|
|
190047
190647
|
var src_exports = {};
|
|
190048
190648
|
__export(src_exports, {
|
|
@@ -190054,6 +190654,7 @@ __export(src_exports, {
|
|
|
190054
190654
|
PostgresChannelStore: () => PostgresChannelStore,
|
|
190055
190655
|
PostgresChatSessionStore: () => PostgresChatSessionStore,
|
|
190056
190656
|
PostgresControlPlaneStore: () => PostgresControlPlaneStore,
|
|
190657
|
+
PostgresDispatchStore: () => PostgresDispatchStore,
|
|
190057
190658
|
PostgresHumanPrefsStore: () => PostgresHumanPrefsStore,
|
|
190058
190659
|
PostgresModelPriceStore: () => PostgresModelPriceStore,
|
|
190059
190660
|
PostgresNodeStore: () => PostgresNodeStore,
|
|
@@ -190066,6 +190667,7 @@ __export(src_exports, {
|
|
|
190066
190667
|
PostgresTypeRegistryStore: () => PostgresTypeRegistryStore,
|
|
190067
190668
|
backfillTypeRegistryFromFile: () => backfillTypeRegistryFromFile,
|
|
190068
190669
|
createPgPool: () => createPgPool,
|
|
190670
|
+
dropSchemaIfExists: () => dropSchemaIfExists,
|
|
190069
190671
|
hasPgUnstorable: () => hasPgUnstorable,
|
|
190070
190672
|
scrubPgJson: () => scrubPgJson,
|
|
190071
190673
|
scrubPgString: () => scrubPgString
|
|
@@ -190089,6 +190691,7 @@ var init_src9 = __esm({
|
|
|
190089
190691
|
init_postgres_type_registry();
|
|
190090
190692
|
init_postgres_actor_memory();
|
|
190091
190693
|
init_postgres_inbox_read();
|
|
190694
|
+
init_postgres_dispatches();
|
|
190092
190695
|
}
|
|
190093
190696
|
});
|
|
190094
190697
|
|
|
@@ -190838,6 +191441,16 @@ async function startServe(opts) {
|
|
|
190838
191441
|
convergenceUnified
|
|
190839
191442
|
});
|
|
190840
191443
|
const kernel = newEngine.kernel;
|
|
191444
|
+
const dispatchLedger = await PostgresDispatchStore.open(pgPool, pgSchema);
|
|
191445
|
+
const ledgerWarn = (label) => (err) => console.warn(`[dispatch-ledger] ${label}: ${String(err)}`);
|
|
191446
|
+
{
|
|
191447
|
+
const t = setInterval(() => {
|
|
191448
|
+
dispatchLedger.closeStaleNeverStarted(10 * 6e4).then((n) => {
|
|
191449
|
+
if (n > 0) console.log(`[dispatch-ledger] \u5BF9\u8D26\uFF1A\u5173\u95ED ${n} \u6761 never-started \u6D3E\u53D1\u884C`);
|
|
191450
|
+
}).catch(ledgerWarn("\u5BF9\u8D26"));
|
|
191451
|
+
}, 5 * 6e4);
|
|
191452
|
+
t.unref();
|
|
191453
|
+
}
|
|
190841
191454
|
console.log(`[serve] \u2605 \u65B0\u5F15\u64CE\u5DF2\u542F\u7528\uFF08schema=engine\uFF09\u2014\u2014 14 \u8868\u5173\u7CFB\u6A21\u578B + scan \u8C03\u5EA6 + \u4E09\u5C42\u81EA\u6108`);
|
|
190842
191455
|
console.log(
|
|
190843
191456
|
`[serve] \u6536\u655B\u95F8\uFF08convergence-unified\uFF09\uFF1A${convergenceUnified ? `\u26A0 \u5DF2\u542F\u7528 \u2014\u2014 manual conclude \u6321\u7EAF\u6765\u4FE1\u3001\u9690\u5F62\u6765\u4FE1\u6FC0\u6D3B\u3001\u56DB\u8F74\u5BF9\u79F0\u3001park \u5168\u8F74\u8C41\u514D\uFF08${process.env.NODE_ENV === "production" ? "prod \u663E\u5F0F\u5F00\u542F" : "\u672C\u5730\u9ED8\u8BA4\u5F00"}\uFF09` : "\u5173 \u2014\u2014 \u73B0\u72B6\u884C\u4E3A\uFF08prod \u9996\u5468\u9ED8\u8BA4\u5173\uFF0C\u8BBE OASIS_CONVERGENCE_UNIFIED=1 \u663E\u5F0F\u5F00\u542F\uFF09"}`
|
|
@@ -191485,6 +192098,8 @@ async function startServe(opts) {
|
|
|
191485
192098
|
...engine.assets ? { assets: engine.assets } : {},
|
|
191486
192099
|
// ★ 新引擎 store:blob GC 从 work_artifacts 扫根(事实源=实体表,oplog 已退役)。
|
|
191487
192100
|
engineStore: engine.kernel.getStore?.(),
|
|
192101
|
+
// ★ 派发台账(proposals/dispatch-ledger):timeline 据此把 work/review 硬关联到 agent_runs。
|
|
192102
|
+
dispatchesOfWorkorder: (wid) => dispatchLedger.listByWorkorder(wid),
|
|
191488
192103
|
// ★ 事件流:bus.onEvent 驱动 /api/events SSE(替代 oplog.subscribe);recentEvents 回放/调试。
|
|
191489
192104
|
engineEvents: (fn) => {
|
|
191490
192105
|
const bus = engine.kernel.getBus?.();
|
|
@@ -192331,6 +192946,7 @@ async function startServe(opts) {
|
|
|
192331
192946
|
if (msg.type === "hello" && msg.meta.activeSessions?.length) {
|
|
192332
192947
|
enqueueChatRecovery("hello \u5BF9\u8D26", () => recoverChatTurns(daemonId, msg.meta.activeSessions));
|
|
192333
192948
|
} else if (msg.type === "session_started") {
|
|
192949
|
+
dispatchLedger.markStarted(msg.dispatchId, { sessionRef: msg.sessionId, daemonId }).catch(ledgerWarn("markStarted"));
|
|
192334
192950
|
const prodMapping = dispatchToWork.get(msg.dispatchId);
|
|
192335
192951
|
if (prodMapping && busRef) {
|
|
192336
192952
|
workToSession.set(prodMapping.workId, { dispatchId: msg.dispatchId, daemonId });
|
|
@@ -192362,6 +192978,11 @@ async function startServe(opts) {
|
|
|
192362
192978
|
}).catch((err) => console.error(`[new-engine] review.started submit \u5931\u8D25:`, err));
|
|
192363
192979
|
}
|
|
192364
192980
|
} else if (msg.type === "session_exited") {
|
|
192981
|
+
dispatchLedger.close(msg.dispatchId, {
|
|
192982
|
+
outcome: msg.info.code === 0 ? "completed" : "failed",
|
|
192983
|
+
exitCode: msg.info.code ?? null,
|
|
192984
|
+
reason: msg.info.reason ?? null
|
|
192985
|
+
}).catch(ledgerWarn("close(exit)"));
|
|
192365
192986
|
const dispatchOwnedAtReceipt = allDispatchers().some((d) => d.inFlightSessions().some((x2) => x2.sessionId === msg.dispatchId));
|
|
192366
192987
|
enqueueChatRecovery("exit \u5BF9\u8D26", async () => {
|
|
192367
192988
|
const chatHandled = await reconcileChatExitFrame({
|
|
@@ -192391,7 +193012,6 @@ async function startServe(opts) {
|
|
|
192391
193012
|
}
|
|
192392
193013
|
}).catch((err) => console.error(`[new-engine] work.response submit \u5931\u8D25:`, err));
|
|
192393
193014
|
dispatchToWork.delete(msg.dispatchId);
|
|
192394
|
-
saveDispatchToWork();
|
|
192395
193015
|
if (mapping) workToSession.delete(mapping.workId);
|
|
192396
193016
|
} else if (busRef) {
|
|
192397
193017
|
await fallbackDispatchExitToWork(msg, busRef);
|
|
@@ -192452,27 +193072,34 @@ async function startServe(opts) {
|
|
|
192452
193072
|
chatRemoteAdapter = new DaemonHubAdapter(hub, { ...hubAdapterOpts, stashUnknownFrames: true });
|
|
192453
193073
|
dispatchRemoteAdapter = new DaemonHubAdapter(hub, { ...hubAdapterOpts, stashUnknownFrames: true });
|
|
192454
193074
|
let newEngineProduce = null;
|
|
193075
|
+
let newEngineReplyWork = null;
|
|
192455
193076
|
let newEngineReview = null;
|
|
192456
193077
|
const dispatchedWorks = /* @__PURE__ */ new Set();
|
|
192457
193078
|
const dispatchedReviews = /* @__PURE__ */ new Set();
|
|
192458
193079
|
const dispatchToWork = /* @__PURE__ */ new Map();
|
|
192459
|
-
|
|
192460
|
-
function loadDispatchToWork() {
|
|
193080
|
+
async function rebuildDispatchMappings(store) {
|
|
192461
193081
|
try {
|
|
192462
|
-
|
|
192463
|
-
|
|
192464
|
-
|
|
192465
|
-
|
|
193082
|
+
const ids2 = await store.transaction((tx) => tx.listActiveWorkorders());
|
|
193083
|
+
let works = 0, reviews = 0;
|
|
193084
|
+
for (const wid of ids2) {
|
|
193085
|
+
const snap = await store.transaction((tx) => tx.loadWorkorder(wid));
|
|
193086
|
+
if (!snap) continue;
|
|
193087
|
+
for (const w2 of snap.works) {
|
|
193088
|
+
if (!w2.endedAt && !w2.deadAt && w2.sessionRef) {
|
|
193089
|
+
dispatchToWork.set(w2.sessionRef, { workId: w2.id, workorderId: wid });
|
|
193090
|
+
works++;
|
|
193091
|
+
}
|
|
193092
|
+
}
|
|
193093
|
+
for (const r of snap.reviews) {
|
|
193094
|
+
if (!r.endedAt && !r.cancelledAt && !r.verdict && r.sessionRef) {
|
|
193095
|
+
dispatchToReview.set(r.sessionRef, { reviewId: r.id, workorderId: wid, nodeId: r.nodeId });
|
|
193096
|
+
reviews++;
|
|
193097
|
+
}
|
|
193098
|
+
}
|
|
192466
193099
|
}
|
|
193100
|
+
if (works || reviews) console.log(`[new-engine] \u4ECE\u5B9E\u4F53\u8868\u91CD\u5EFA dispatch \u6620\u5C04\uFF1Awork ${works} \u6761\u3001review ${reviews} \u6761`);
|
|
192467
193101
|
} catch (err) {
|
|
192468
|
-
console.warn(`[new-engine] \
|
|
192469
|
-
}
|
|
192470
|
-
}
|
|
192471
|
-
function saveDispatchToWork() {
|
|
192472
|
-
try {
|
|
192473
|
-
fs25.writeFileSync(dispatchToWorkFile, JSON.stringify(Object.fromEntries(dispatchToWork), null, 2), { mode: 384 });
|
|
192474
|
-
} catch (err) {
|
|
192475
|
-
console.warn(`[new-engine] \u4FDD\u5B58 dispatch\u2192work \u6620\u5C04\u5931\u8D25: ${String(err)}`);
|
|
193102
|
+
console.warn(`[new-engine] \u91CD\u5EFA dispatch \u6620\u5C04\u5931\u8D25: ${String(err)}`);
|
|
192476
193103
|
}
|
|
192477
193104
|
}
|
|
192478
193105
|
const workToSession = /* @__PURE__ */ new Map();
|
|
@@ -192481,7 +193108,7 @@ async function startServe(opts) {
|
|
|
192481
193108
|
let busRef = null;
|
|
192482
193109
|
async function fallbackDispatchExitToWork(msg, bus) {
|
|
192483
193110
|
try {
|
|
192484
|
-
const entry =
|
|
193111
|
+
const entry = journalRing.find(
|
|
192485
193112
|
(e) => {
|
|
192486
193113
|
if (e.kind !== "dispatched" && e.kind !== "recovered") return false;
|
|
192487
193114
|
return e.dispatchId === msg.dispatchId;
|
|
@@ -192523,7 +193150,10 @@ async function startServe(opts) {
|
|
|
192523
193150
|
{
|
|
192524
193151
|
const newKernel = newEngine.kernel;
|
|
192525
193152
|
if (newKernel && newKernel.setEngineIO) {
|
|
192526
|
-
|
|
193153
|
+
{
|
|
193154
|
+
const engStore = newKernel.getStore?.();
|
|
193155
|
+
if (engStore) void rebuildDispatchMappings(engStore);
|
|
193156
|
+
}
|
|
192527
193157
|
newKernel.setEngineIO({
|
|
192528
193158
|
async dispatchWork(workId) {
|
|
192529
193159
|
if (!newEngineProduce) return;
|
|
@@ -192534,13 +193164,29 @@ async function startServe(opts) {
|
|
|
192534
193164
|
const rev = kernelModel.revisions.get(workId);
|
|
192535
193165
|
if (!rev) return;
|
|
192536
193166
|
if (rev.state === "merged") return;
|
|
192537
|
-
|
|
193167
|
+
const replyIssueId = rev.replyToIssueId ?? null;
|
|
193168
|
+
console.log(`[new-engine] dispatchWork ${workId} \u2192 ${rev.artifactId} / ${rev.author}${replyIssueId ? `\uFF08\u56DE\u4FE1 ${replyIssueId}\uFF09` : ""}`);
|
|
192538
193169
|
const art = kernelModel.artifacts.get(rev.artifactId);
|
|
192539
193170
|
const wid = art?.workspace ?? "";
|
|
193171
|
+
const ledgerId = `dispatch:${(0, import_node_crypto40.randomUUID)()}`;
|
|
193172
|
+
try {
|
|
193173
|
+
await dispatchLedger.insertOpen({
|
|
193174
|
+
id: ledgerId,
|
|
193175
|
+
targetKind: "work",
|
|
193176
|
+
targetId: workId,
|
|
193177
|
+
workorderId: wid || null,
|
|
193178
|
+
nodeId: rev.artifactId,
|
|
193179
|
+
actorId: rev.author,
|
|
193180
|
+
jobKey: replyIssueId ? `reply::${replyIssueId}::${rev.author}` : `produce::${rev.artifactId}`
|
|
193181
|
+
});
|
|
193182
|
+
} catch (err) {
|
|
193183
|
+
ledgerWarn("insertOpen(work)")(err);
|
|
193184
|
+
}
|
|
192540
193185
|
let result;
|
|
192541
193186
|
try {
|
|
192542
|
-
result = await newEngineProduce(rev.artifactId, rev.author);
|
|
193187
|
+
result = replyIssueId && newEngineReplyWork ? await newEngineReplyWork(rev.artifactId, rev.author, replyIssueId, ledgerId) : await newEngineProduce(rev.artifactId, rev.author, ledgerId);
|
|
192543
193188
|
} catch (err) {
|
|
193189
|
+
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch error: ${err instanceof Error ? err.message : String(err)}` }).catch(ledgerWarn("close(error)"));
|
|
192544
193190
|
console.log(`[new-engine] dispatchWork ${workId} \u6D3E\u53D1\u5F02\u5E38\uFF1A${String(err)} \u2192 \u7F6E work failed`);
|
|
192545
193191
|
if (busRef) {
|
|
192546
193192
|
await busRef.submit({
|
|
@@ -192560,9 +193206,9 @@ async function startServe(opts) {
|
|
|
192560
193206
|
}
|
|
192561
193207
|
if (result?.dispatchId) {
|
|
192562
193208
|
dispatchToWork.set(result.dispatchId, { workId, workorderId: wid });
|
|
192563
|
-
saveDispatchToWork();
|
|
192564
193209
|
dispatchedWorks.add(workId);
|
|
192565
193210
|
} else if (result?.kind === "spawn-failed" || result?.kind === "predispatch-timeout") {
|
|
193211
|
+
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch ${result.kind}` }).catch(ledgerWarn("close(spawn-failed)"));
|
|
192566
193212
|
console.log(`[new-engine] dispatchWork ${workId} \u6D3E\u53D1\u5931\u8D25\uFF08${result.kind}\uFF09\u2192 \u7F6E work failed`);
|
|
192567
193213
|
if (busRef) {
|
|
192568
193214
|
await busRef.submit({
|
|
@@ -192579,6 +193225,7 @@ async function startServe(opts) {
|
|
|
192579
193225
|
});
|
|
192580
193226
|
}
|
|
192581
193227
|
} else if (result?.kind) {
|
|
193228
|
+
dispatchLedger.close(ledgerId, { outcome: "superseded", reason: result.kind }).catch(ledgerWarn("close(skipped)"));
|
|
192582
193229
|
console.log(`[new-engine] dispatchWork ${workId} \u672C\u8F6E\u672A\u6D3E\u51FA\uFF08${result.kind}\uFF09\u2014\u2014\u4FDD\u6301 running\uFF0Cscan \u4E0B\u8F6E\u91CD\u8BD5`);
|
|
192583
193230
|
}
|
|
192584
193231
|
rev.state = "working";
|
|
@@ -192588,11 +193235,36 @@ async function startServe(opts) {
|
|
|
192588
193235
|
if (dispatchedReviews.has(reviewId)) return;
|
|
192589
193236
|
dispatchedReviews.add(reviewId);
|
|
192590
193237
|
console.log(`[new-engine] dispatchReview ${reviewId} \u2192 ${nodeId} / ${reviewerActorId}`);
|
|
192591
|
-
const
|
|
193238
|
+
const kernelModel = newKernel.model;
|
|
193239
|
+
const art = kernelModel.artifacts.get(nodeId);
|
|
193240
|
+
const revWid = art?.workspace ?? "";
|
|
193241
|
+
const ledgerId = `dispatch:${(0, import_node_crypto40.randomUUID)()}`;
|
|
193242
|
+
try {
|
|
193243
|
+
await dispatchLedger.insertOpen({
|
|
193244
|
+
id: ledgerId,
|
|
193245
|
+
targetKind: "review",
|
|
193246
|
+
targetId: reviewId,
|
|
193247
|
+
workorderId: revWid || null,
|
|
193248
|
+
nodeId,
|
|
193249
|
+
actorId: reviewerActorId,
|
|
193250
|
+
jobKey: `review::${nodeId}::${reviewerActorId}`
|
|
193251
|
+
});
|
|
193252
|
+
} catch (err) {
|
|
193253
|
+
ledgerWarn("insertOpen(review)")(err);
|
|
193254
|
+
}
|
|
193255
|
+
let result;
|
|
193256
|
+
try {
|
|
193257
|
+
result = await newEngineReview(nodeId, reviewerActorId, ledgerId);
|
|
193258
|
+
} catch (err) {
|
|
193259
|
+
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch error: ${err instanceof Error ? err.message : String(err)}` }).catch(ledgerWarn("close(review-error)"));
|
|
193260
|
+
throw err;
|
|
193261
|
+
}
|
|
192592
193262
|
if (result?.dispatchId) {
|
|
192593
|
-
|
|
192594
|
-
|
|
192595
|
-
|
|
193263
|
+
dispatchToReview.set(result.dispatchId, { reviewId, workorderId: revWid, nodeId });
|
|
193264
|
+
} else if (result?.kind === "spawn-failed" || result?.kind === "predispatch-timeout") {
|
|
193265
|
+
dispatchLedger.close(ledgerId, { outcome: "failed", reason: `dispatch ${result.kind}` }).catch(ledgerWarn("close(review-spawn-failed)"));
|
|
193266
|
+
} else if (result?.kind) {
|
|
193267
|
+
dispatchLedger.close(ledgerId, { outcome: "superseded", reason: result.kind }).catch(ledgerWarn("close(review-skipped)"));
|
|
192596
193268
|
}
|
|
192597
193269
|
},
|
|
192598
193270
|
async cancelSession(ref2) {
|
|
@@ -192601,6 +193273,7 @@ async function startServe(opts) {
|
|
|
192601
193273
|
console.log(`[new-engine] cancelSession workId=${ref2} dispatchId=${workSess.dispatchId} daemon=${workSess.daemonId}`);
|
|
192602
193274
|
hub.dispatch(workSess.daemonId, { type: "kill", dispatchId: workSess.dispatchId });
|
|
192603
193275
|
workToSession.delete(ref2);
|
|
193276
|
+
dispatchLedger.close(workSess.dispatchId, { outcome: "cancelled", reason: "session-cancelled" }).catch(ledgerWarn("close(cancel-work)"));
|
|
192604
193277
|
return;
|
|
192605
193278
|
}
|
|
192606
193279
|
const reviewSess = reviewToSession.get(ref2);
|
|
@@ -192608,6 +193281,18 @@ async function startServe(opts) {
|
|
|
192608
193281
|
console.log(`[new-engine] cancelSession reviewId=${ref2} dispatchId=${reviewSess.dispatchId} daemon=${reviewSess.daemonId}`);
|
|
192609
193282
|
hub.dispatch(reviewSess.daemonId, { type: "kill", dispatchId: reviewSess.dispatchId });
|
|
192610
193283
|
reviewToSession.delete(ref2);
|
|
193284
|
+
dispatchLedger.close(reviewSess.dispatchId, { outcome: "cancelled", reason: "session-cancelled" }).catch(ledgerWarn("close(cancel-review)"));
|
|
193285
|
+
return;
|
|
193286
|
+
}
|
|
193287
|
+
try {
|
|
193288
|
+
const kind = ref2.startsWith("rv:") ? "review" : "work";
|
|
193289
|
+
const closed = await dispatchLedger.closeOpenByTarget(kind, ref2, { outcome: "cancelled", reason: "session-cancelled (map-miss)" });
|
|
193290
|
+
for (const row of closed) {
|
|
193291
|
+
if (row.daemonId && hub) hub.dispatch(row.daemonId, { type: "kill", dispatchId: row.id });
|
|
193292
|
+
}
|
|
193293
|
+
if (closed.length) console.log(`[dispatch-ledger] cancelSession map-miss\uFF0C\u6309\u53F0\u8D26\u5173\u884C ${closed.length} \u6761\uFF08${ref2}\uFF09`);
|
|
193294
|
+
} catch (err) {
|
|
193295
|
+
ledgerWarn("cancel(map-miss)")(err);
|
|
192611
193296
|
}
|
|
192612
193297
|
},
|
|
192613
193298
|
async notify(_actorIds, _payload) {
|
|
@@ -192892,12 +193577,16 @@ async function startServe(opts) {
|
|
|
192892
193577
|
});
|
|
192893
193578
|
const slot = { dispatcher: d, logged: 0, ticking: false };
|
|
192894
193579
|
dispatchers.set(companyId, slot);
|
|
192895
|
-
newEngineProduce = async (artifactId, actorId) => {
|
|
192896
|
-
const result = await d.requestProduce({ artifactId, actor: actorId, bypassScheduling: true });
|
|
193580
|
+
newEngineProduce = async (artifactId, actorId, dispatchId) => {
|
|
193581
|
+
const result = await d.requestProduce({ artifactId, actor: actorId, bypassScheduling: true, ...dispatchId !== void 0 ? { dispatchId } : {} });
|
|
193582
|
+
return { kind: result.kind, dispatchId: result.kind === "dispatched" ? result.dispatchId : void 0 };
|
|
193583
|
+
};
|
|
193584
|
+
newEngineReplyWork = async (artifactId, actorId, issueId, dispatchId) => {
|
|
193585
|
+
const result = await d.requestReplyWork({ artifactId, actor: actorId, annotationId: issueId, bypassScheduling: true, ...dispatchId !== void 0 ? { dispatchId } : {} });
|
|
192897
193586
|
return { kind: result.kind, dispatchId: result.kind === "dispatched" ? result.dispatchId : void 0 };
|
|
192898
193587
|
};
|
|
192899
|
-
newEngineReview = async (artifactId, actorId) => {
|
|
192900
|
-
const result = await d.requestReview({ artifactId, actor: actorId, bypassScheduling: true });
|
|
193588
|
+
newEngineReview = async (artifactId, actorId, dispatchId) => {
|
|
193589
|
+
const result = await d.requestReview({ artifactId, actor: actorId, bypassScheduling: true, ...dispatchId !== void 0 ? { dispatchId } : {} });
|
|
192901
193590
|
return { kind: result.kind, dispatchId: result.kind === "dispatched" ? result.dispatchId : void 0 };
|
|
192902
193591
|
};
|
|
192903
193592
|
};
|
|
@@ -193874,6 +194563,9 @@ var HANDSHAKE_TIMEOUT_MS = 15e3;
|
|
|
193874
194563
|
var OUTBOX_TTL_MS = 5 * 6e4;
|
|
193875
194564
|
var OUTBOX_MAX = 500;
|
|
193876
194565
|
var PENDING_STREAM_MAX = 2e3;
|
|
194566
|
+
var AUTH_REJECT_BACKOFF_MAX_MS = 15 * 6e4;
|
|
194567
|
+
var AUTH_REJECT_GIVE_UP_AFTER = 3;
|
|
194568
|
+
var AUTH_REJECT_RE = /Unexpected server response:\s*(401|403)/;
|
|
193877
194569
|
async function preflightOasisAccess(job) {
|
|
193878
194570
|
const base = job.server.url.replace(/\/+$/, "");
|
|
193879
194571
|
try {
|
|
@@ -193910,6 +194602,8 @@ var DaemonWsClient = class {
|
|
|
193910
194602
|
this.handshakeTimeoutMs = opts.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS;
|
|
193911
194603
|
this.outboxMax = opts.outboxMaxFrames ?? OUTBOX_MAX;
|
|
193912
194604
|
this.workRoot = opts.workRoot;
|
|
194605
|
+
this.authRejectGiveUpAfter = opts.authRejectGiveUpAfter ?? AUTH_REJECT_GIVE_UP_AFTER;
|
|
194606
|
+
this.onAuthGiveUp = opts.onAuthGiveUp ?? (() => process.exit(0));
|
|
193913
194607
|
}
|
|
193914
194608
|
ws = null;
|
|
193915
194609
|
pingTimer = null;
|
|
@@ -193917,11 +194611,20 @@ var DaemonWsClient = class {
|
|
|
193917
194611
|
/** 收到 update 但有活跃会话时置真:延后到所有会话结束再自更新,避免打断执行中的 agent。 */
|
|
193918
194612
|
pendingUpdate = false;
|
|
193919
194613
|
/**
|
|
193920
|
-
* 回执 outbox(proposal run-ledger §4.2
|
|
194614
|
+
* 回执 outbox(proposal run-ledger §4.2):**服务端据以判决的帧**发出后先留一份,收到
|
|
193921
194615
|
* 服务端 ack 才删;ws 抖动坏窗口发失败/丢失的帧,在重连时全量重发——投递升级 at-least-once,
|
|
193922
194616
|
* 幽灵派发/身份拔河那类"帧掉进缝里"不再永久丢。内存态即可根治 ws 闪断(节点仍在);节点进程
|
|
193923
194617
|
* 重启丢 outbox 的更窄场景本就被服务端 L3 时钟扫描兜底(重启会话已死),故不落盘。按 dispatchId 键
|
|
193924
|
-
*
|
|
194618
|
+
* (一次派发的判决帧按生命周期先后覆盖:started → exited,后者信息严格更强,重复 ack 无害)。
|
|
194619
|
+
*
|
|
194620
|
+
* **范围为什么不只是 session_exited**(2026-08-13 生产实证):本 outbox 落地时(§4.2,07-07 17:17)
|
|
194621
|
+
* 的问题定义是「退出帧丢 → run 永久 running → 僵尸」,故只收闭合类。但就在两小时前(§4.4,07-07 15:13)
|
|
194622
|
+
* 落地的 dispatch-ack 为了**不动契约**,选了「复用既有帧」变体——超时未见 `session_started` 即判派发
|
|
194623
|
+
* 丢失。这一步把 session_started 从"可有可无的正向信号"升格成"判定这次派发死活的唯一凭据",而当时
|
|
194624
|
+
* 没人回头改这里的范围。后果实测:节点 0.4~8 秒就起了会话并发了 started 帧,因闪断丢在路上,服务端
|
|
194625
|
+
* 15s 判幽灵、用户看到"对话失败",而那条会话又干净地跑了 260 秒(chat:7bf874f7)。
|
|
194626
|
+
* 判据一句话:**凡是服务端拿来做判决的帧,都必须走 outbox**;只做遥测/展示的(output/telemetry)走
|
|
194627
|
+
* sendStream 即可。
|
|
193925
194628
|
*/
|
|
193926
194629
|
outbox = /* @__PURE__ */ new Map();
|
|
193927
194630
|
/** ADR-0053 D4:在途的 GC 产物状态查询(requestId → resolver)。 */
|
|
@@ -193934,6 +194637,13 @@ var DaemonWsClient = class {
|
|
|
193934
194637
|
silenceTimeoutMs;
|
|
193935
194638
|
reconnectDelayMs;
|
|
193936
194639
|
handshakeTimeoutMs;
|
|
194640
|
+
/** 连续被认证拒绝的次数;任何一次成功 open 即清零(见 AUTH_REJECT_* 注释)。 */
|
|
194641
|
+
authRejectStreak = 0;
|
|
194642
|
+
/** 本次连接尝试是否因握手 401/403 失败——error 事件里置、close 事件里消费。 */
|
|
194643
|
+
lastErrorWasAuthReject = false;
|
|
194644
|
+
authRejectGiveUpAfter;
|
|
194645
|
+
/** 放弃时的动作;默认干净退出(exit 0),测试注入以免打死测试进程。 */
|
|
194646
|
+
onAuthGiveUp;
|
|
193937
194647
|
/**
|
|
193938
194648
|
* 工作区根,供 chat 文件浏览 handler 定位会话目录。缺省 undefined = 走 adapter 同款默认根
|
|
193939
194649
|
* (`~/.oasis/work`)——production 节点 adapter 也不带自定义 workRoot,两者恒同根。
|
|
@@ -193966,6 +194676,7 @@ var DaemonWsClient = class {
|
|
|
193966
194676
|
this.ws = ws;
|
|
193967
194677
|
ws.on("open", () => {
|
|
193968
194678
|
log2("[node-cli]", `connected id=${this.daemonId}`);
|
|
194679
|
+
this.authRejectStreak = 0;
|
|
193969
194680
|
this.lastReceivedAt = Date.now();
|
|
193970
194681
|
this.send({ type: "hello", daemonId: this.daemonId, meta: this.buildMeta() });
|
|
193971
194682
|
this.flushPendingStream();
|
|
@@ -193994,12 +194705,32 @@ var DaemonWsClient = class {
|
|
|
193994
194705
|
clearInterval(this.pingTimer);
|
|
193995
194706
|
this.pingTimer = null;
|
|
193996
194707
|
}
|
|
193997
|
-
if (
|
|
194708
|
+
if (this.stopped) return;
|
|
194709
|
+
const authRejected = this.lastErrorWasAuthReject;
|
|
194710
|
+
this.lastErrorWasAuthReject = false;
|
|
194711
|
+
if (!authRejected) {
|
|
194712
|
+
this.authRejectStreak = 0;
|
|
193998
194713
|
log2("[node-cli]", `disconnected \u2014 reconnecting in ${this.reconnectDelayMs}ms`);
|
|
193999
194714
|
setTimeout(() => this.connect(), this.reconnectDelayMs);
|
|
194715
|
+
return;
|
|
194716
|
+
}
|
|
194717
|
+
this.authRejectStreak += 1;
|
|
194718
|
+
if (this.authRejectStreak >= this.authRejectGiveUpAfter) {
|
|
194719
|
+
log2("[node-cli]", `\u670D\u52A1\u7AEF\u8FDE\u7EED ${this.authRejectStreak} \u6B21\u62D2\u7EDD\u672C\u8282\u70B9\u7684\u51ED\u636E\uFF08\u63E1\u624B 401/403\uFF09\u2014\u2014\u672C\u673A\u7684 node token \u5DF2\u5931\u6548\uFF0C\u6216\u8BE5\u8282\u70B9\u5DF2\u5728\u63A7\u5236\u53F0\u88AB\u79FB\u9664\u3002`);
|
|
194720
|
+
log2("[node-cli]", ` \u91CD\u8BD5\u4E0D\u4F1A\u8BA9\u5B83\u53D8\u597D\uFF1A\u8BF7\u5728 Oasis \u63A7\u5236\u53F0\u91CD\u65B0\u751F\u6210\u672C\u8282\u70B9\u7684\u5165\u7F51\u811A\u672C\uFF0C\u5E76\u5728\u672C\u673A\u91CD\u65B0\u8DD1\u4E00\u904D\u3002`);
|
|
194721
|
+
log2("[node-cli]", ` \u5B88\u62A4\u8FDB\u7A0B\u5C31\u6B64\u5E72\u51C0\u9000\u51FA\uFF08exit 0\uFF09\uFF0Csystemd \u7684 Restart=on-failure \u4E0D\u4F1A\u628A\u5B83\u62C9\u8D77\u6765\u3002`);
|
|
194722
|
+
this.stopped = true;
|
|
194723
|
+
this.onAuthGiveUp();
|
|
194724
|
+
return;
|
|
194000
194725
|
}
|
|
194726
|
+
const delay = Math.min(this.reconnectDelayMs * 2 ** this.authRejectStreak, AUTH_REJECT_BACKOFF_MAX_MS);
|
|
194727
|
+
log2("[node-cli]", `\u8BA4\u8BC1\u88AB\u62D2\uFF08\u7B2C ${this.authRejectStreak} \u6B21\uFF09\u2014\u2014\u51ED\u636E\u53EF\u80FD\u5DF2\u5931\u6548\uFF1Breconnecting in ${delay}ms`);
|
|
194728
|
+
setTimeout(() => this.connect(), delay);
|
|
194729
|
+
});
|
|
194730
|
+
ws.on("error", (err) => {
|
|
194731
|
+
log2("[node-cli]", `ws error: ${err.message}`);
|
|
194732
|
+
if (AUTH_REJECT_RE.test(err.message)) this.lastErrorWasAuthReject = true;
|
|
194001
194733
|
});
|
|
194002
|
-
ws.on("error", (err) => log2("[node-cli]", `ws error: ${err.message}`));
|
|
194003
194734
|
}
|
|
194004
194735
|
async handleMessage(msg) {
|
|
194005
194736
|
switch (msg.type) {
|
|
@@ -194033,7 +194764,7 @@ var DaemonWsClient = class {
|
|
|
194033
194764
|
...job.action ? { action: job.action } : {}
|
|
194034
194765
|
});
|
|
194035
194766
|
log2("[node-cli]", ` \u2192 session_started id=${handle.id} setup=${Date.now() - t0}ms`);
|
|
194036
|
-
this.
|
|
194767
|
+
this.sendKeyFrame(dispatchId, { type: "session_started", dispatchId, sessionId: handle.id, jobArtifactId: job.artifactId });
|
|
194037
194768
|
handle.onOutput?.((chunk) => this.sendStream({ type: "session_output", dispatchId, chunk }));
|
|
194038
194769
|
handle.onTelemetry?.((event) => this.sendStream({ type: "session_event", dispatchId, event }));
|
|
194039
194770
|
handle.onExit((info) => {
|
|
@@ -194165,8 +194896,14 @@ var DaemonWsClient = class {
|
|
|
194165
194896
|
for (const msg of this.pendingStream.splice(0)) this.send(msg);
|
|
194166
194897
|
}
|
|
194167
194898
|
/**
|
|
194168
|
-
*
|
|
194169
|
-
* 静默丢弃、但帧留在 outbox,重连时 resendOutbox 全量重发;收到服务端 ack 才
|
|
194899
|
+
* 发送判决类关键帧(session_started / session_exited):先入 outbox(收到 ack 前不删),再尝试发。
|
|
194900
|
+
* ws 非 OPEN 时 send 静默丢弃、但帧留在 outbox,重连时 resendOutbox 全量重发;收到服务端 ack 才
|
|
194901
|
+
* outbox.delete。
|
|
194902
|
+
*
|
|
194903
|
+
* 服务端当前只对 session_exited 回 ack(daemon-hub),故 started 那条要么被同 dispatchId 的 exited
|
|
194904
|
+
* 覆盖(正常路径,秒级到分钟级),要么在重连时被 TTL 淘汰——**不给 started 单独加 ack 是有意的**:
|
|
194905
|
+
* ack 帧只带 dispatchId、不带类型,服务端若为 started 回 ack,节点会误删同槽位上排队的 exited,
|
|
194906
|
+
* 把一个窄的丢帧窗口换成另一个。等价能力已由"覆盖 + TTL"提供,不值得为它动契约。
|
|
194170
194907
|
*/
|
|
194171
194908
|
sendKeyFrame(dispatchId, frame) {
|
|
194172
194909
|
if (this.outbox.size >= this.outboxMax && !this.outbox.has(dispatchId)) {
|
|
@@ -195408,16 +196145,16 @@ var COMMAND_DECLS = {
|
|
|
195408
196145
|
examples: ["oasis resolve-gap artifact:dev:abc --gap gap-001"]
|
|
195409
196146
|
},
|
|
195410
196147
|
"resolve-escalation": {
|
|
195411
|
-
usage: "oasis resolve-escalation
|
|
195412
|
-
description: "\
|
|
195413
|
-
positional: [
|
|
195414
|
-
{ name: "<artifactId>", required: true, desc: "\u4EA7\u7269 id" }
|
|
195415
|
-
],
|
|
196148
|
+
usage: "oasis resolve-escalation [--escalation <id>] [--reason <t>]",
|
|
196149
|
+
description: "\u5173\u95ED\u4E00\u6761\u4E0A\u62A5\uFF08escalation\uFF09\u3002\u8F6C\u6210\u5F85\u4EBA\u786E\u8BA4\u7684\u5361\u7247\u3001\u4E0D\u76F4\u63A5\u751F\u6548\u3002",
|
|
195416
196150
|
flags: [
|
|
195417
|
-
{ name: "escalation", desc: "\
|
|
195418
|
-
{ name: "reason", desc: "\
|
|
196151
|
+
{ name: "escalation", desc: "\u4E0A\u62A5 id\uFF08\u5F62\u5982 esc:<artifactId>:\u2026\uFF0C\u89C1 `oasis status` \u7684\u300C\u4E0A\u62A5\u300D\u6BB5\uFF09\u3002\u4ECE\u4E0A\u62A5\u5361\u300C\u6C9F\u901A\u804A\u804A\u300D\u5F00\u7684\u4F1A\u8BDD\u91CC\u53EF\u7701\u7565\u2014\u2014\u670D\u52A1\u7AEF\u636E\u4F1A\u8BDD\u89E3\u6790\uFF1B\u81EA\u4E3B\u5206\u8BCA\u4EFB\u52A1\u4E66\u4F1A\u76F4\u63A5\u7ED9\u4F60" },
|
|
196152
|
+
{ name: "reason", desc: "\u4E3A\u4EC0\u4E48\u73B0\u5728\u53EF\u4EE5\u5173\uFF08\u8FDB\u64CD\u4F5C\u65E5\u5FD7\uFF0C\u4E5F\u662F\u4EBA\u5728\u786E\u8BA4\u5361\u4E0A\u770B\u5230\u7684\u5224\u65AD\u6750\u6599\uFF09" }
|
|
195419
196153
|
],
|
|
195420
|
-
examples: [
|
|
196154
|
+
examples: [
|
|
196155
|
+
'oasis resolve-escalation --reason "\u51ED\u8BC1\u8865\u4E0A\u4E86"',
|
|
196156
|
+
'oasis resolve-escalation --escalation esc:artifact:cd:abc:9f2c1e --reason "\u5DF2\u6362\u673A\u5668"'
|
|
196157
|
+
]
|
|
195421
196158
|
},
|
|
195422
196159
|
// —— 治理(§11) ——
|
|
195423
196160
|
hold: {
|
|
@@ -195442,17 +196179,6 @@ var COMMAND_DECLS = {
|
|
|
195442
196179
|
],
|
|
195443
196180
|
examples: ['oasis release artifact:dev:abc --reason "\u4E0A\u6E38\u5DF2\u66F4\u65B0"']
|
|
195444
196181
|
},
|
|
195445
|
-
reopen: {
|
|
195446
|
-
usage: "oasis reopen <artifactId> [--note <t>]",
|
|
195447
|
-
description: "\u89E3\u5C01\u4EA7\u7269\uFF08lifecycle \u2192 active\uFF09\u3002",
|
|
195448
|
-
positional: [
|
|
195449
|
-
{ name: "<artifactId>", required: true, desc: "\u4EA7\u7269 id" }
|
|
195450
|
-
],
|
|
195451
|
-
flags: [
|
|
195452
|
-
{ name: "note", desc: "\u5907\u6CE8" }
|
|
195453
|
-
],
|
|
195454
|
-
examples: ["oasis reopen artifact:dev:abc"]
|
|
195455
|
-
},
|
|
195456
196182
|
"force-conclude": {
|
|
195457
196183
|
usage: "oasis force-conclude <artifactId> --reason <t>",
|
|
195458
196184
|
description: "\u8C41\u514D gate \u5F3A\u5236\u6536\u5C3E\u2014\u2014\u673A\u68B0\u68C0\u67E5\u7167\u8DD1\u3001\u91CC\u7A0B\u7891\u5E26 forced \u6807\u8BB0\u3002",
|
|
@@ -195913,7 +196639,6 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
|
|
|
195913
196639
|
var <reveal|list|set|rm> ... # reveal=\u53D6\u660E\u6587\u5165\u53E3\uFF1Blist/set/rm=\u7BA1\u7406\u9762\uFF08\u4EC5\u4EBA\u7C7B operator\uFF09
|
|
195914
196640
|
|
|
195915
196641
|
\u6CBB\u7406\uFF08\xA711\uFF09
|
|
195916
|
-
reopen <artifactId> [--note t] # \u89E3\u5C01\uFF08lifecycle \u2192 active\uFF09
|
|
195917
196642
|
force-conclude <artifactId> --reason <t> # \u8C41\u514D gate\uFF1B\u673A\u68B0\u68C0\u67E5\u7167\u8DD1\u3001\u91CC\u7A0B\u7891\u5E26 forced \u6807\u8BB0
|
|
195918
196643
|
intervene --file <plan.json> [--apply true] # \u7ED3\u6784\u53D8\u66F4\u539F\u5B50\u6279\uFF1Apreview\uFF08\u7F3A\u7701\uFF09\u2192 --apply \u843D\u5730
|
|
195919
196644
|
intervention draft --file <plan.json> # \xA711.8 \u8D77\u8349\u901A\u9053\uFF1A\u534F\u8C03\u8005\u63D0\u4EA4 C \u7C7B\u8349\u6848\u5F85\u4EBA\u786E\u8BA4\uFF08B \u7C7B\u53EF\u76F4\u63A5 intervene\uFF09
|
|
@@ -195923,7 +196648,7 @@ var USAGE = `oasis \u2014\u2014 artifact-centric \u534F\u4F5C\u5185\u6838 CLI\uF
|
|
|
195923
196648
|
plan --file <plan.json> [--apply true] # \u540C\u7BA1\u7EBF\u7684\u6210\u56FE\u4E8B\u52A1\uFF1Aspawn/link/annotate \u539F\u5B50\u843D\u5730\uFF08A3\uFF0C\u4EFB\u52A1\u4E66\u968F\u56FE\u8D70\uFF09
|
|
195924
196649
|
rework <artifactId> # A2 \u4EBA\u95F8"\u8FD4\u5DE5"\u6863\uFF1Abump \u6700\u65B0\uFF08\u65E0 ack\uFF09\u2192 \u70B9\u540D\u8FD4\u5DE5\u3001\u91CD\u65B0\u53EF\u6D3E
|
|
195925
196650
|
ack <artifactId> [--note t] # "\u786E\u8BA4\u6CBF\u7528"\uFF1A\u770B\u8FC7\u4E0A\u6E38\u65B0\u7248\u3001\u786E\u8BA4\u672C\u4EA7\u7269\u4E0D\u53D7\u5F71\u54CD \u2192 \u4E0D\u8FD4\u5DE5\uFF0C\u7EA7\u8054\u6B62\u4E8E\u6B64\u679D\uFF08--note \u5199\u6E05\u4E3A\u4EC0\u4E48\u4E0D\u53D7\u5F71\u54CD\uFF09
|
|
195926
|
-
# plan.json: { "reason": "...", "ops": [ {"action":"spawn"|"seal"|"
|
|
196651
|
+
# plan.json: { "reason": "...", "ops": [ {"action":"spawn"|"seal"|"link"|"unlink"|"pin"|"assign", ...} ] }
|
|
195927
196652
|
|
|
195928
196653
|
\u4EA4\u4ED8\u7269\u6587\u6863\u4F53\u7CFB
|
|
195929
196654
|
project create --name <name> [--goal <text>] [--slug <slug>] [--members <json-array>]
|
|
@@ -196019,7 +196744,7 @@ function renderGraphLines(graph) {
|
|
|
196019
196744
|
for (const n of graph.nodes) {
|
|
196020
196745
|
out.push(` ${n.id}`);
|
|
196021
196746
|
const systemNote = closureIds.has(n.id) ? " [\u672C\u5355\u672B\u7AEF\xB7\u7CFB\u7EDF\u7EF4\u62A4\uFF0C\u8FDE\u7EBF\u4E0E\u4EFB\u52A1\u4E66\u90FD\u4E0D\u7528\u4F60\u7BA1]" : "";
|
|
196022
|
-
out.push(` type=${n.type}${n.stage ? ` \u9636\u6BB5=${n.stage}` : ""}${n.blocked ? " [\u5361\u4F4F]" : ""} owner=${n.owner?.name ?? n.owner?.id ?? "?"}${n.label ? ` ${n.label}` : ""}${systemNote}`);
|
|
196747
|
+
out.push(` type=${n.type}${n.stage ? ` \u9636\u6BB5=${n.stage}` : ""}${n.blocked ? " [\u5361\u4F4F]" : ""}${n.unresolvedEscalations ? ` [\u4E0A\u62A5 ${n.unresolvedEscalations}]` : ""} owner=${n.owner?.name ?? n.owner?.id ?? "?"}${n.label ? ` ${n.label}` : ""}${systemNote}`);
|
|
196023
196748
|
}
|
|
196024
196749
|
const shown = graph.edges.filter((e) => !closureIds.has(e.from));
|
|
196025
196750
|
const folded = [...closureIds].filter((id) => graph.edges.some((e) => e.from === id));
|
|
@@ -196148,7 +196873,7 @@ function makeClient(base, token) {
|
|
|
196148
196873
|
}
|
|
196149
196874
|
};
|
|
196150
196875
|
}
|
|
196151
|
-
var STAGE_COMMANDS = /* @__PURE__ */ new Set(["link", "spawn", "unlink", "cancel", "freeze", "cancel-part", "edit", "assign"
|
|
196876
|
+
var STAGE_COMMANDS = /* @__PURE__ */ new Set(["link", "spawn", "unlink", "cancel", "freeze", "cancel-part", "edit", "assign"]);
|
|
196152
196877
|
function fieldsFromFlags(flags, ownFlags) {
|
|
196153
196878
|
const fields = {};
|
|
196154
196879
|
for (const [k2, v2] of flags) {
|
|
@@ -196210,8 +196935,6 @@ function buildStageOp(cmd, flags, positional, readFile6 = (file) => fs29.readFil
|
|
|
196210
196935
|
return { action: "cancelPart", artifactId: needPos(positional, 0, "oasis cancel-part <id> --part <name>"), part: need(flags, "part") };
|
|
196211
196936
|
case "assign":
|
|
196212
196937
|
return { action: "assign", artifactId: needPos(positional, 0, "oasis assign <id> --owner <actorId>"), owner: need(flags, "owner") };
|
|
196213
|
-
case "reopen":
|
|
196214
|
-
return { action: "reopen", artifactId: needPos(positional, 0, "oasis reopen <id> [--note <\u539F\u56E0>]"), ...flags.get("note") !== void 0 ? { note: flags.get("note") } : {} };
|
|
196215
196938
|
case "edit": {
|
|
196216
196939
|
const fields = fieldsFromFlags(flags, ownFlagsFromDecl("edit"));
|
|
196217
196940
|
return {
|
|
@@ -196241,8 +196964,6 @@ function describeStageOp(op) {
|
|
|
196241
196964
|
return `cancel-part ${op["artifactId"]} part=${op["part"]}`;
|
|
196242
196965
|
case "assign":
|
|
196243
196966
|
return `assign ${op["artifactId"]} \u2192 owner=${op["owner"]}`;
|
|
196244
|
-
case "reopen":
|
|
196245
|
-
return `reopen ${op["artifactId"]}\uFF08\u89E3\u5C01 \u2192 active\uFF09`;
|
|
196246
196967
|
case "edit":
|
|
196247
196968
|
return `edit ${op["artifactId"]}\uFF08${["title", "description"].filter((k2) => op[k2] !== void 0).join("/") || "?"}\uFF09`;
|
|
196248
196969
|
default:
|
|
@@ -197619,8 +198340,7 @@ ${res.warning}`);
|
|
|
197619
198340
|
}
|
|
197620
198341
|
case "resolve-escalation": {
|
|
197621
198342
|
const { message, data } = await api.cmd("resolveEscalation", {
|
|
197622
|
-
|
|
197623
|
-
escalationId: need(flags, "escalation"),
|
|
198343
|
+
...flags.get("escalation") !== void 0 ? { escalationId: flags.get("escalation") } : {},
|
|
197624
198344
|
...flags.get("reason") !== void 0 ? { reason: flags.get("reason") } : {}
|
|
197625
198345
|
});
|
|
197626
198346
|
println(message);
|
|
@@ -197872,14 +198592,6 @@ ${res.warning}`);
|
|
|
197872
198592
|
println(message);
|
|
197873
198593
|
break;
|
|
197874
198594
|
}
|
|
197875
|
-
case "reopen": {
|
|
197876
|
-
const { message } = await api.cmd("reopen", {
|
|
197877
|
-
artifactId: needPos(positional, 0, "oasis reopen <artifactId>"),
|
|
197878
|
-
note: flags.get("note")
|
|
197879
|
-
});
|
|
197880
|
-
println(message);
|
|
197881
|
-
break;
|
|
197882
|
-
}
|
|
197883
198595
|
case "force-conclude": {
|
|
197884
198596
|
const { message } = await api.cmd("forceConclude", {
|
|
197885
198597
|
artifactId: needPos(positional, 0, "oasis force-conclude <artifactId> --reason t"),
|
|
@@ -198245,7 +198957,7 @@ ${res.warning}`);
|
|
|
198245
198957
|
break;
|
|
198246
198958
|
}
|
|
198247
198959
|
case "content": {
|
|
198248
|
-
const id = needPos(positional, 0, "oasis content <artifactId|revisionId|annotationId|reviewId> [path]");
|
|
198960
|
+
const id = needPos(positional, 0, "oasis content <artifactId|revisionId|annotationId|reviewId|escalationId> [path]");
|
|
198249
198961
|
const path26 = positional[1];
|
|
198250
198962
|
const c = await api.view("content", id, path26 ? { path: path26 } : void 0);
|
|
198251
198963
|
if (c.kind === "empty") {
|
|
@@ -198284,6 +198996,14 @@ ${res.warning}`);
|
|
|
198284
198996
|
}
|
|
198285
198997
|
break;
|
|
198286
198998
|
}
|
|
198999
|
+
if (c.kind === "escalation") {
|
|
199000
|
+
println(`# ${c.escalationId} ${c.resolved ? "resolved" : "open"} by ${c.by}${c.hand ? `\uFF08\u7ECF ${c.hand} \u4EE3\u5F55\uFF09` : ""} ${c.at}`);
|
|
199001
|
+
println(`# \u6240\u5728\u8282\u70B9 ${c.artifactId}${c.nodeLabel ? `\uFF08${c.nodeLabel}\uFF09` : ""}${c.part ? ` part=${c.part}` : ""}${c.gapId ? ` \u5173\u8054 gap ${c.gapId}` : ""}`);
|
|
199002
|
+
if (c.resolved) println(`# \u5DF2\u5173\u95ED${c.resolvedReason ? `\uFF1A${c.resolvedReason}` : "\uFF08\u672A\u8BB0\u5F55\u539F\u56E0\uFF09"}`);
|
|
199003
|
+
println(`---`);
|
|
199004
|
+
println(c.reason);
|
|
199005
|
+
break;
|
|
199006
|
+
}
|
|
198287
199007
|
const at = c.at;
|
|
198288
199008
|
const which = at.isCurrentHead ? "current head" : "\u5386\u53F2\u7248\u672C";
|
|
198289
199009
|
println(`# ${at.artifactId} @ ${at.revisionId}\uFF08${which}\uFF09 by ${at.author} "${at.reason}"`);
|
|
@@ -198308,7 +199028,7 @@ ${res.warning}`);
|
|
|
198308
199028
|
const id = needPos(positional, 0, "oasis status <artifactId>");
|
|
198309
199029
|
const v2 = await api.view("show", id);
|
|
198310
199030
|
const headRev = v2.revisions.find((r) => r.id === v2.artifact.currentRev);
|
|
198311
|
-
const kind = !headRev ? "\uFF08\u7A7A\uFF0C\u5C1A\u65E0\u5185\u5BB9\uFF09" : headRev.contentKind === "external-pin" ? "\u4EE3\u7801/\u5916\u90E8(git)" : headRev.contentKind === "manifest" ? "\u591A\u6587\u4EF6" : "\u5355\u6587\u4EF6\u6587\u672C";
|
|
199031
|
+
const kind = !headRev || headRev.contentKind === "empty" ? "\uFF08\u7A7A\uFF0C\u5C1A\u65E0\u5185\u5BB9\uFF09" : headRev.contentKind === "external-pin" ? "\u4EE3\u7801/\u5916\u90E8(git)" : headRev.contentKind === "manifest" ? "\u591A\u6587\u4EF6" : "\u5355\u6587\u4EF6\u6587\u672C";
|
|
198312
199032
|
println(`${v2.artifact.id} type=${v2.artifact.type} owner=${v2.artifact.owner} lifecycle=${v2.lifecycle}`);
|
|
198313
199033
|
println(`current head : ${v2.artifact.currentRev ?? "\u2205"}`);
|
|
198314
199034
|
if (v2.concludedHead === null) {
|
|
@@ -198367,6 +199087,16 @@ ${res.warning}`);
|
|
|
198367
199087
|
println(`\u672A\u51B3 gap(${openGaps.length}):`);
|
|
198368
199088
|
for (const g2 of openGaps) println(` ${g2.gapId}${g2.part ? ` part=${g2.part}` : ""} by ${g2.by} \u300C${g2.description}\u300D`);
|
|
198369
199089
|
}
|
|
199090
|
+
const escs = v2.escalations ?? [];
|
|
199091
|
+
if (escs.length > 0) {
|
|
199092
|
+
const openN = escs.filter((e) => !e.resolved).length;
|
|
199093
|
+
println(`\u4E0A\u62A5(${escs.length}\uFF0C\u5176\u4E2D\u672A\u89E3 ${openN}) \u2190 \u53D6\u5168\u6587\u7528 \`oasis content <escalationId>\`\uFF1B\u5173\u4E00\u6761\u7528 \`oasis resolve-escalation --escalation <id> --reason "\u2026"\`\uFF1A`);
|
|
199094
|
+
for (const e of [...escs].reverse()) {
|
|
199095
|
+
const first = e.reason.split("\n")[0] ?? "";
|
|
199096
|
+
const closed = e.resolved ? ` \u2190 \u5DF2\u5173\u95ED${e.resolvedReason ? `\uFF1A${e.resolvedReason.split("\n")[0]}` : ""}` : "";
|
|
199097
|
+
println(` ${e.escalationId} ${e.resolved ? "resolved" : "open"} by ${e.by}${e.hand ? `\uFF08\u7ECF ${e.hand} \u4EE3\u5F55\uFF09` : ""} ${e.at}${e.gapId ? ` \u5173\u8054 gap ${e.gapId}` : ""} \u300C${first}\u300D${closed}`);
|
|
199098
|
+
}
|
|
199099
|
+
}
|
|
198370
199100
|
if (v2.blocked.blocked) println(`BLOCKED: ${v2.blocked.reasons.join("\uFF1B")}`);
|
|
198371
199101
|
if (v2.pendingGate) println(`\u5F85\u5BA1 gate: quorum=${v2.pendingGate.gate.quorum}`);
|
|
198372
199102
|
break;
|
|
@@ -198608,7 +199338,7 @@ function syncRuntimeAssets(candidateRoots, binDir) {
|
|
|
198608
199338
|
}
|
|
198609
199339
|
|
|
198610
199340
|
// src/index.ts
|
|
198611
|
-
var PKG_VERSION = true ? "0.1.
|
|
199341
|
+
var PKG_VERSION = true ? "0.1.99" : "dev";
|
|
198612
199342
|
var OASIS_DIR = path25.join(os10.homedir(), ".oasis");
|
|
198613
199343
|
var CONFIG_FILE = path25.join(OASIS_DIR, "node-config.json");
|
|
198614
199344
|
var PID_FILE = path25.join(OASIS_DIR, "node.pid");
|