oasis_test_v2 2.2.2 → 2.2.3
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 +1385 -324
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1190,6 +1190,9 @@ function deriveLastTurnQuality(lastAssistant) {
|
|
|
1190
1190
|
if (status === "error") return "abnormal";
|
|
1191
1191
|
return lastAssistant && lastAssistant.content.trim().length > 0 ? "clean" : "abnormal";
|
|
1192
1192
|
}
|
|
1193
|
+
function deriveSessionRunning(lastAssistant) {
|
|
1194
|
+
return lastAssistant?.status === "running";
|
|
1195
|
+
}
|
|
1193
1196
|
function windowMessagesByTurns(messages, turns) {
|
|
1194
1197
|
if (turns <= 0) return { messages, hasMoreBefore: false };
|
|
1195
1198
|
let seen = 0;
|
|
@@ -1340,11 +1343,39 @@ function collectDelegationArtifacts(roundSummaries) {
|
|
|
1340
1343
|
const artifacts = /* @__PURE__ */ new Map();
|
|
1341
1344
|
for (const round of roundSummaries) {
|
|
1342
1345
|
for (const file of round.files ?? []) {
|
|
1343
|
-
artifacts.
|
|
1346
|
+
const seen = artifacts.get(file.name);
|
|
1347
|
+
if (seen) {
|
|
1348
|
+
if (!seen.blobRef && file.blobRef) seen.blobRef = file.blobRef;
|
|
1349
|
+
continue;
|
|
1350
|
+
}
|
|
1351
|
+
artifacts.set(file.name, {
|
|
1352
|
+
name: file.name,
|
|
1353
|
+
...file.blobRef ? { blobRef: file.blobRef } : {},
|
|
1354
|
+
round: round.round,
|
|
1355
|
+
// settledSeq 透传自这个名字**第一次出现**的那一轮。未收口的轮次(pending 续跑那一支)
|
|
1356
|
+
// 与本字段落地前的存量行都没有值——前端凭 `settledSeq` 是否缺失走两条路径:
|
|
1357
|
+
// 有 → 按 settledSeq 各自定位对应 `chat-msg:delegation-done|<id>:<seq>`;
|
|
1358
|
+
// 无 → 该委派整条退回「单委派单锚点」降级(`ann:36bfa04b`)。
|
|
1359
|
+
...typeof round.settledSeq === "number" ? { settledSeq: round.settledSeq } : {}
|
|
1360
|
+
});
|
|
1344
1361
|
}
|
|
1345
1362
|
}
|
|
1346
1363
|
return [...artifacts.values()];
|
|
1347
1364
|
}
|
|
1365
|
+
function delegationOutboxFingerprints(roundSummaries) {
|
|
1366
|
+
const out = /* @__PURE__ */ new Map();
|
|
1367
|
+
for (const round of roundSummaries) {
|
|
1368
|
+
for (const file of round.files ?? []) {
|
|
1369
|
+
if (typeof file.size !== "number" || !file.mtime) continue;
|
|
1370
|
+
out.set(file.name, { size: file.size, mtime: file.mtime });
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return out;
|
|
1374
|
+
}
|
|
1375
|
+
function touchAnchorMessageId(messageId) {
|
|
1376
|
+
const hash2 = messageId.indexOf("#");
|
|
1377
|
+
return hash2 >= 0 ? messageId.slice(0, hash2) : messageId;
|
|
1378
|
+
}
|
|
1348
1379
|
var DelegationLabelConflictError, DELEGATION_FILE_LIMITS, DELEGATION_INBOUND_DIR, DELEGATION_OUTBOUND_DIR, DELEGATION_OUTPUTS_DIR, DELEGATION_RETURN_RECENT_ROUNDS;
|
|
1349
1380
|
var init_delegation = __esm({
|
|
1350
1381
|
"../contract/src/delegation.ts"() {
|
|
@@ -1537,6 +1568,16 @@ var init_http_api = __esm({
|
|
|
1537
1568
|
}
|
|
1538
1569
|
});
|
|
1539
1570
|
|
|
1571
|
+
// ../contract/src/file-preview.ts
|
|
1572
|
+
var OFFICE_PREVIEW_MAX_BYTES, FILE_CONTENT_MAX_BYTES;
|
|
1573
|
+
var init_file_preview = __esm({
|
|
1574
|
+
"../contract/src/file-preview.ts"() {
|
|
1575
|
+
"use strict";
|
|
1576
|
+
OFFICE_PREVIEW_MAX_BYTES = 20 * 1024 * 1024;
|
|
1577
|
+
FILE_CONTENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
|
|
1540
1581
|
// ../contract/src/index.ts
|
|
1541
1582
|
var init_src = __esm({
|
|
1542
1583
|
"../contract/src/index.ts"() {
|
|
@@ -1596,6 +1637,7 @@ var init_src = __esm({
|
|
|
1596
1637
|
init_knowledge();
|
|
1597
1638
|
init_knowledge_governance();
|
|
1598
1639
|
init_http_api();
|
|
1640
|
+
init_file_preview();
|
|
1599
1641
|
}
|
|
1600
1642
|
});
|
|
1601
1643
|
|
|
@@ -4036,7 +4078,7 @@ var init_events = __esm({
|
|
|
4036
4078
|
// ../engine/src/views.ts
|
|
4037
4079
|
function workState(w2, hasOutput2) {
|
|
4038
4080
|
if (w2.status) return w2.status;
|
|
4039
|
-
if (w2.retryAt) return "retry";
|
|
4081
|
+
if (w2.retryAt && w2.deadAt) return "retry";
|
|
4040
4082
|
if (w2.deadAt) return "dead";
|
|
4041
4083
|
if (w2.endedAt) {
|
|
4042
4084
|
if (w2.outcome === "failed") return "failed";
|
|
@@ -4458,6 +4500,36 @@ function lastPostIsFromNode(state, issue2, node2) {
|
|
|
4458
4500
|
function repliedInWork(state, work, issueId) {
|
|
4459
4501
|
return repliesSorted(state, issueId).some((r) => r.createdAt >= REPLY_ORIGIN_SINCE ? r.viaWorkId === work.id : r.authorActorId === work.assigneeActorId && r.createdAt >= work.createdAt);
|
|
4460
4502
|
}
|
|
4503
|
+
function recordHandledNotification(state, work, issue2, acknowledged = false) {
|
|
4504
|
+
if (issue2.aboutNodeId !== work.nodeId || work.endedAt || work.deadAt) return;
|
|
4505
|
+
const gap = issue2.gapId ? state.issue(issue2.gapId) : void 0;
|
|
4506
|
+
const ids2 = [issue2.id, ...gap?.kind === "gap" && gap.resolvedAt && gap.raisedByNodeId === work.nodeId ? [gap.id] : []];
|
|
4507
|
+
state.updateWork(work.id, {
|
|
4508
|
+
frozenIssueIds: [.../* @__PURE__ */ new Set([...work.frozenIssueIds ?? [], ...ids2])],
|
|
4509
|
+
...acknowledged ? { handledIssueIds: [.../* @__PURE__ */ new Set([...work.handledIssueIds ?? [], ...ids2])] } : {}
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4512
|
+
function notificationsHandledInWork(state, work) {
|
|
4513
|
+
if (work.conclusion != null) return false;
|
|
4514
|
+
const node2 = state.node(work.nodeId);
|
|
4515
|
+
const accepted = node2?.latestAcceptId ? state.work(node2.latestAcceptId) : void 0;
|
|
4516
|
+
if (!node2 || !accepted || node2.latestAcceptId !== node2.latestProposedWorkId || accepted.nodeVersion !== work.nodeVersion) return false;
|
|
4517
|
+
const previousInputs = new Set(state.workInputsOf(accepted.id).map((i) => i.upstreamWorkId));
|
|
4518
|
+
const currentInputs = new Set(state.workInputsOf(work.id).map((i) => i.upstreamWorkId));
|
|
4519
|
+
if (previousInputs.size !== currentInputs.size || [...currentInputs].some((id) => !previousInputs.has(id))) return false;
|
|
4520
|
+
const ids2 = work.frozenIssueIds ?? [];
|
|
4521
|
+
const handledNow = (work.handledIssueIds ?? []).length > 0 || ids2.some((id) => repliedInWork(state, work, id));
|
|
4522
|
+
return handledNow && ids2.length > 0 && ids2.every((id) => {
|
|
4523
|
+
const issue2 = state.issue(id);
|
|
4524
|
+
if (!issue2) return false;
|
|
4525
|
+
if (issue2.kind === "gap") return issue2.resolvedAt !== null && ((work.handledIssueIds ?? []).includes(id) || ids2.some((relatedId) => {
|
|
4526
|
+
const related = state.issue(relatedId);
|
|
4527
|
+
return related?.kind === "comment" && related.gapId === id && repliedInWork(state, work, relatedId) && lastPostIsFromNode(state, related, node2);
|
|
4528
|
+
}) || state.mainWorksOf(work.nodeId).some((prior) => prior.id !== work.id && prior.status === "success" && ((prior.handledIssueIds ?? []).includes(id) || prior.acceptedAt && (prior.frozenIssueIds ?? []).includes(id))));
|
|
4529
|
+
if (issue2.kind !== "comment" && issue2.kind !== "change_request") return false;
|
|
4530
|
+
return issue2.resolvedAt !== null && (work.handledIssueIds ?? []).includes(id) || issue2.kind === "comment" && repliedInWork(state, work, id) && lastPostIsFromNode(state, issue2, node2);
|
|
4531
|
+
});
|
|
4532
|
+
}
|
|
4461
4533
|
function threadLength(state, issueId) {
|
|
4462
4534
|
return state.repliesOf(issueId).length + 1;
|
|
4463
4535
|
}
|
|
@@ -4559,6 +4631,7 @@ var init_kill = __esm({
|
|
|
4559
4631
|
|
|
4560
4632
|
// ../engine/src/activate.ts
|
|
4561
4633
|
function scan(state, ctx) {
|
|
4634
|
+
if (state.workorder.dispatchPaused) return { events: [], terminated: false };
|
|
4562
4635
|
const events = [];
|
|
4563
4636
|
const now = ctx.at;
|
|
4564
4637
|
const policy = ctx.policy ?? CODE_DEFAULT_POLICY;
|
|
@@ -5651,6 +5724,7 @@ ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS last_activity_at timestamptz;
|
|
|
5651
5724
|
-- \u2605 \u51BB\u7ED3\u6765\u4FE1\u96C6\u5408\uFF08\u8BA1\u5212 \xA76.5\uFF0C\u9636\u6BB5 E\uFF09\uFF1Awork.create \u6309 reasons \u7684 issue \u9879\u51BB\u7ED3\u7684 issue id \u6279\u6B21\uFF08jsonb \u6570\u7EC4\uFF09\u3002
|
|
5652
5725
|
-- reply \u5F52\u5C5E/\u9500\u8D26/\u5BA1\u8BA1\u53EA\u8BA4\u5B83\uFF0C\u4E0D\u9760\u65F6\u95F4\u7A97\u731C\u6D4B\u3002\u5B58\u91CF\u4E3A NULL\uFF08\u65E0\u6279\u6B21\u8BED\u4E49\uFF09\u3002
|
|
5653
5726
|
ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS frozen_issue_ids jsonb;
|
|
5727
|
+
ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS handled_issue_ids jsonb;
|
|
5654
5728
|
-- \u56DE\u4FE1\u8F6E\u6307\u5411\u54EA\u6761 comment issue\uFF08\u7EAF\u6570\u636E\uFF09
|
|
5655
5729
|
ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS reply_to_issue_id text;
|
|
5656
5730
|
-- \u2605 work lane\uFF08\u6B63\u5411\u5224\u636E\uFF0C\u53D6\u4EE3 "reply_to_issue_id IS NULL"\uFF09\uFF1A\u5B58\u91CF\u884C\u6309\u6709\u65E0 reply_to_issue_id \u56DE\u586B\u3002
|
|
@@ -5800,7 +5874,7 @@ ALTER TABLE ${q2}.workorders DROP COLUMN IF EXISTS resume_marker;
|
|
|
5800
5874
|
ALTER TABLE ${q2}.events DROP COLUMN IF EXISTS effect_lease_until;
|
|
5801
5875
|
-- \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
|
|
5802
5876
|
UPDATE ${q2}.works SET status = CASE
|
|
5803
|
-
WHEN retry_at IS NOT NULL THEN 'retry'
|
|
5877
|
+
WHEN retry_at IS NOT NULL AND dead_at IS NOT NULL THEN 'retry'
|
|
5804
5878
|
WHEN dead_at IS NOT NULL THEN 'dead'
|
|
5805
5879
|
WHEN ended_at IS NOT NULL AND outcome = 'failed' THEN 'failed'
|
|
5806
5880
|
WHEN ended_at IS NOT NULL AND (conclusion IS NOT NULL OR
|
|
@@ -11289,6 +11363,7 @@ var init_store_postgres = __esm({
|
|
|
11289
11363
|
proposalReason: w2.proposal_reason ?? null,
|
|
11290
11364
|
lastActivityAt: iso(w2.last_activity_at),
|
|
11291
11365
|
frozenIssueIds: w2.frozen_issue_ids ?? null,
|
|
11366
|
+
handledIssueIds: w2.handled_issue_ids ?? null,
|
|
11292
11367
|
agentHandoffAttemptId: w2.agent_handoff_attempt_id ?? null,
|
|
11293
11368
|
businessHandoffStatus: w2.business_handoff_status ?? null,
|
|
11294
11369
|
businessHandoffRecordedAt: iso(w2.business_handoff_recorded_at),
|
|
@@ -11494,8 +11569,8 @@ var init_store_postgres = __esm({
|
|
|
11494
11569
|
(id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
|
|
11495
11570
|
outcome_detail,session_ref,continues_work_id,parent_work_id,reply_to_issue_id,output_version_no,
|
|
11496
11571
|
node_version,conclusion,proposal_reason,last_activity_at,frozen_issue_ids,agent_handoff_attempt_id,business_handoff_status,business_handoff_recorded_at,
|
|
11497
|
-
acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane)
|
|
11498
|
-
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,$26,$27,$28,$29,$30,$31,$32,$33)
|
|
11572
|
+
acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane,handled_issue_ids)
|
|
11573
|
+
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,$26,$27,$28,$29,$30,$31,$32,$33,$34)
|
|
11499
11574
|
ON CONFLICT (id) DO NOTHING`,
|
|
11500
11575
|
[
|
|
11501
11576
|
w2.id,
|
|
@@ -11530,13 +11605,14 @@ var init_store_postgres = __esm({
|
|
|
11530
11605
|
w2.rejectedReason,
|
|
11531
11606
|
w2.overrideBy,
|
|
11532
11607
|
w2.overrideReason,
|
|
11533
|
-
w2.lane
|
|
11608
|
+
w2.lane,
|
|
11609
|
+
w2.handledIssueIds ? JSON.stringify(w2.handledIssueIds) : null
|
|
11534
11610
|
]
|
|
11535
11611
|
);
|
|
11536
11612
|
break;
|
|
11537
11613
|
}
|
|
11538
11614
|
case "work.update":
|
|
11539
|
-
await this.patch("works", "id", m2.id, m2.patch, { outcomeDetail: true });
|
|
11615
|
+
await this.patch("works", "id", m2.id, m2.patch, { outcomeDetail: true, frozenIssueIds: true, handledIssueIds: true });
|
|
11540
11616
|
break;
|
|
11541
11617
|
case "artifact.replace":
|
|
11542
11618
|
await this.c.query(`DELETE FROM ${this.s}.work_artifacts WHERE work_id = $1`, [m2.workId]);
|
|
@@ -11866,7 +11942,7 @@ function newNode(id, workorderId, at, version2) {
|
|
|
11866
11942
|
updatedAt: at
|
|
11867
11943
|
};
|
|
11868
11944
|
}
|
|
11869
|
-
function markNodeForRetry(state, nodeId, at) {
|
|
11945
|
+
function markNodeForRetry(state, nodeId, at, resetRejectedBudget = false) {
|
|
11870
11946
|
const node2 = state.node(nodeId);
|
|
11871
11947
|
if (!node2) return;
|
|
11872
11948
|
const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
|
|
@@ -11874,7 +11950,7 @@ function markNodeForRetry(state, nodeId, at) {
|
|
|
11874
11950
|
state.updateWork(lw.id, { retryAt: at, status: "retry" });
|
|
11875
11951
|
}
|
|
11876
11952
|
if (lw && !lw.deadAt && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success") {
|
|
11877
|
-
if (lw.acceptanceState === "rejected") state.updateWork(lw.id, { retryAt: at });
|
|
11953
|
+
if (resetRejectedBudget && lw.acceptanceState === "rejected") state.updateWork(lw.id, { retryAt: at });
|
|
11878
11954
|
for (const req of state.requirementsOf(node2.id)) {
|
|
11879
11955
|
if (!req.latestReviewId) continue;
|
|
11880
11956
|
const r = state.review(req.latestReviewId);
|
|
@@ -12006,6 +12082,9 @@ var init_plan = __esm({
|
|
|
12006
12082
|
const changed = node2.assigneeActorId !== e.assigneeActorId;
|
|
12007
12083
|
const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
|
|
12008
12084
|
const settled = lw !== null && lw !== void 0 && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success";
|
|
12085
|
+
if (changed && e.resetRejectedBudget === true && lw && !lw.deadAt && settled && lw.acceptanceState === "rejected") {
|
|
12086
|
+
state.updateWork(lw.id, { retryAt: ctx.at });
|
|
12087
|
+
}
|
|
12009
12088
|
state.updateNode(e.nodeId, {
|
|
12010
12089
|
assigneeActorId: e.assigneeActorId,
|
|
12011
12090
|
assigneeRole: e.assigneeRole,
|
|
@@ -12035,7 +12114,7 @@ var init_plan = __esm({
|
|
|
12035
12114
|
name: "plan/node-retry",
|
|
12036
12115
|
kind: "plan.node_retry",
|
|
12037
12116
|
apply(e, state, ctx) {
|
|
12038
|
-
markNodeForRetry(state, e.nodeId, ctx.at);
|
|
12117
|
+
markNodeForRetry(state, e.nodeId, ctx.at, e.resetRejectedBudget === true);
|
|
12039
12118
|
}
|
|
12040
12119
|
};
|
|
12041
12120
|
planUpdateFields = {
|
|
@@ -12168,7 +12247,13 @@ var init_workorder = __esm({
|
|
|
12168
12247
|
apply(e, state, ctx) {
|
|
12169
12248
|
state.updateWorkorder({ dispatchPaused: false, pausedBy: null, pausedReason: null, updatedAt: ctx.at });
|
|
12170
12249
|
for (const node2 of state.liveNodes()) {
|
|
12171
|
-
planNodeRetry.apply({
|
|
12250
|
+
planNodeRetry.apply({
|
|
12251
|
+
kind: "plan.node_retry",
|
|
12252
|
+
workorderId: e.workorderId,
|
|
12253
|
+
nodeId: node2.id,
|
|
12254
|
+
by: e.by,
|
|
12255
|
+
resetRejectedBudget: e.resetRejectedBudget
|
|
12256
|
+
}, state, ctx);
|
|
12172
12257
|
}
|
|
12173
12258
|
}
|
|
12174
12259
|
};
|
|
@@ -12182,8 +12267,8 @@ var init_workorder = __esm({
|
|
|
12182
12267
|
});
|
|
12183
12268
|
|
|
12184
12269
|
// ../engine/src/handlers/work.ts
|
|
12185
|
-
function latestProposedOf(state, nodeId) {
|
|
12186
|
-
const successes = state.mainWorksOf(nodeId).filter((w2) => w2.status === "success").sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
12270
|
+
function latestProposedOf(state, nodeId, deliveriesOnly) {
|
|
12271
|
+
const successes = state.mainWorksOf(nodeId).filter((w2) => w2.status === "success" && (!deliveriesOnly || state.artifactsOf(w2.id).length > 0 || w2.conclusion != null)).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
12187
12272
|
return successes.length > 0 ? successes[successes.length - 1].id : null;
|
|
12188
12273
|
}
|
|
12189
12274
|
var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workContentEdited, workConclude, nodeLatestProposedRebased, workTimeout;
|
|
@@ -12198,6 +12283,7 @@ var init_work = __esm({
|
|
|
12198
12283
|
name: "work/create",
|
|
12199
12284
|
kind: "work.create",
|
|
12200
12285
|
apply(e, state, ctx) {
|
|
12286
|
+
if (e.pauseGateVersion === 1 && state.workorder.dispatchPaused) return;
|
|
12201
12287
|
if (state.work(e.workId)) return;
|
|
12202
12288
|
const node2 = state.node(e.nodeId);
|
|
12203
12289
|
if (!node2) return;
|
|
@@ -12223,6 +12309,7 @@ var init_work = __esm({
|
|
|
12223
12309
|
replyToIssueId: e.replyToIssueId,
|
|
12224
12310
|
outputVersionNo: null,
|
|
12225
12311
|
conclusion: null,
|
|
12312
|
+
handledIssueIds: null,
|
|
12226
12313
|
agentHandoffAttemptId: null,
|
|
12227
12314
|
businessHandoffStatus: null,
|
|
12228
12315
|
businessHandoffRecordedAt: null,
|
|
@@ -12262,6 +12349,7 @@ var init_work = __esm({
|
|
|
12262
12349
|
outputVersionNo: null,
|
|
12263
12350
|
conclusion: null,
|
|
12264
12351
|
frozenIssueIds: frozen.length > 0 ? frozen : null,
|
|
12352
|
+
handledIssueIds: null,
|
|
12265
12353
|
agentHandoffAttemptId: null,
|
|
12266
12354
|
businessHandoffStatus: null,
|
|
12267
12355
|
businessHandoffRecordedAt: null,
|
|
@@ -12296,6 +12384,7 @@ var init_work = __esm({
|
|
|
12296
12384
|
// 会话侧也要杀(旧 agent 可能还挂着)。cancelPreviousWork 找不到/会话已退则 no-op。
|
|
12297
12385
|
// 回信 work 不杀前会话(apply 不 kill 主链,节点已完结、无在跑会话),只派发。
|
|
12298
12386
|
async effect(e, ctx) {
|
|
12387
|
+
if (e.pauseGateVersion === 1 && !await ctx.canDispatchWork(e.workorderId, e.workId)) return;
|
|
12299
12388
|
if (!e.replyToIssueId) await ctx.cancelPreviousWork(e.nodeId, e.workId);
|
|
12300
12389
|
await ctx.dispatchWork(e.workId, e.resumeEligible !== void 0 ? { resumeEligible: e.resumeEligible } : void 0);
|
|
12301
12390
|
}
|
|
@@ -12326,7 +12415,7 @@ var init_work = __esm({
|
|
|
12326
12415
|
if (node2.latestProposedWorkId) {
|
|
12327
12416
|
const ls = state.work(node2.latestProposedWorkId);
|
|
12328
12417
|
if (!ls || ls.status !== "success") {
|
|
12329
|
-
state.updateNode(e.nodeId, { latestProposedWorkId: latestProposedOf(state, e.nodeId) });
|
|
12418
|
+
state.updateNode(e.nodeId, { latestProposedWorkId: latestProposedOf(state, e.nodeId, e.semanticsVersion === 2) });
|
|
12330
12419
|
}
|
|
12331
12420
|
}
|
|
12332
12421
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
@@ -12356,17 +12445,34 @@ var init_work = __esm({
|
|
|
12356
12445
|
}
|
|
12357
12446
|
const businessFailed = w2.businessHandoffStatus === "not_delivered";
|
|
12358
12447
|
const artifactCount = state.artifactsOf(e.workId).length;
|
|
12359
|
-
const pureReply = e.semanticsVersion
|
|
12360
|
-
const succeeded = e.outcome === "completed" && !businessFailed && (e.semanticsVersion
|
|
12448
|
+
const pureReply = (e.semanticsVersion ?? 1) >= 2 && artifactCount === 0 && (w2.conclusion === null || w2.conclusion === void 0) && (w2.frozenIssueIds ?? []).some((issueId) => state.issue(issueId)?.kind === "comment" && repliedInWork(state, w2, issueId));
|
|
12449
|
+
const succeeded = e.outcome === "completed" && !businessFailed && ((e.semanticsVersion ?? 1) >= 2 ? artifactCount > 0 || (e.semanticsVersion === 3 ? notificationsHandledInWork(state, w2) : pureReply) : hasOutput(w2, artifactCount));
|
|
12361
12450
|
const status = succeeded ? "success" : "failed";
|
|
12362
|
-
|
|
12451
|
+
if (e.semanticsVersion === 3 && artifactCount === 0 && e.outcome === "completed") {
|
|
12452
|
+
const node2 = state.node(w2.nodeId);
|
|
12453
|
+
if (node2) for (const id of w2.frozenIssueIds ?? []) {
|
|
12454
|
+
const issue2 = state.issue(id);
|
|
12455
|
+
if (issue2?.kind === "comment" && !issue2.resolvedAt && repliedInWork(state, w2, id) && lastPostIsFromNode(state, issue2, node2)) {
|
|
12456
|
+
state.emit({
|
|
12457
|
+
kind: "issue.resolve",
|
|
12458
|
+
workorderId: e.workorderId,
|
|
12459
|
+
issueId: id,
|
|
12460
|
+
state: "resolved",
|
|
12461
|
+
by: w2.assigneeActorId,
|
|
12462
|
+
note: null,
|
|
12463
|
+
viaWorkId: w2.id
|
|
12464
|
+
});
|
|
12465
|
+
}
|
|
12466
|
+
}
|
|
12467
|
+
}
|
|
12468
|
+
const zeroOutputReason = (e.semanticsVersion ?? 1) >= 2 && !succeeded && e.outcome === "completed" && !businessFailed ? artifactCount === 0 && w2.conclusion ? "conclusion without artifacts (invariant violation)" : e.semanticsVersion === 3 && (w2.frozenIssueIds ?? []).length > 0 ? "notifications incomplete or delivery requirements changed" : "zero output" : null;
|
|
12363
12469
|
state.updateWork(e.workId, {
|
|
12364
12470
|
endedAt: ctx.at,
|
|
12365
12471
|
outcome: businessFailed ? "failed" : e.outcome,
|
|
12366
12472
|
outcomeDetail: businessFailed ? { ...e.detail ?? {}, reason: "agent business handoff not delivered" } : zeroOutputReason ? { ...e.detail ?? {}, reason: e.detail?.reason ?? zeroOutputReason } : e.detail,
|
|
12367
12473
|
status
|
|
12368
12474
|
});
|
|
12369
|
-
if (status === "success" && !(e.semanticsVersion
|
|
12475
|
+
if (status === "success" && !((e.semanticsVersion ?? 1) >= 2 && artifactCount === 0)) {
|
|
12370
12476
|
state.updateNode(w2.nodeId, { latestProposedWorkId: e.workId });
|
|
12371
12477
|
}
|
|
12372
12478
|
}
|
|
@@ -12390,7 +12496,7 @@ var init_work = __esm({
|
|
|
12390
12496
|
} : {}
|
|
12391
12497
|
});
|
|
12392
12498
|
if (flipsToFailed && state.node(w2.nodeId)?.latestProposedWorkId === e.workId) {
|
|
12393
|
-
state.updateNode(w2.nodeId, { latestProposedWorkId: latestProposedOf(state, w2.nodeId) });
|
|
12499
|
+
state.updateNode(w2.nodeId, { latestProposedWorkId: latestProposedOf(state, w2.nodeId, e.semanticsVersion === 2) });
|
|
12394
12500
|
}
|
|
12395
12501
|
}
|
|
12396
12502
|
};
|
|
@@ -12508,6 +12614,7 @@ var init_issue = __esm({
|
|
|
12508
12614
|
"../engine/src/handlers/issue.ts"() {
|
|
12509
12615
|
"use strict";
|
|
12510
12616
|
init_plan();
|
|
12617
|
+
init_letters();
|
|
12511
12618
|
issueCreate = {
|
|
12512
12619
|
name: "issue/create",
|
|
12513
12620
|
kind: "issue.create",
|
|
@@ -12578,6 +12685,11 @@ var init_issue = __esm({
|
|
|
12578
12685
|
// 缺失(人 / chat 会话 / 存量事件)落 null,判据按"不是节点发言"处理。
|
|
12579
12686
|
viaWorkId: e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId ?? null : null
|
|
12580
12687
|
});
|
|
12688
|
+
const workId = e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId : null;
|
|
12689
|
+
const work = workId ? state.work(workId) : void 0;
|
|
12690
|
+
if (e.handlingVersion === 1 && work && work.assigneeActorId === e.authorActorId && ctx.actorId === e.authorActorId) {
|
|
12691
|
+
recordHandledNotification(state, work, issue2);
|
|
12692
|
+
}
|
|
12581
12693
|
}
|
|
12582
12694
|
};
|
|
12583
12695
|
issueResolve = {
|
|
@@ -12585,7 +12697,13 @@ var init_issue = __esm({
|
|
|
12585
12697
|
kind: "issue.resolve",
|
|
12586
12698
|
apply(e, state, ctx) {
|
|
12587
12699
|
const issue2 = state.issue(e.issueId);
|
|
12588
|
-
if (!issue2
|
|
12700
|
+
if (!issue2) return;
|
|
12701
|
+
const workId = e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId : null;
|
|
12702
|
+
const work = workId ? state.work(workId) : void 0;
|
|
12703
|
+
if (e.acknowledged && work && work.assigneeActorId === e.by && ctx.actorId === e.by && e.note?.trim() && issue2.kind !== "escalation" && (issue2.kind !== "gap" || issue2.resolvedAt !== null)) {
|
|
12704
|
+
recordHandledNotification(state, work, issue2, true);
|
|
12705
|
+
}
|
|
12706
|
+
if (issue2.resolvedAt !== null) return;
|
|
12589
12707
|
const humanResolution = e.by.startsWith("actor:human:") && e.by === ctx.actorId;
|
|
12590
12708
|
if (e.semanticsVersion === 2 && issue2.kind === "escalation" && !humanResolution) {
|
|
12591
12709
|
throw new Error("\u5173\u95ED\u5347\u7EA7\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4\uFF1Bagent \u8BF7\u63D0\u4EA4\u5173\u95ED\u6388\u6743\u7533\u8BF7");
|
|
@@ -12600,7 +12718,7 @@ var init_issue = __esm({
|
|
|
12600
12718
|
resolvedNote: e.note,
|
|
12601
12719
|
resolvedViaWorkId: e.viaWorkId
|
|
12602
12720
|
});
|
|
12603
|
-
if (issue2.kind === "gap" && (e.semanticsVersion !== 2 || humanResolution)) {
|
|
12721
|
+
if (issue2.kind === "gap" && e.closeRelatedEscalations !== false && (e.semanticsVersion !== 2 || humanResolution)) {
|
|
12604
12722
|
for (const other of state.allIssues()) {
|
|
12605
12723
|
if (other.kind === "escalation" && other.resolvedAt === null && other.workorderId === issue2.workorderId && other.gapId === issue2.id) {
|
|
12606
12724
|
state.updateIssue(other.id, {
|
|
@@ -12618,7 +12736,7 @@ var init_issue = __esm({
|
|
|
12618
12736
|
}
|
|
12619
12737
|
if (issue2.kind === "gap" || issue2.kind === "escalation") {
|
|
12620
12738
|
const nodeId = issue2.raisedByNodeId ?? issue2.aboutNodeId;
|
|
12621
|
-
if (nodeId) markNodeForRetry(state, nodeId, ctx.at);
|
|
12739
|
+
if (nodeId) markNodeForRetry(state, nodeId, ctx.at, e.resetRejectedBudget === true);
|
|
12622
12740
|
}
|
|
12623
12741
|
}
|
|
12624
12742
|
};
|
|
@@ -12656,6 +12774,7 @@ var init_review = __esm({
|
|
|
12656
12774
|
name: "review/create",
|
|
12657
12775
|
kind: "review.create",
|
|
12658
12776
|
apply(e, state, ctx) {
|
|
12777
|
+
if (e.pauseGateVersion === 1 && state.workorder.dispatchPaused) return;
|
|
12659
12778
|
const work = state.work(e.targetWorkId);
|
|
12660
12779
|
if (!work) return;
|
|
12661
12780
|
const dup = state.reviewsOf(e.targetWorkId).some(
|
|
@@ -12691,6 +12810,7 @@ var init_review = __esm({
|
|
|
12691
12810
|
}
|
|
12692
12811
|
},
|
|
12693
12812
|
async effect(e, ctx) {
|
|
12813
|
+
if (e.pauseGateVersion === 1 && !await ctx.canDispatchReview(e.workorderId, e.reviewId)) return;
|
|
12694
12814
|
if (e.reviewerIsAgent) await ctx.dispatchReview(e.reviewId, e.nodeId, e.reviewerActorId);
|
|
12695
12815
|
else await ctx.notify([e.reviewerActorId], { kind: "review-requested", reviewId: e.reviewId });
|
|
12696
12816
|
}
|
|
@@ -12997,11 +13117,28 @@ var init_handlers = __esm({
|
|
|
12997
13117
|
});
|
|
12998
13118
|
|
|
12999
13119
|
// ../engine/src/bus.ts
|
|
13000
|
-
function
|
|
13001
|
-
if (event.kind === "work.
|
|
13002
|
-
return { ...event,
|
|
13120
|
+
function versionCommandEvent(event) {
|
|
13121
|
+
if (event.kind === "work.create" || event.kind === "review.create") {
|
|
13122
|
+
return { ...event, pauseGateVersion: 1 };
|
|
13123
|
+
}
|
|
13124
|
+
if (event.kind === "issue.reply") return { ...event, handlingVersion: 1 };
|
|
13125
|
+
if (event.kind === "work.response" && event.semanticsVersion === 2) {
|
|
13126
|
+
return { ...event, semanticsVersion: 3 };
|
|
13127
|
+
}
|
|
13128
|
+
switch (event.kind) {
|
|
13129
|
+
case "work.accept":
|
|
13130
|
+
case "work.kill":
|
|
13131
|
+
case "work.handoff_recorded":
|
|
13132
|
+
return { ...event, semanticsVersion: 2 };
|
|
13133
|
+
case "issue.resolve":
|
|
13134
|
+
return { ...event, semanticsVersion: 2, resetRejectedBudget: true };
|
|
13135
|
+
case "plan.node_retry":
|
|
13136
|
+
case "workorder.resumed":
|
|
13137
|
+
case "plan.assign_actor":
|
|
13138
|
+
return { ...event, resetRejectedBudget: true };
|
|
13139
|
+
default:
|
|
13140
|
+
return event;
|
|
13003
13141
|
}
|
|
13004
|
-
return event;
|
|
13005
13142
|
}
|
|
13006
13143
|
function emptySnapshotPlaceholder(record8) {
|
|
13007
13144
|
return {
|
|
@@ -13209,7 +13346,7 @@ var init_bus = __esm({
|
|
|
13209
13346
|
workorderId: input.workorderId,
|
|
13210
13347
|
actorId: input.actorId,
|
|
13211
13348
|
handActorId: input.handActorId ?? null,
|
|
13212
|
-
event:
|
|
13349
|
+
event: versionCommandEvent(input.event),
|
|
13213
13350
|
causedBySeq: null,
|
|
13214
13351
|
origin: "command",
|
|
13215
13352
|
dedupKey: input.dedupKey ?? null,
|
|
@@ -13234,6 +13371,32 @@ var init_bus = __esm({
|
|
|
13234
13371
|
}
|
|
13235
13372
|
}
|
|
13236
13373
|
}
|
|
13374
|
+
/** 同一命令的一组最终修改一起入队。scan-on-empty 在末项之前始终看到 pending,不能派发中间状态。 */
|
|
13375
|
+
async submitBatchAndProcess(inputs) {
|
|
13376
|
+
if (!inputs.length) return;
|
|
13377
|
+
const seqs = await this.store.transaction(async (tx) => {
|
|
13378
|
+
for (const wid of [...new Set(inputs.map((i) => i.workorderId))].sort()) await tx.lockWorkorder(wid);
|
|
13379
|
+
return tx.appendEvents(inputs.map((input) => ({
|
|
13380
|
+
companyId: input.companyId,
|
|
13381
|
+
workorderId: input.workorderId,
|
|
13382
|
+
actorId: input.actorId,
|
|
13383
|
+
handActorId: input.handActorId ?? null,
|
|
13384
|
+
event: versionCommandEvent(input.event),
|
|
13385
|
+
causedBySeq: null,
|
|
13386
|
+
origin: "command",
|
|
13387
|
+
dedupKey: input.dedupKey ?? null,
|
|
13388
|
+
createdAt: this.clock.now(),
|
|
13389
|
+
...eventAnchors(input.event)
|
|
13390
|
+
})));
|
|
13391
|
+
});
|
|
13392
|
+
if (this.running) {
|
|
13393
|
+
const last = /* @__PURE__ */ new Map();
|
|
13394
|
+
inputs.forEach((input, i) => last.set(input.workorderId, seqs[i]));
|
|
13395
|
+
for (const [wid, seq] of last) await this.waitForSettled(wid, seq);
|
|
13396
|
+
} else {
|
|
13397
|
+
for (let i = 0; i < 2e3; i++) if (!await this.drainOne()) break;
|
|
13398
|
+
}
|
|
13399
|
+
}
|
|
13237
13400
|
/* ═══════════════════════ 调度器主循环 ═══════════════════════ */
|
|
13238
13401
|
/**
|
|
13239
13402
|
* 启动调度器——永不返回。
|
|
@@ -13469,7 +13632,7 @@ var init_bus = __esm({
|
|
|
13469
13632
|
workorderId: record8.workorderId,
|
|
13470
13633
|
actorId: SYSTEM_ACTOR,
|
|
13471
13634
|
handActorId: null,
|
|
13472
|
-
event:
|
|
13635
|
+
event: versionCommandEvent(event),
|
|
13473
13636
|
causedBySeq: record8.seq,
|
|
13474
13637
|
origin: "apply",
|
|
13475
13638
|
dedupKey: null,
|
|
@@ -13500,6 +13663,18 @@ var init_bus = __esm({
|
|
|
13500
13663
|
// ★ 注入 workorderId:派发侧读模型没追上时,据它指名重读那张工单(见 EngineIO.dispatchWork 注释)。
|
|
13501
13664
|
dispatchWork: (id, opts) => this.io.dispatchWork(id, { ...opts, workorderId: record8.workorderId }),
|
|
13502
13665
|
dispatchReview: (id, nodeId, reviewerActorId) => this.io.dispatchReview(id, nodeId, reviewerActorId),
|
|
13666
|
+
canDispatchWork: async (workorderId, workId) => {
|
|
13667
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
|
|
13668
|
+
if (!snap || snap.workorder.dispatchPaused) return false;
|
|
13669
|
+
const work = snap.works.find((row) => row.id === workId);
|
|
13670
|
+
return work?.status === "running" && !work.deadAt && !work.endedAt;
|
|
13671
|
+
},
|
|
13672
|
+
canDispatchReview: async (workorderId, reviewId) => {
|
|
13673
|
+
const snap = await this.store.transaction((tx) => tx.loadWorkorder(workorderId));
|
|
13674
|
+
if (!snap || snap.workorder.dispatchPaused) return false;
|
|
13675
|
+
const review = snap.reviews.find((row) => row.id === reviewId);
|
|
13676
|
+
return review?.status === "running" && !review.cancelledAt && !review.endedAt;
|
|
13677
|
+
},
|
|
13503
13678
|
cancelSession: (ref2) => this.io.cancelSession(ref2),
|
|
13504
13679
|
// ★ work.create 的「kill 旧会话」:apply 已把该节点上一个 latest_work 置 deadAt(会话可能还挂着),
|
|
13505
13680
|
// 这里按 store 找它 cancel。找不到 / 不是刚被 kill / 会话已退 → no-op。这是设计「节点重新运行 = 自动
|
|
@@ -13566,7 +13741,7 @@ var init_bus = __esm({
|
|
|
13566
13741
|
workorderId: record8.workorderId,
|
|
13567
13742
|
actorId: b2.actorId ?? SYSTEM_ACTOR,
|
|
13568
13743
|
handActorId: null,
|
|
13569
|
-
event:
|
|
13744
|
+
event: versionCommandEvent(b2.event),
|
|
13570
13745
|
causedBySeq: record8.seq,
|
|
13571
13746
|
origin: "effect",
|
|
13572
13747
|
dedupKey: b2.dedupKey ?? null,
|
|
@@ -13604,7 +13779,7 @@ var init_bus = __esm({
|
|
|
13604
13779
|
workorderId,
|
|
13605
13780
|
actorId: SYSTEM_ACTOR,
|
|
13606
13781
|
handActorId: null,
|
|
13607
|
-
event:
|
|
13782
|
+
event: versionCommandEvent(event),
|
|
13608
13783
|
causedBySeq: null,
|
|
13609
13784
|
origin: "apply",
|
|
13610
13785
|
dedupKey: null,
|
|
@@ -13656,7 +13831,7 @@ var init_bus = __esm({
|
|
|
13656
13831
|
workorderId: record8.workorderId,
|
|
13657
13832
|
actorId: SYSTEM_ACTOR,
|
|
13658
13833
|
handActorId: null,
|
|
13659
|
-
event:
|
|
13834
|
+
event: versionCommandEvent(event),
|
|
13660
13835
|
causedBySeq: record8.seq,
|
|
13661
13836
|
origin: "apply",
|
|
13662
13837
|
dedupKey: null,
|
|
@@ -14502,6 +14677,7 @@ async function assembleContext(args) {
|
|
|
14502
14677
|
const siblingParts = args.part !== void 0 ? (artifact.parts ?? []).filter((p2) => p2.name !== args.part) : [];
|
|
14503
14678
|
const consumers = consumersOf(model, artifactId).filter((c) => c.type !== DELIVERY_SUMMARY_ARTIFACT_TYPE);
|
|
14504
14679
|
const staleSet = staleSetOf(model, artifactId);
|
|
14680
|
+
const acceptedHead = concludedHead(model, artifactId);
|
|
14505
14681
|
const head = artifact.currentRev;
|
|
14506
14682
|
const headRev = head !== null ? model.revisions.get(head) : null;
|
|
14507
14683
|
const headSpread = headRev !== null ? await spreadRevisionContent(blobs, headRev, files, "deliverable") : null;
|
|
@@ -14817,10 +14993,19 @@ ${manifestDiff(baseC, newC)}
|
|
|
14817
14993
|
} else if (head === null) {
|
|
14818
14994
|
whyLines.push(typeDef?.contentType === "code" ? `**\u672C\u4EA7\u7269\u5728 Oasis \u91CC\u8FD8\u6CA1\u6709\u5DF2\u4EA4\u4ED8\u7684\u7248\u672C**\u2014\u2014\u4F46**\u8FD9\u4E0D\u7B49\u4E8E git \u91CC\u6CA1\u6709\u4E1C\u897F**\uFF1A\u4E0A\u4E00\u8F6E\u53EF\u80FD\u63A8\u4E86\u5206\u652F\u5374\u6CA1\u6765\u5F97\u53CA\u4EA4\u4ED8\u3002**\u4EE5\u4F60 fetch \u5230\u7684\u5206\u652F\u5B9E\u51B5\u4E3A\u51C6**\uFF08\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u7B2C 1 \u6B65\uFF09\u3002` : canvasMode ? `**\u5168\u65B0\u4EA7\u7269**\uFF1A\u8FD9\u662F\u9996\u7248\u2014\u2014\u672C\u4EA7\u7269\u8FD8\u6CA1\u6709\u4EFB\u4F55\u63D0\u4EA4\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002` : `**\u5168\u65B0\u4EA7\u7269**\uFF1A\`deliverable/\` \u4E3A\u7A7A\uFF0C\u8FD8\u6CA1\u6709\u4EFB\u4F55\u5185\u5BB9\u3002`);
|
|
14819
14995
|
whatLines.push(typeDef?.contentType === "code" ? `\u6309\u300C\u600E\u4E48\u63D0\u4EA4\u300D\u7B2C 1 \u6B65\u843D\u5230\u5DE5\u5355\u5206\u652F\u3001**\u5148\u770B\u6E05\u5B83\u4E0A\u9762\u5DF2\u7ECF\u6709\u4EC0\u4E48**\uFF0C\u518D\u63A5\u7740\u505A\u5B8C\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002` : `\u4EA7\u51FA**\u9996\u7248**\u5185\u5BB9\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002`);
|
|
14996
|
+
} else if (acceptedHead) {
|
|
14997
|
+
whyLines.push("**\u672C\u8F6E\u5DE5\u4F5C**\uFF1A\u6309\u5F53\u524D\u4EFB\u52A1\u8981\u6C42\u3001\u901A\u77E5\u53CA\u6062\u590D\u8BF4\u660E\u6838\u5BF9\u5DF2\u6709\u4EA4\u4ED8\u3002");
|
|
14998
|
+
whatLines.push("\u82E5\u6709\u771F\u5B9E\u7684\u65B0\u4FEE\u6539\u8981\u6C42\uFF0C\u5B8C\u6210\u4FEE\u6539\u540E\u518D\u4EA4\u4ED8\uFF1B\u82E5\u672C\u8F6E\u4EC5\u5904\u7406\u901A\u77E5\u4E14\u65E0\u9700\u6539\u7A3F\uFF0C\u9010\u6761\u660E\u786E\u6807\u8BB0\u5DF2\u5904\u7406\u540E\u6536\u5DE5\uFF0C\u4E0D\u5FC5\u91CD\u65B0 propose / conclude\u3002");
|
|
14820
14999
|
} else {
|
|
14821
15000
|
whyLines.push(`**\u7EE7\u7EED\u5B8C\u5584**\uFF1A\u4E0A\u6B21\u672A conclude\u3002`);
|
|
14822
15001
|
whatLines.push(`**\u63A5\u7740\u628A\u5B83\u505A\u5B8C**\u3001\u771F\u6B63\u5B8C\u6574\u4E86\u518D conclude\uFF08\u600E\u4E48\u843D\u89C1\u300C\u600E\u4E48\u63D0\u4EA4\u300D\uFF09\u3002`);
|
|
14823
15002
|
}
|
|
15003
|
+
if (acceptedHead) {
|
|
15004
|
+
whyLines.unshift(`**\u5DF2\u6709\u4EA4\u4ED8\u5DF2\u9A8C\u6536**\uFF1A${acceptedHead}\u3002\u6267\u884C\u8F6E\u6B21\u4E0E\u4EA4\u4ED8\u7248\u672C\u5206\u5F00\u8BA1\u6570\uFF0C\u672C\u8F6E\u88AB\u5524\u8D77\u4E0D\u4F1A\u64A4\u9500\u8BE5\u9A8C\u6536\u3002`);
|
|
15005
|
+
for (const gap of freshGaps) {
|
|
15006
|
+
whatLines.push(`\u6838\u5BF9\u7F3A\u53E3 ${gap.gapId} \u7684\u89E3\u6CD5\uFF1B\u786E\u8BA4\u65E0\u9700\u4FEE\u6539\u65F6\uFF1A\`oasis resolve ${gap.gapId} --as acknowledged --note "<\u6838\u5BF9\u4F9D\u636E\u53CA\u65E0\u9700\u4FEE\u6539\u7684\u539F\u56E0>"\`\u3002`);
|
|
15007
|
+
}
|
|
15008
|
+
}
|
|
14824
15009
|
if (pendingConcludeAttempt(model, artifactId) !== null) {
|
|
14825
15010
|
whyLines.push(
|
|
14826
15011
|
``,
|
|
@@ -18050,7 +18235,7 @@ function bridgeLoadReadModel(input) {
|
|
|
18050
18235
|
const art = nodeToArtifact(node2, edgesByTarget.get(node2.id) ?? [], requirementsByNode.get(node2.id) ?? []);
|
|
18051
18236
|
const lw = node2.latestWorkId ? workById.get(node2.latestWorkId) : void 0;
|
|
18052
18237
|
if (lw) {
|
|
18053
|
-
const st = lw.status ?? (lw.retryAt ? "retry" : lw.deadAt ? "dead" : lw.endedAt ? lw.outcome === "failed" ? "failed" : "success" : "running");
|
|
18238
|
+
const st = lw.status ?? (lw.retryAt && lw.deadAt ? "retry" : lw.deadAt ? "dead" : lw.endedAt ? lw.outcome === "failed" ? "failed" : "success" : "running");
|
|
18054
18239
|
const hasConclusionWithoutResponse = lw.conclusion !== null && lw.conclusion !== void 0 && st === "running" && !lw.endedAt;
|
|
18055
18240
|
let uiSt;
|
|
18056
18241
|
if (st === "retry") uiSt = null;
|
|
@@ -18553,22 +18738,18 @@ ${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192
|
|
|
18553
18738
|
return { ...plan, ops };
|
|
18554
18739
|
}
|
|
18555
18740
|
function buildConstituents(model, plan, schema, roleIndex) {
|
|
18556
|
-
const
|
|
18557
|
-
plan.ops.filter((o) => o.action === "unlink").map((o) => `${o.artifactId} ${o.to}`)
|
|
18558
|
-
);
|
|
18741
|
+
const inputs = new Map([...model.artifacts].map(([id, art]) => [id, new Set(art.inputs.map((i) => i.to))]));
|
|
18559
18742
|
const out = [];
|
|
18560
18743
|
for (const c of buildConstituentsRaw(model, plan, schema, roleIndex)) {
|
|
18744
|
+
if (c.kind === "spawn_artifact") inputs.set(c.artifactId, new Set(c.payload.inputs.map((i) => i.to)));
|
|
18745
|
+
if (c.kind === "link_input") inputs.get(c.artifactId)?.add(c.payload.to);
|
|
18746
|
+
if (c.kind === "unlink_input") inputs.get(c.artifactId)?.delete(c.payload.to);
|
|
18561
18747
|
if (c.kind === "seal" && c.payload.reason === "cancelled") {
|
|
18562
|
-
for (const
|
|
18563
|
-
const
|
|
18564
|
-
|
|
18565
|
-
|
|
18566
|
-
|
|
18567
|
-
artifactId: edge.artifactId,
|
|
18568
|
-
kind: "unlink_input",
|
|
18569
|
-
target: edge.artifactId,
|
|
18570
|
-
payload: { to: edge.to }
|
|
18571
|
-
});
|
|
18748
|
+
for (const [id, upstreams] of inputs) {
|
|
18749
|
+
for (const to of upstreams) if (id === c.artifactId || to === c.artifactId) {
|
|
18750
|
+
out.push({ artifactId: id, kind: "unlink_input", target: id, payload: { to } });
|
|
18751
|
+
upstreams.delete(to);
|
|
18752
|
+
}
|
|
18572
18753
|
}
|
|
18573
18754
|
}
|
|
18574
18755
|
out.push(c);
|
|
@@ -18907,6 +19088,10 @@ function filterReadModelByWorkspace(model, workspace) {
|
|
|
18907
19088
|
pinMeta: new Map([...model.pinMeta].filter(([artId]) => myArtIds.has(artId)))
|
|
18908
19089
|
};
|
|
18909
19090
|
}
|
|
19091
|
+
function assertWorkorderDispatchActive(snap) {
|
|
19092
|
+
if (!snap?.workorder.dispatchPaused) return;
|
|
19093
|
+
throw new KernelError("\u5DE5\u5355\u5DF2\u6682\u505C\u2014\u2014\u8BF7\u5148\u901A\u8FC7\u5DE5\u5355\u64CD\u4F5C\u6062\u590D\u8FD0\u884C\uFF0C\u518D\u6267\u884C\u8282\u70B9\u7EA7\u8C03\u5EA6\u64CD\u4F5C", "workorder-paused");
|
|
19094
|
+
}
|
|
18910
19095
|
async function mapWithConcurrency(items, limit, fn) {
|
|
18911
19096
|
if (items.length === 0) return [];
|
|
18912
19097
|
const out = new Array(items.length);
|
|
@@ -19542,6 +19727,7 @@ var init_kernel_bridge = __esm({
|
|
|
19542
19727
|
}
|
|
19543
19728
|
if (!workId) {
|
|
19544
19729
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
19730
|
+
assertWorkorderDispatchActive(snap);
|
|
19545
19731
|
const nodeRow = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
19546
19732
|
const headId = nodeRow?.latestWorkId ?? null;
|
|
19547
19733
|
if (headId) {
|
|
@@ -19963,6 +20149,30 @@ var init_kernel_bridge = __esm({
|
|
|
19963
20149
|
});
|
|
19964
20150
|
}
|
|
19965
20151
|
async resolveAnnotation(args) {
|
|
20152
|
+
if (args.resolution === "acknowledged" && args.viaNode) {
|
|
20153
|
+
const gap = (this.model.gaps.get(args.viaNode) ?? []).find((g2) => g2.gapId === args.annotationId);
|
|
20154
|
+
if (gap) {
|
|
20155
|
+
if (!gap.resolved || !args.note?.trim()) throw new Error("\u53EA\u80FD\u786E\u8BA4\u5DF2\u89E3\u51B3\u7684\u7F3A\u53E3\uFF0C\u4E14\u9700\u8BF4\u660E\u4E3A\u4F55\u65E0\u9700\u6539\u7A3F");
|
|
20156
|
+
const wid2 = this.wo(args.viaNode);
|
|
20157
|
+
await this.commit({
|
|
20158
|
+
companyId: "",
|
|
20159
|
+
workorderId: wid2,
|
|
20160
|
+
actorId: args.actor,
|
|
20161
|
+
event: {
|
|
20162
|
+
kind: "issue.resolve",
|
|
20163
|
+
workorderId: wid2,
|
|
20164
|
+
issueId: gap.gapId,
|
|
20165
|
+
state: "resolved",
|
|
20166
|
+
by: args.actor,
|
|
20167
|
+
note: args.note,
|
|
20168
|
+
viaWorkId: null,
|
|
20169
|
+
viaNodeId: args.viaNode,
|
|
20170
|
+
acknowledged: true
|
|
20171
|
+
}
|
|
20172
|
+
});
|
|
20173
|
+
return;
|
|
20174
|
+
}
|
|
20175
|
+
}
|
|
19966
20176
|
const ann = this.model.annotations.get(args.annotationId);
|
|
19967
20177
|
if (!ann) throw new Error(`annotation not found: ${args.annotationId}`);
|
|
19968
20178
|
if (isHumanChangeRequest(ann) && args.actor.startsWith("actor:agent:") && args.resolution === "resolved" && !args.via) {
|
|
@@ -19981,7 +20191,9 @@ var init_kernel_bridge = __esm({
|
|
|
19981
20191
|
state: args.resolution === "wontfix" ? "closed" : "resolved",
|
|
19982
20192
|
by: args.actor,
|
|
19983
20193
|
note: args.note ?? null,
|
|
19984
|
-
viaWorkId: args.via ?? null
|
|
20194
|
+
viaWorkId: args.via ?? null,
|
|
20195
|
+
...args.viaNode ? { viaNodeId: args.viaNode } : {},
|
|
20196
|
+
...args.resolution === "acknowledged" ? { acknowledged: true } : {}
|
|
19985
20197
|
}
|
|
19986
20198
|
});
|
|
19987
20199
|
}
|
|
@@ -20047,7 +20259,7 @@ var init_kernel_bridge = __esm({
|
|
|
20047
20259
|
for (const [artifactId, gaps] of this.model.gaps) {
|
|
20048
20260
|
if (gaps.some((g2) => g2.gapId === args.gapId && !g2.resolved)) {
|
|
20049
20261
|
const wid = this.wo(artifactId);
|
|
20050
|
-
|
|
20262
|
+
const events = [{
|
|
20051
20263
|
companyId: "",
|
|
20052
20264
|
workorderId: wid,
|
|
20053
20265
|
actorId: args.actor,
|
|
@@ -20060,16 +20272,31 @@ var init_kernel_bridge = __esm({
|
|
|
20060
20272
|
note: args.note ?? null,
|
|
20061
20273
|
viaWorkId: null
|
|
20062
20274
|
}
|
|
20275
|
+
}];
|
|
20276
|
+
if (args.rework) events.push({
|
|
20277
|
+
companyId: "",
|
|
20278
|
+
workorderId: wid,
|
|
20279
|
+
actorId: args.actor,
|
|
20280
|
+
event: {
|
|
20281
|
+
kind: "issue.create",
|
|
20282
|
+
workorderId: wid,
|
|
20283
|
+
issueId: `ann:rework:${args.gapId}`,
|
|
20284
|
+
issueKind: "comment",
|
|
20285
|
+
body: args.note ?? "\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C\u8BF7\u6838\u5BF9\u89E3\u6CD5",
|
|
20286
|
+
aboutNodeId: artifactId,
|
|
20287
|
+
aboutWorkId: null,
|
|
20288
|
+
raisedByNodeId: null,
|
|
20289
|
+
authorActorId: args.actor,
|
|
20290
|
+
handActorId: null,
|
|
20291
|
+
recipients: [],
|
|
20292
|
+
attachments: [],
|
|
20293
|
+
gapId: args.gapId,
|
|
20294
|
+
source: "gap-recovery"
|
|
20295
|
+
}
|
|
20063
20296
|
});
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
|
|
20067
|
-
actor: args.actor,
|
|
20068
|
-
kind: "comment",
|
|
20069
|
-
annotationId: `ann:rework:${args.gapId}`,
|
|
20070
|
-
body: args.note ?? "\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C\u8BF7\u6309\u7B54\u590D\u91CD\u505A"
|
|
20071
|
-
});
|
|
20072
|
-
}
|
|
20297
|
+
await this.bus.submitBatchAndProcess(events);
|
|
20298
|
+
this.markWorkorderDirty(wid);
|
|
20299
|
+
await this.refreshTracked();
|
|
20073
20300
|
return;
|
|
20074
20301
|
}
|
|
20075
20302
|
}
|
|
@@ -20118,6 +20345,57 @@ var init_kernel_bridge = __esm({
|
|
|
20118
20345
|
}
|
|
20119
20346
|
});
|
|
20120
20347
|
}
|
|
20348
|
+
/** 人确认恢复卡后,只关闭卡中列出的缺口和关联上报。其他问题仍由各自责任人处理。 */
|
|
20349
|
+
async resolveRecovery(args) {
|
|
20350
|
+
if (!args.actor.startsWith("actor:human:")) throw new Error("\u6062\u590D\u51B3\u5B9A\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4");
|
|
20351
|
+
if (!args.reason.trim()) throw new Error("\u6062\u590D\u51B3\u5B9A\u5FC5\u987B\u8BF4\u660E\u91C7\u7528\u7684\u65B9\u6848");
|
|
20352
|
+
const wid = this.wo(args.artifactId);
|
|
20353
|
+
const snapshot = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
20354
|
+
const gap = snapshot?.issues.find((i) => i.id === args.gapId && i.kind === "gap" && i.aboutNodeId === args.artifactId);
|
|
20355
|
+
const escalation = snapshot?.issues.find((i) => i.id === args.escalationId && i.kind === "escalation" && i.aboutNodeId === args.artifactId);
|
|
20356
|
+
if (!gap || !escalation || escalation.gapId !== gap.id) throw new Error("\u6062\u590D\u5361\u7684\u7F3A\u53E3\u4E0E\u4E0A\u62A5\u5173\u8054\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u786E\u8BA4");
|
|
20357
|
+
const events = [];
|
|
20358
|
+
for (const issue2 of [gap, escalation]) if (!issue2.resolvedAt) events.push({
|
|
20359
|
+
companyId: "",
|
|
20360
|
+
workorderId: wid,
|
|
20361
|
+
actorId: args.actor,
|
|
20362
|
+
event: {
|
|
20363
|
+
kind: "issue.resolve",
|
|
20364
|
+
workorderId: wid,
|
|
20365
|
+
issueId: issue2.id,
|
|
20366
|
+
state: "resolved",
|
|
20367
|
+
by: args.actor,
|
|
20368
|
+
note: args.reason,
|
|
20369
|
+
viaWorkId: null,
|
|
20370
|
+
closeRelatedEscalations: false
|
|
20371
|
+
}
|
|
20372
|
+
});
|
|
20373
|
+
if (!gap.resolvedAt) events.push({
|
|
20374
|
+
companyId: "",
|
|
20375
|
+
workorderId: wid,
|
|
20376
|
+
actorId: args.actor,
|
|
20377
|
+
event: {
|
|
20378
|
+
kind: "issue.create",
|
|
20379
|
+
workorderId: wid,
|
|
20380
|
+
issueId: `ann:recovery:${gap.id}`,
|
|
20381
|
+
issueKind: "comment",
|
|
20382
|
+
body: args.reason,
|
|
20383
|
+
aboutNodeId: args.artifactId,
|
|
20384
|
+
aboutWorkId: null,
|
|
20385
|
+
raisedByNodeId: null,
|
|
20386
|
+
authorActorId: args.actor,
|
|
20387
|
+
handActorId: null,
|
|
20388
|
+
recipients: [],
|
|
20389
|
+
attachments: [],
|
|
20390
|
+
gapId: gap.id,
|
|
20391
|
+
source: "gap-recovery"
|
|
20392
|
+
}
|
|
20393
|
+
});
|
|
20394
|
+
await this.bus.submitBatchAndProcess(events);
|
|
20395
|
+
this.markWorkorderDirty(wid);
|
|
20396
|
+
await this.refreshTracked();
|
|
20397
|
+
await this.releaseIfStillHeld(args.artifactId, args.actor);
|
|
20398
|
+
}
|
|
20121
20399
|
async resolveEscalation(args) {
|
|
20122
20400
|
if (!args.actor.startsWith("actor:human:")) {
|
|
20123
20401
|
throw new Error("\u5173\u95ED\u5347\u7EA7\u9700\u8981\u4EBA\u660E\u786E\u786E\u8BA4\uFF1Bagent \u8BF7\u63D0\u4EA4\u5173\u95ED\u6388\u6743\u7533\u8BF7");
|
|
@@ -20322,6 +20600,7 @@ var init_kernel_bridge = __esm({
|
|
|
20322
20600
|
async rerunNode(args) {
|
|
20323
20601
|
const wid = this.wo(args.artifactId);
|
|
20324
20602
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
20603
|
+
assertWorkorderDispatchActive(snap);
|
|
20325
20604
|
const node2 = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
20326
20605
|
if (!node2 || node2.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
|
|
20327
20606
|
if (!node2.assigneeActorId) throw new KernelError("\u8282\u70B9\u672A\u6307\u6D3E\u6267\u884C\u4EBA\u2014\u2014\u5148\u6307\u6D3E\u518D\u91CD\u65B0\u8FD0\u884C", "no-assignee");
|
|
@@ -20371,6 +20650,7 @@ var init_kernel_bridge = __esm({
|
|
|
20371
20650
|
async rereviewNode(args) {
|
|
20372
20651
|
const wid = this.wo(args.artifactId);
|
|
20373
20652
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
20653
|
+
assertWorkorderDispatchActive(snap);
|
|
20374
20654
|
const node2 = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
20375
20655
|
if (!node2 || node2.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
|
|
20376
20656
|
const lw = node2.latestWorkId ? snap.works.find((w2) => w2.id === node2.latestWorkId) : null;
|
|
@@ -20573,12 +20853,9 @@ var init_kernel_bridge = __esm({
|
|
|
20573
20853
|
throw new Error(`resolveEscalation \u6388\u6743\u5361\u7F3A escalationId\uFF1A${args.draftId}`);
|
|
20574
20854
|
}
|
|
20575
20855
|
const reason = typeof card2.commandArgs?.["reason"] === "string" ? card2.commandArgs["reason"] : void 0;
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
escalationId,
|
|
20580
|
-
...reason !== void 0 ? { reason } : {}
|
|
20581
|
-
});
|
|
20856
|
+
const gapId = typeof card2.commandArgs?.["gapId"] === "string" ? card2.commandArgs["gapId"] : void 0;
|
|
20857
|
+
if (gapId) await this.resolveRecovery({ artifactId, actor: args.actor, escalationId, gapId, reason: reason ?? "" });
|
|
20858
|
+
else await this.resolveEscalation({ artifactId, actor: args.actor, escalationId, ...reason !== void 0 ? { reason } : {} });
|
|
20582
20859
|
} else {
|
|
20583
20860
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u4E0D\u652F\u6301\u6267\u884C ${card2.command}`);
|
|
20584
20861
|
}
|
|
@@ -20622,11 +20899,29 @@ var init_kernel_bridge = __esm({
|
|
|
20622
20899
|
if (preview2.changeClass === "C" && !_actor.startsWith("actor:human:")) {
|
|
20623
20900
|
throw new Error("C \u7C7B\u6539\u52A8\uFF08\u975E\u5355\u8C03\u6539\u56FE\uFF09\u5FC5\u987B\u7531\u4EBA\u786E\u8BA4\uFF0Cagent \u4E0D\u80FD\u76F4\u63A5\u843D\u5730\u2014\u2014\u8BF7\u8D70 preview \u2192 draft \u2192 \u4EBA apply-draft");
|
|
20624
20901
|
}
|
|
20625
|
-
const wid =
|
|
20902
|
+
const wid = ws ?? plan.ops.find((o) => o.action === "spawn" && o.workspace)?.workspace ?? "ws:default";
|
|
20626
20903
|
let applied = 0;
|
|
20627
20904
|
const unsupported = [];
|
|
20628
20905
|
const reviewerStaffing = [];
|
|
20629
|
-
|
|
20906
|
+
const events = [];
|
|
20907
|
+
const artifacts = new Map([...this.model.artifacts].map(([id, art]) => [id, { ...art, fields: { ...art.fields }, inputs: [...art.inputs] }]));
|
|
20908
|
+
const woFor = (id) => {
|
|
20909
|
+
const art = artifacts.get(id);
|
|
20910
|
+
if (!art) throw new Error(`artifact not found: ${id}`);
|
|
20911
|
+
return art.workspace;
|
|
20912
|
+
};
|
|
20913
|
+
const queue = async (input) => {
|
|
20914
|
+
if (input.event.kind === "plan.update_spec") {
|
|
20915
|
+
const event = input.event;
|
|
20916
|
+
const previous3 = events.find((e) => e.workorderId === input.workorderId && e.event.kind === "plan.update_spec" && e.event.nodeId === event.nodeId);
|
|
20917
|
+
if (previous3?.event.kind === "plan.update_spec") {
|
|
20918
|
+
previous3.event = { ...previous3.event, ...event };
|
|
20919
|
+
return;
|
|
20920
|
+
}
|
|
20921
|
+
}
|
|
20922
|
+
events.push(input);
|
|
20923
|
+
};
|
|
20924
|
+
const enqueue = async (event) => queue({ companyId: "", workorderId: event.workorderId, actorId: _actor, event });
|
|
20630
20925
|
for (const op of plan.ops) {
|
|
20631
20926
|
switch (op.action) {
|
|
20632
20927
|
case "spawn": {
|
|
@@ -20658,7 +20953,7 @@ var init_kernel_bridge = __esm({
|
|
|
20658
20953
|
// ★ executionEnv 同走 fields jsonb(与路径 A 对称——两条 spawn 路径必须一致,前车之鉴:类型默认评审闸)。
|
|
20659
20954
|
...op.executionEnv !== void 0 ? { executionEnv: op.executionEnv } : {}
|
|
20660
20955
|
} : opFields;
|
|
20661
|
-
await
|
|
20956
|
+
await queue({
|
|
20662
20957
|
companyId: "",
|
|
20663
20958
|
workorderId: wid,
|
|
20664
20959
|
actorId: _actor,
|
|
@@ -20688,27 +20983,64 @@ var init_kernel_bridge = __esm({
|
|
|
20688
20983
|
activateWorkorder: false
|
|
20689
20984
|
}
|
|
20690
20985
|
});
|
|
20986
|
+
artifacts.set(id, {
|
|
20987
|
+
id,
|
|
20988
|
+
type: op.type,
|
|
20989
|
+
title: op.title ?? op.type,
|
|
20990
|
+
description: op.description ?? "",
|
|
20991
|
+
workspace: wid,
|
|
20992
|
+
owner: spawnOwner,
|
|
20993
|
+
fields: mergedFields ?? {},
|
|
20994
|
+
reviewRoles: opReviewRoles,
|
|
20995
|
+
reviewers: opReviewers ?? [],
|
|
20996
|
+
inputs: (op.inputs ?? []).map((i) => ({ to: i.to, required: i.required ?? true, pinned: null }))
|
|
20997
|
+
});
|
|
20691
20998
|
applied++;
|
|
20692
20999
|
break;
|
|
20693
21000
|
}
|
|
20694
|
-
case "link":
|
|
20695
|
-
await
|
|
21001
|
+
case "link": {
|
|
21002
|
+
await enqueue({ kind: "plan.add_edge", workorderId: woFor(op.artifactId), fromNodeId: op.to, toNodeId: op.artifactId, edgeKind: "data", required: op.required ?? true });
|
|
21003
|
+
const art = artifacts.get(op.artifactId);
|
|
21004
|
+
if (!art.inputs.some((i) => i.to === op.to)) art.inputs.push({ to: op.to, required: op.required ?? true, pinned: null });
|
|
20696
21005
|
applied++;
|
|
20697
21006
|
break;
|
|
20698
|
-
|
|
20699
|
-
|
|
21007
|
+
}
|
|
21008
|
+
case "unlink": {
|
|
21009
|
+
await enqueue({ kind: "plan.delete_edge", workorderId: woFor(op.artifactId), fromNodeId: op.to, toNodeId: op.artifactId });
|
|
21010
|
+
const art = artifacts.get(op.artifactId);
|
|
21011
|
+
art.inputs = art.inputs.filter((i) => i.to !== op.to);
|
|
20700
21012
|
applied++;
|
|
20701
21013
|
break;
|
|
21014
|
+
}
|
|
20702
21015
|
case "assign":
|
|
20703
|
-
await
|
|
21016
|
+
await enqueue({ kind: "plan.assign_actor", workorderId: woFor(op.artifactId), nodeId: op.artifactId, assigneeActorId: op.owner, assigneeRole: null });
|
|
20704
21017
|
applied++;
|
|
20705
21018
|
break;
|
|
20706
21019
|
case "seal":
|
|
20707
|
-
|
|
21020
|
+
if (op.reason === "cancelled") for (const art of artifacts.values()) {
|
|
21021
|
+
for (const edge of art.inputs.filter((i) => art.id === op.artifactId || i.to === op.artifactId)) {
|
|
21022
|
+
await enqueue({ kind: "plan.delete_edge", workorderId: woFor(art.id), fromNodeId: edge.to, toNodeId: art.id });
|
|
21023
|
+
}
|
|
21024
|
+
art.inputs = art.inputs.filter((i) => art.id !== op.artifactId && i.to !== op.artifactId);
|
|
21025
|
+
}
|
|
21026
|
+
await enqueue({ kind: "plan.delete_node", workorderId: woFor(op.artifactId), nodeId: op.artifactId, sealReason: op.reason });
|
|
20708
21027
|
applied++;
|
|
20709
21028
|
break;
|
|
20710
21029
|
case "annotate":
|
|
20711
|
-
await
|
|
21030
|
+
await enqueue({
|
|
21031
|
+
kind: "issue.create",
|
|
21032
|
+
workorderId: woFor(op.artifactId),
|
|
21033
|
+
issueId: `ann:${(0, import_node_crypto3.randomUUID)()}`,
|
|
21034
|
+
issueKind: "comment",
|
|
21035
|
+
body: op.body,
|
|
21036
|
+
aboutNodeId: op.artifactId,
|
|
21037
|
+
aboutWorkId: null,
|
|
21038
|
+
raisedByNodeId: null,
|
|
21039
|
+
authorActorId: _actor,
|
|
21040
|
+
handActorId: null,
|
|
21041
|
+
recipients: [],
|
|
21042
|
+
attachments: []
|
|
21043
|
+
});
|
|
20712
21044
|
applied++;
|
|
20713
21045
|
break;
|
|
20714
21046
|
case "edit": {
|
|
@@ -20716,7 +21048,7 @@ var init_kernel_bridge = __esm({
|
|
|
20716
21048
|
const opReviewers = op.reviewers;
|
|
20717
21049
|
const editReviewRoles = op.reviewRoles;
|
|
20718
21050
|
if (artId && (opReviewers || editReviewRoles !== void 0)) {
|
|
20719
|
-
const art =
|
|
21051
|
+
const art = artifacts.get(artId);
|
|
20720
21052
|
const effectiveRoles = editReviewRoles !== void 0 ? editReviewRoles === null ? void 0 : editReviewRoles : art?.reviewRoles;
|
|
20721
21053
|
const effectiveExplicit = opReviewers ?? (art?.reviewers ?? []).map((r) => ({ actor: r.actor, source: r.source }));
|
|
20722
21054
|
const { reviewers: nextReqs, staffingIssues } = resolveNodeReviewers({
|
|
@@ -20730,13 +21062,13 @@ var init_kernel_bridge = __esm({
|
|
|
20730
21062
|
reviewerStaffing.push({ nodeId: artId, type: art?.type ?? "", role: issue2.role, from: issue2.from });
|
|
20731
21063
|
console.warn(`[edit] ${artId} \u7684\u300C${issue2.role}\u300D\u8BC4\u5BA1\u95F8\u5728\u5185\u6838 roleIndex \u91CC\u6CA1\u6709\u6301\u6709\u8005\u2014\u2014\u8BE5\u6876\u6682\u7F3A\u3002`);
|
|
20732
21064
|
}
|
|
20733
|
-
await
|
|
21065
|
+
await queue({
|
|
20734
21066
|
companyId: "",
|
|
20735
|
-
workorderId:
|
|
21067
|
+
workorderId: woFor(artId),
|
|
20736
21068
|
actorId: _actor,
|
|
20737
21069
|
event: {
|
|
20738
21070
|
kind: "plan.update_review_requirements",
|
|
20739
|
-
workorderId:
|
|
21071
|
+
workorderId: woFor(artId),
|
|
20740
21072
|
nodeId: artId,
|
|
20741
21073
|
reviewers: nextReqs
|
|
20742
21074
|
}
|
|
@@ -20745,15 +21077,15 @@ var init_kernel_bridge = __esm({
|
|
|
20745
21077
|
const opTitle = op.title;
|
|
20746
21078
|
const opDescription = op.description;
|
|
20747
21079
|
if (artId && (opTitle !== void 0 || opDescription !== void 0)) {
|
|
20748
|
-
await
|
|
21080
|
+
await queue({
|
|
20749
21081
|
companyId: "",
|
|
20750
|
-
workorderId:
|
|
21082
|
+
workorderId: woFor(artId),
|
|
20751
21083
|
actorId: _actor,
|
|
20752
21084
|
event: {
|
|
20753
21085
|
kind: "plan.update_spec",
|
|
20754
|
-
workorderId:
|
|
21086
|
+
workorderId: woFor(artId),
|
|
20755
21087
|
nodeId: artId,
|
|
20756
|
-
spec: opDescription ??
|
|
21088
|
+
spec: opDescription ?? artifacts.get(artId)?.description ?? null,
|
|
20757
21089
|
...opTitle !== void 0 ? { title: opTitle } : {},
|
|
20758
21090
|
policy: "auto"
|
|
20759
21091
|
}
|
|
@@ -20761,21 +21093,30 @@ var init_kernel_bridge = __esm({
|
|
|
20761
21093
|
}
|
|
20762
21094
|
const fields = op.fields;
|
|
20763
21095
|
if (artId && fields) {
|
|
20764
|
-
const editDef = this.schema.get(
|
|
21096
|
+
const editDef = this.schema.get(artifacts.get(artId)?.type ?? "");
|
|
20765
21097
|
const validated = editDef?.fieldDefs ? validateArtifactFields(fields, editDef.fieldDefs, "edit") : fields;
|
|
20766
|
-
await
|
|
21098
|
+
await queue({
|
|
20767
21099
|
companyId: "",
|
|
20768
|
-
workorderId:
|
|
21100
|
+
workorderId: woFor(artId),
|
|
20769
21101
|
actorId: _actor,
|
|
20770
21102
|
event: {
|
|
20771
21103
|
kind: "plan.update_fields",
|
|
20772
|
-
workorderId:
|
|
21104
|
+
workorderId: woFor(artId),
|
|
20773
21105
|
nodeId: artId,
|
|
20774
21106
|
fields: validated ?? {},
|
|
20775
21107
|
merge: true
|
|
20776
21108
|
}
|
|
20777
21109
|
});
|
|
20778
21110
|
}
|
|
21111
|
+
const previous3 = artifacts.get(artId);
|
|
21112
|
+
if (previous3) artifacts.set(artId, {
|
|
21113
|
+
...previous3,
|
|
21114
|
+
...opTitle !== void 0 ? { title: opTitle } : {},
|
|
21115
|
+
...opDescription !== void 0 ? { description: opDescription } : {},
|
|
21116
|
+
...fields ? { fields: { ...previous3.fields, ...fields } } : {},
|
|
21117
|
+
...editReviewRoles !== void 0 ? { reviewRoles: editReviewRoles ?? void 0 } : {},
|
|
21118
|
+
...opReviewers ? { reviewers: opReviewers } : {}
|
|
21119
|
+
});
|
|
20779
21120
|
applied++;
|
|
20780
21121
|
break;
|
|
20781
21122
|
}
|
|
@@ -20784,11 +21125,16 @@ var init_kernel_bridge = __esm({
|
|
|
20784
21125
|
if (!escOp.escalationId) {
|
|
20785
21126
|
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`);
|
|
20786
21127
|
}
|
|
20787
|
-
|
|
20788
|
-
|
|
20789
|
-
|
|
20790
|
-
|
|
20791
|
-
|
|
21128
|
+
const escalation = (this.model.escalations.get(escOp.artifactId) ?? []).find((e) => e.escalationId === escOp.escalationId);
|
|
21129
|
+
if (!escalation) throw new Error(`escalation not found: ${escOp.escalationId}`);
|
|
21130
|
+
await enqueue({
|
|
21131
|
+
kind: "issue.resolve",
|
|
21132
|
+
workorderId: woFor(escOp.artifactId),
|
|
21133
|
+
issueId: escOp.escalationId,
|
|
21134
|
+
state: "resolved",
|
|
21135
|
+
by: _actor,
|
|
21136
|
+
note: escOp.reason ?? null,
|
|
21137
|
+
viaWorkId: null
|
|
20792
21138
|
});
|
|
20793
21139
|
applied++;
|
|
20794
21140
|
break;
|
|
@@ -20798,7 +21144,7 @@ var init_kernel_bridge = __esm({
|
|
|
20798
21144
|
break;
|
|
20799
21145
|
}
|
|
20800
21146
|
}
|
|
20801
|
-
await
|
|
21147
|
+
await queue({
|
|
20802
21148
|
companyId: "",
|
|
20803
21149
|
workorderId: wid,
|
|
20804
21150
|
actorId: _actor,
|
|
@@ -20813,17 +21159,22 @@ var init_kernel_bridge = __esm({
|
|
|
20813
21159
|
activateWorkorder: true
|
|
20814
21160
|
}
|
|
20815
21161
|
});
|
|
21162
|
+
if (unsupported.length) throw new KernelError(`\u6574\u6279\u672A\u843D\u5730\uFF1B\u4EE5\u4E0B op \u672C\u5F15\u64CE\u4E0D\u652F\u6301\uFF1A${unsupported.join("\u3001")}`, "unsupported-op");
|
|
21163
|
+
await this.ensureWorkorder(wid, "", _actor);
|
|
21164
|
+
await this.bus.submitBatchAndProcess(events);
|
|
21165
|
+
for (const event of events) {
|
|
21166
|
+
this.trackedWorkorders.add(event.workorderId);
|
|
21167
|
+
this.markWorkorderDirty(event.workorderId);
|
|
21168
|
+
}
|
|
21169
|
+
await this.refreshTracked();
|
|
20816
21170
|
await this.syncFromBus(wid);
|
|
21171
|
+
for (const id of new Set(plan.ops.filter((op) => op.action === "resolveEscalation").map((op) => op.artifactId))) {
|
|
21172
|
+
await this.releaseIfStillHeld(id, _actor);
|
|
21173
|
+
}
|
|
20817
21174
|
if (typeof plan.resolvesProposal === "string") {
|
|
20818
21175
|
this.draftCards.delete(plan.resolvesProposal);
|
|
20819
21176
|
await this.refreshModel();
|
|
20820
21177
|
}
|
|
20821
|
-
if (unsupported.length > 0) {
|
|
20822
|
-
throw new KernelError(
|
|
20823
|
-
`\u5DF2\u843D\u5730 ${applied} \u6761 op\uFF1B\u4EE5\u4E0B op \u672C\u5F15\u64CE\u4E0D\u652F\u6301\u3001\u672A\u6267\u884C\uFF1A${unsupported.join("\u3001")}\uFF08pin \u5DF2\u5E9F\u5F03\u2014\u2014\u4E0B\u6E38\u7248\u672C\u7531\u9A8C\u6536\u4E0A\u6E38\u81EA\u52A8\u63A8\u8FDB\uFF09`,
|
|
20824
|
-
"unsupported-op"
|
|
20825
|
-
);
|
|
20826
|
-
}
|
|
20827
21178
|
return {
|
|
20828
21179
|
interventionId: `intervention:${(0, import_node_crypto3.randomUUID)()}`,
|
|
20829
21180
|
applied,
|
|
@@ -20875,67 +21226,6 @@ var init_chat_system_message = __esm({
|
|
|
20875
21226
|
}
|
|
20876
21227
|
});
|
|
20877
21228
|
|
|
20878
|
-
// ../server/src/build-info.ts
|
|
20879
|
-
function findRepoRoot() {
|
|
20880
|
-
let dir;
|
|
20881
|
-
try {
|
|
20882
|
-
dir = path.dirname((0, import_node_url2.fileURLToPath)(__esm_import_meta_url));
|
|
20883
|
-
} catch {
|
|
20884
|
-
return null;
|
|
20885
|
-
}
|
|
20886
|
-
for (let i = 0; i < 12; i++) {
|
|
20887
|
-
if (fs.existsSync(path.join(dir, ".git"))) return dir;
|
|
20888
|
-
const parent = path.dirname(dir);
|
|
20889
|
-
if (parent === dir) break;
|
|
20890
|
-
dir = parent;
|
|
20891
|
-
}
|
|
20892
|
-
return null;
|
|
20893
|
-
}
|
|
20894
|
-
function git(root, args) {
|
|
20895
|
-
try {
|
|
20896
|
-
const out = (0, import_node_child_process.execFileSync)("git", args, {
|
|
20897
|
-
cwd: root,
|
|
20898
|
-
encoding: "utf8",
|
|
20899
|
-
timeout: 3e3,
|
|
20900
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
20901
|
-
});
|
|
20902
|
-
const v2 = out.trim();
|
|
20903
|
-
return v2 || null;
|
|
20904
|
-
} catch {
|
|
20905
|
-
return null;
|
|
20906
|
-
}
|
|
20907
|
-
}
|
|
20908
|
-
function getBuildInfo() {
|
|
20909
|
-
if (cached) return cached;
|
|
20910
|
-
const envSha = process.env["OASIS_GIT_SHA"]?.trim();
|
|
20911
|
-
const envBranch = process.env["OASIS_GIT_BRANCH"]?.trim();
|
|
20912
|
-
let gitSha = envSha || null;
|
|
20913
|
-
let gitBranch = envBranch || null;
|
|
20914
|
-
if (!gitSha || !gitBranch) {
|
|
20915
|
-
const root = findRepoRoot();
|
|
20916
|
-
if (root) {
|
|
20917
|
-
if (!gitSha) gitSha = git(root, ["rev-parse", "HEAD"]);
|
|
20918
|
-
if (!gitBranch) {
|
|
20919
|
-
const b2 = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
20920
|
-
gitBranch = b2 === "HEAD" ? null : b2;
|
|
20921
|
-
}
|
|
20922
|
-
}
|
|
20923
|
-
}
|
|
20924
|
-
cached = { gitSha, gitBranch, startedAt: STARTED_AT };
|
|
20925
|
-
return cached;
|
|
20926
|
-
}
|
|
20927
|
-
var import_node_child_process, fs, path, import_node_url2, STARTED_AT, cached;
|
|
20928
|
-
var init_build_info = __esm({
|
|
20929
|
-
"../server/src/build-info.ts"() {
|
|
20930
|
-
"use strict";
|
|
20931
|
-
import_node_child_process = require("node:child_process");
|
|
20932
|
-
fs = __toESM(require("node:fs"), 1);
|
|
20933
|
-
path = __toESM(require("node:path"), 1);
|
|
20934
|
-
import_node_url2 = require("node:url");
|
|
20935
|
-
STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
20936
|
-
}
|
|
20937
|
-
});
|
|
20938
|
-
|
|
20939
21229
|
// ../server/src/facades/api/router.ts
|
|
20940
21230
|
var ApiError, Router;
|
|
20941
21231
|
var init_router = __esm({
|
|
@@ -21081,6 +21371,32 @@ async function saveArtifactContent(kernel, blobs, actor, input) {
|
|
|
21081
21371
|
}
|
|
21082
21372
|
return { ...view, contentRef, text: input.text };
|
|
21083
21373
|
}
|
|
21374
|
+
async function readArtifactFile(kernel, blobs, artifactId, path41, expectedRef) {
|
|
21375
|
+
await kernel.refreshModel();
|
|
21376
|
+
const artifact = kernel.model.artifacts.get(artifactId);
|
|
21377
|
+
const output = artifact?.currentRev ? kernel.model.revisions.get(artifact.currentRev) : void 0;
|
|
21378
|
+
if (!artifact || !output) throw new ApiError(404, "NOT_FOUND", "\u6B64\u4EA7\u7269\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u539F\u6587\u4EF6");
|
|
21379
|
+
if (expectedRef && output.contentRef !== expectedRef) throw new ApiError(409, "CONTENT_CONFLICT", "\u6587\u4EF6\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u91CD\u65B0\u6253\u5F00");
|
|
21380
|
+
let hash2 = output.contentRef;
|
|
21381
|
+
let name = artifact.title || artifactId;
|
|
21382
|
+
if (output.contentKind === "manifest") {
|
|
21383
|
+
if (!path41) throw new ApiError(400, "PATH_REQUIRED", "\u8BF7\u9009\u62E9\u6587\u4EF6\u5939\u4E2D\u7684\u5177\u4F53\u6587\u4EF6");
|
|
21384
|
+
const entry = parseManifest(await blobs.get(output.contentRef)).entries.find((e) => e.path === path41);
|
|
21385
|
+
if (!entry) throw new ApiError(404, "NOT_FOUND", "\u5305\u5185\u6587\u4EF6\u4E0D\u5B58\u5728");
|
|
21386
|
+
hash2 = entry.hash;
|
|
21387
|
+
name = entry.path;
|
|
21388
|
+
if (entry.size !== void 0 && entry.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
|
|
21389
|
+
} else if (output.contentKind !== "inline-blob") {
|
|
21390
|
+
throw new ApiError(404, "NOT_FOUND", "\u6B64\u4EA7\u7269\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u539F\u6587\u4EF6");
|
|
21391
|
+
}
|
|
21392
|
+
const meta = await blobs.meta?.(hash2);
|
|
21393
|
+
if (meta && meta.size > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
|
|
21394
|
+
try {
|
|
21395
|
+
return { bytes: await blobs.get(hash2), name, contentType: meta?.contentType };
|
|
21396
|
+
} catch {
|
|
21397
|
+
throw new ApiError(404, "NOT_FOUND", "\u5F53\u524D\u516C\u53F8\u4E2D\u627E\u4E0D\u5230\u8BE5\u6587\u4EF6\u6B63\u6587");
|
|
21398
|
+
}
|
|
21399
|
+
}
|
|
21084
21400
|
var init_artifact_content2 = __esm({
|
|
21085
21401
|
"../server/src/artifact-content.ts"() {
|
|
21086
21402
|
"use strict";
|
|
@@ -21089,6 +21405,110 @@ var init_artifact_content2 = __esm({
|
|
|
21089
21405
|
}
|
|
21090
21406
|
});
|
|
21091
21407
|
|
|
21408
|
+
// ../server/src/file-http.ts
|
|
21409
|
+
async function serveFileRequest(req, res, url, engine) {
|
|
21410
|
+
try {
|
|
21411
|
+
if (req.method !== "GET" || url.pathname !== "/api/files/content") throw new ApiError(404, "NOT_FOUND", "\u6587\u4EF6\u63A5\u53E3\u4E0D\u5B58\u5728");
|
|
21412
|
+
const { bytes: bytes2, name, contentType } = await readArtifactFile(
|
|
21413
|
+
engine.kernel,
|
|
21414
|
+
engine.blobs,
|
|
21415
|
+
url.searchParams.get("artifact") ?? "",
|
|
21416
|
+
url.searchParams.get("path"),
|
|
21417
|
+
url.searchParams.get("ref")
|
|
21418
|
+
);
|
|
21419
|
+
if (bytes2.length > FILE_CONTENT_MAX_BYTES) throw new ApiError(413, "FILE_TOO_LARGE", "\u6587\u4EF6\u8D85\u8FC7 50 MiB \u8BFB\u53D6\u9650\u5236");
|
|
21420
|
+
res.writeHead(200, {
|
|
21421
|
+
"content-type": contentType ?? sniffContentType(bytes2) ?? "application/octet-stream",
|
|
21422
|
+
"content-length": String(bytes2.length),
|
|
21423
|
+
"cache-control": "private, no-store",
|
|
21424
|
+
"x-content-type-options": "nosniff",
|
|
21425
|
+
"content-disposition": `attachment; filename*=UTF-8''${encodeURIComponent(name.split(/[\\/]/).pop() || "file").replace(/['()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`)}`
|
|
21426
|
+
}).end(Buffer.from(bytes2));
|
|
21427
|
+
} catch (error2) {
|
|
21428
|
+
if (res.destroyed) return;
|
|
21429
|
+
const status = error2 instanceof ApiError ? error2.status : 500;
|
|
21430
|
+
if (!req.complete) {
|
|
21431
|
+
res.setHeader("connection", "close");
|
|
21432
|
+
req.resume();
|
|
21433
|
+
}
|
|
21434
|
+
res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" }).end(JSON.stringify({
|
|
21435
|
+
error: {
|
|
21436
|
+
code: error2 instanceof ApiError ? error2.code : "FILE_READ_FAILED",
|
|
21437
|
+
message: error2 instanceof ApiError ? error2.message : "\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5"
|
|
21438
|
+
}
|
|
21439
|
+
}));
|
|
21440
|
+
}
|
|
21441
|
+
}
|
|
21442
|
+
var init_file_http = __esm({
|
|
21443
|
+
"../server/src/file-http.ts"() {
|
|
21444
|
+
"use strict";
|
|
21445
|
+
init_src();
|
|
21446
|
+
init_artifact_content2();
|
|
21447
|
+
init_router();
|
|
21448
|
+
}
|
|
21449
|
+
});
|
|
21450
|
+
|
|
21451
|
+
// ../server/src/build-info.ts
|
|
21452
|
+
function findRepoRoot() {
|
|
21453
|
+
let dir;
|
|
21454
|
+
try {
|
|
21455
|
+
dir = path.dirname((0, import_node_url2.fileURLToPath)(__esm_import_meta_url));
|
|
21456
|
+
} catch {
|
|
21457
|
+
return null;
|
|
21458
|
+
}
|
|
21459
|
+
for (let i = 0; i < 12; i++) {
|
|
21460
|
+
if (fs.existsSync(path.join(dir, ".git"))) return dir;
|
|
21461
|
+
const parent = path.dirname(dir);
|
|
21462
|
+
if (parent === dir) break;
|
|
21463
|
+
dir = parent;
|
|
21464
|
+
}
|
|
21465
|
+
return null;
|
|
21466
|
+
}
|
|
21467
|
+
function git(root, args) {
|
|
21468
|
+
try {
|
|
21469
|
+
const out = (0, import_node_child_process.execFileSync)("git", args, {
|
|
21470
|
+
cwd: root,
|
|
21471
|
+
encoding: "utf8",
|
|
21472
|
+
timeout: 3e3,
|
|
21473
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
21474
|
+
});
|
|
21475
|
+
const v2 = out.trim();
|
|
21476
|
+
return v2 || null;
|
|
21477
|
+
} catch {
|
|
21478
|
+
return null;
|
|
21479
|
+
}
|
|
21480
|
+
}
|
|
21481
|
+
function getBuildInfo() {
|
|
21482
|
+
if (cached) return cached;
|
|
21483
|
+
const envSha = process.env["OASIS_GIT_SHA"]?.trim();
|
|
21484
|
+
const envBranch = process.env["OASIS_GIT_BRANCH"]?.trim();
|
|
21485
|
+
let gitSha = envSha || null;
|
|
21486
|
+
let gitBranch = envBranch || null;
|
|
21487
|
+
if (!gitSha || !gitBranch) {
|
|
21488
|
+
const root = findRepoRoot();
|
|
21489
|
+
if (root) {
|
|
21490
|
+
if (!gitSha) gitSha = git(root, ["rev-parse", "HEAD"]);
|
|
21491
|
+
if (!gitBranch) {
|
|
21492
|
+
const b2 = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
21493
|
+
gitBranch = b2 === "HEAD" ? null : b2;
|
|
21494
|
+
}
|
|
21495
|
+
}
|
|
21496
|
+
}
|
|
21497
|
+
cached = { gitSha, gitBranch, startedAt: STARTED_AT };
|
|
21498
|
+
return cached;
|
|
21499
|
+
}
|
|
21500
|
+
var import_node_child_process, fs, path, import_node_url2, STARTED_AT, cached;
|
|
21501
|
+
var init_build_info = __esm({
|
|
21502
|
+
"../server/src/build-info.ts"() {
|
|
21503
|
+
"use strict";
|
|
21504
|
+
import_node_child_process = require("node:child_process");
|
|
21505
|
+
fs = __toESM(require("node:fs"), 1);
|
|
21506
|
+
path = __toESM(require("node:path"), 1);
|
|
21507
|
+
import_node_url2 = require("node:url");
|
|
21508
|
+
STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
21509
|
+
}
|
|
21510
|
+
});
|
|
21511
|
+
|
|
21092
21512
|
// ../server/src/command-policy.ts
|
|
21093
21513
|
function classifyCommand(command) {
|
|
21094
21514
|
return COMMAND_POLICY[command] ?? { risk: "direct" };
|
|
@@ -21115,7 +21535,7 @@ var init_command_policy = __esm({
|
|
|
21115
21535
|
// 重复关同一条是幂等的(kernel.resolveEscalation 内部按 escalationId 去重)
|
|
21116
21536
|
// 「关错=静默停摆」的唯一防线是人在卡上一眼看出「关的是哪一条」——escalationId 与 reason 摘要必须上卡。
|
|
21117
21537
|
// a 里 escalationId/artifactId 由 server.ts 起草时冻进 commandArgs(已四级解析),保证是真值、非 `?`。
|
|
21118
|
-
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
|
|
21538
|
+
effect: (a) => typeof a.gapId === "string" ? `\u91C7\u7528\u65B9\u6848\uFF1A${String(a.reason ?? "")}\uFF1B\u89E3\u51B3\u7F3A\u53E3 ${a.gapId}\uFF1B\u5173\u95ED\u5173\u8054\u4E0A\u62A5 ${String(a.escalationId)}\uFF1B\u7EE7\u7EED\u6267\u884C\uFF1A\u662F\uFF08\u4ECD\u987B\u6EE1\u8DB3\u5176\u4ED6\u963B\u585E\u6761\u4EF6\uFF09` : `\u5173\u95ED\u4E0A\u62A5 ${shortId(a.escalationId)}\uFF08\u8282\u70B9 ${shortId(a.artifactId)}${typeof a.reason === "string" ? `\uFF0C\u7406\u7531\uFF1A${a.reason}` : ""}\uFF09`
|
|
21119
21539
|
},
|
|
21120
21540
|
gc: {
|
|
21121
21541
|
risk: "forbidden",
|
|
@@ -28452,6 +28872,16 @@ var init_chat_item_ledger = __esm({
|
|
|
28452
28872
|
version: writeVersion
|
|
28453
28873
|
});
|
|
28454
28874
|
row.ord = created.ord;
|
|
28875
|
+
try {
|
|
28876
|
+
this.deps.onPersisted?.({
|
|
28877
|
+
itemId: id,
|
|
28878
|
+
ord: created.ord,
|
|
28879
|
+
version: writeVersion,
|
|
28880
|
+
role: input.role,
|
|
28881
|
+
text: input.text
|
|
28882
|
+
});
|
|
28883
|
+
} catch {
|
|
28884
|
+
}
|
|
28455
28885
|
}, `create:${input.kind}`);
|
|
28456
28886
|
return id;
|
|
28457
28887
|
}
|
|
@@ -28580,17 +29010,21 @@ async function openAssistantRunningRow(deps) {
|
|
|
28580
29010
|
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28581
29011
|
if (typeof deps.chatStore.appendMessage !== "function") return void 0;
|
|
28582
29012
|
const id = (0, import_node_crypto16.randomUUID)();
|
|
29013
|
+
const at = now();
|
|
28583
29014
|
try {
|
|
28584
29015
|
await deps.chatStore.appendMessage({
|
|
28585
29016
|
id,
|
|
28586
29017
|
sessionId: deps.chatSessionId,
|
|
28587
29018
|
role: "assistant",
|
|
28588
29019
|
content: "",
|
|
28589
|
-
createdAt:
|
|
29020
|
+
createdAt: at,
|
|
28590
29021
|
status: "running",
|
|
28591
29022
|
...deps.runId ? { runId: deps.runId } : {},
|
|
28592
29023
|
...deps.turnId ? { turnId: deps.turnId } : {}
|
|
28593
29024
|
});
|
|
29025
|
+
if (typeof deps.chatStore.updateSession === "function") {
|
|
29026
|
+
await deps.chatStore.updateSession(deps.chatSessionId, { touchedAt: at }).catch(() => void 0);
|
|
29027
|
+
}
|
|
28594
29028
|
return id;
|
|
28595
29029
|
} catch {
|
|
28596
29030
|
return void 0;
|
|
@@ -141794,7 +142228,7 @@ function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
|
141794
142228
|
if (w2.nodeId !== artifactId) continue;
|
|
141795
142229
|
if (isReplyWork(w2)) continue;
|
|
141796
142230
|
nodeWorkIds.add(w2.id);
|
|
141797
|
-
const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
142231
|
+
const status = w2.status ?? (w2.retryAt && w2.deadAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
141798
142232
|
items.push({
|
|
141799
142233
|
type: "work",
|
|
141800
142234
|
id: w2.id,
|
|
@@ -159475,6 +159909,58 @@ var init_live_chat = __esm({
|
|
|
159475
159909
|
}
|
|
159476
159910
|
}
|
|
159477
159911
|
}
|
|
159912
|
+
/**
|
|
159913
|
+
* 把**用户自己那条消息**推上 v3 流(ADR-0510 D2 的收口)。
|
|
159914
|
+
*
|
|
159915
|
+
* ## 为什么需要
|
|
159916
|
+
*
|
|
159917
|
+
* 用户点发送时前端先画一条「乐观条」,它**没有服务端展示序**(D2:不许前端发明号),
|
|
159918
|
+
* 渲染时钉在列表末尾。这条规矩对「刚发完、还没有任何回复」是对的。
|
|
159919
|
+
*
|
|
159920
|
+
* 但服务端落账本时**立刻**给了这条 user item 一个 ord(它是这一轮第一个建的行,号最小),
|
|
159921
|
+
* 而这个号此前**只走快照**——`emitV3` 只服务 provider 事件(`server.ts` 那句注释:
|
|
159922
|
+
* 「不走 v3 流帧(那条只服务 assistant)」)。于是有个几秒的窗口:
|
|
159923
|
+
* agent 的正文带着更大的号从直播流到了、进入「有号」段,而用户那条还在「无号」段被钉在最后
|
|
159924
|
+
* ⇒ **agent 的回复显示在用户消息上面**,等下一次快照才跳回去。
|
|
159925
|
+
* 2026-09-09 用户现场原话:「过几秒钟页面刷新一下,agent 回复内容会回到用户消息下面」。
|
|
159926
|
+
*
|
|
159927
|
+
* 修法就是把这条也推上去:前端按 itemId(`oasis-user-optim:<csid>`,与乐观桶位同名)覆盖,
|
|
159928
|
+
* 拿到真号立刻归位,窗口从「到下一次快照」压到「一次 INSERT 往返」。
|
|
159929
|
+
*
|
|
159930
|
+
* ## 为什么不在前端算
|
|
159931
|
+
*
|
|
159932
|
+
* 试过的三种都被证伪(见 `chat-items-merge.ts` 里那段注释)。这次也不能用「记下创建时的最大号」:
|
|
159933
|
+
* 空对话首次发送时那个最大号是 0,随后到达的历史(号更大)会全部排到乐观条**后面**——
|
|
159934
|
+
* 正是 ADR D9 点名必须验的「空桶先发送」那一档。号只能由服务端给。
|
|
159935
|
+
*
|
|
159936
|
+
* ## 拿不到轮就静默跳过
|
|
159937
|
+
*
|
|
159938
|
+
* 本方法由账本的落库链回调触发,而那一刻这条会话的 live 轮可能还没 `start`
|
|
159939
|
+
* (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
|
|
159940
|
+
* 前端退回「等下一次快照」,与修复前一致,不会更坏。
|
|
159941
|
+
*/
|
|
159942
|
+
publishUserItem(chatSessionId, item) {
|
|
159943
|
+
const turn = this.turns.get(chatSessionId);
|
|
159944
|
+
if (!turn) return false;
|
|
159945
|
+
const payload = { role: "user", text: item.text };
|
|
159946
|
+
if (item.clientSubmitId) payload.clientSubmitId = item.clientSubmitId;
|
|
159947
|
+
if (item.attachments?.length) payload.attachments = item.attachments;
|
|
159948
|
+
const frame = {
|
|
159949
|
+
protocolVersion: 3,
|
|
159950
|
+
streamId: turn.liveStreamId,
|
|
159951
|
+
turnId: this.wireTurnId(turn),
|
|
159952
|
+
itemId: item.itemId,
|
|
159953
|
+
itemType: "message",
|
|
159954
|
+
operation: "set_text",
|
|
159955
|
+
itemVersion: item.version,
|
|
159956
|
+
...item.ord !== null ? { ord: item.ord } : {},
|
|
159957
|
+
offset: 0,
|
|
159958
|
+
payload
|
|
159959
|
+
};
|
|
159960
|
+
this.flushPendingV3(turn);
|
|
159961
|
+
this.publishV3(turn, this.assignV3Seq(turn, frame));
|
|
159962
|
+
return true;
|
|
159963
|
+
}
|
|
159478
159964
|
emitV3TurnTerminal(turn, status) {
|
|
159479
159965
|
if (status !== "done" && status !== "error") return;
|
|
159480
159966
|
const operation = status === "done" ? "turn_completed" : "turn_failed";
|
|
@@ -194349,8 +194835,15 @@ function isTextual(contentType) {
|
|
|
194349
194835
|
const t = contentType.toLowerCase();
|
|
194350
194836
|
return t.startsWith("text/") || t.includes("json") || t.includes("xml") || t.includes("yaml") || t.includes("javascript");
|
|
194351
194837
|
}
|
|
194352
|
-
function toPrepared(rel, source, read) {
|
|
194353
|
-
const base = {
|
|
194838
|
+
function toPrepared(rel, source, read, src) {
|
|
194839
|
+
const base = {
|
|
194840
|
+
rel,
|
|
194841
|
+
source,
|
|
194842
|
+
size: read.size,
|
|
194843
|
+
contentType: read.contentType,
|
|
194844
|
+
...src ? { srcSize: src.size } : {},
|
|
194845
|
+
...src?.mtime ? { srcMtime: src.mtime } : {}
|
|
194846
|
+
};
|
|
194354
194847
|
if (!isTextual(read.contentType)) return { ...base, base64: read.contentBase64 };
|
|
194355
194848
|
return { ...base, text: Buffer.from(read.contentBase64, "base64").toString("utf8") };
|
|
194356
194849
|
}
|
|
@@ -194387,7 +194880,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194387
194880
|
if (!lenient) throw error2;
|
|
194388
194881
|
skipped.push({ source, code: error2.code, message: error2.message });
|
|
194389
194882
|
};
|
|
194390
|
-
if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0 };
|
|
194883
|
+
if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
|
|
194391
194884
|
if (!port) {
|
|
194392
194885
|
const error2 = new DelegationFileError(
|
|
194393
194886
|
"FILE_UNREADABLE",
|
|
@@ -194396,7 +194889,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194396
194889
|
);
|
|
194397
194890
|
if (!lenient) throw error2;
|
|
194398
194891
|
for (const source of wanted) skipped.push({ source, code: error2.code, message: error2.message });
|
|
194399
|
-
return { prepared: [], skipped, totalBytes: 0 };
|
|
194892
|
+
return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
|
|
194400
194893
|
}
|
|
194401
194894
|
let accepted = wanted;
|
|
194402
194895
|
if (wanted.length > limits.maxFiles) {
|
|
@@ -194474,10 +194967,10 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194474
194967
|
used.add(rel);
|
|
194475
194968
|
prepared.push(toPrepared(rel, source, read));
|
|
194476
194969
|
}
|
|
194477
|
-
return { prepared, skipped, totalBytes };
|
|
194970
|
+
return { prepared, skipped, totalBytes, unchanged: [] };
|
|
194478
194971
|
}
|
|
194479
|
-
async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS) {
|
|
194480
|
-
const out = { prepared: [], skipped: [], totalBytes: 0 };
|
|
194972
|
+
async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS, known) {
|
|
194973
|
+
const out = { prepared: [], skipped: [], totalBytes: 0, unchanged: [] };
|
|
194481
194974
|
if (!port) return out;
|
|
194482
194975
|
if (port.locate) {
|
|
194483
194976
|
const located = await port.locate(child).catch((err) => ({
|
|
@@ -194537,7 +195030,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194537
195030
|
for (const entry of level.entries) {
|
|
194538
195031
|
const childPath = `${dir}/${entry.name}`;
|
|
194539
195032
|
if (entry.type === "dir") queue.push(childPath);
|
|
194540
|
-
else files.push({ rel: childPath, size: entry.size });
|
|
195033
|
+
else files.push({ rel: childPath, size: entry.size, ...entry.mtime ? { mtime: entry.mtime } : {} });
|
|
194541
195034
|
}
|
|
194542
195035
|
}
|
|
194543
195036
|
for (const dir of queue) {
|
|
@@ -194553,6 +195046,12 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194553
195046
|
out.skipped.push({ source: file.rel, code: "PATH_REJECTED", message: "\u8DEF\u5F84\u4E0D\u5408\u6CD5\uFF0C\u6CA1\u642C" });
|
|
194554
195047
|
continue;
|
|
194555
195048
|
}
|
|
195049
|
+
const target = `${DELEGATION_OUTBOUND_DIR}/${dirName}/${relative5}`;
|
|
195050
|
+
const seen = known?.get(target);
|
|
195051
|
+
if (seen && file.mtime && seen.size === file.size && seen.mtime === file.mtime) {
|
|
195052
|
+
out.unchanged.push(target);
|
|
195053
|
+
continue;
|
|
195054
|
+
}
|
|
194556
195055
|
if (out.prepared.length >= limits.maxFiles) {
|
|
194557
195056
|
out.skipped.push({ source: file.rel, code: "FILE_QUOTA_EXCEEDED", message: `\u8D85\u8FC7\u4E00\u6B21 ${limits.maxFiles} \u4E2A\u7684\u4E0A\u9650\uFF0C\u6CA1\u642C` });
|
|
194558
195057
|
continue;
|
|
@@ -194571,7 +195070,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194571
195070
|
continue;
|
|
194572
195071
|
}
|
|
194573
195072
|
out.totalBytes += read.size;
|
|
194574
|
-
out.prepared.push(toPrepared(
|
|
195073
|
+
out.prepared.push(toPrepared(target, file.rel, read, { size: file.size, ...file.mtime ? { mtime: file.mtime } : {} }));
|
|
194575
195074
|
}
|
|
194576
195075
|
return out;
|
|
194577
195076
|
}
|
|
@@ -194628,6 +195127,31 @@ function digestOf(text5) {
|
|
|
194628
195127
|
const line = text5.trim().replace(/\s+/g, " ");
|
|
194629
195128
|
return line.length > DIGEST_CHARS ? `${line.slice(0, DIGEST_CHARS)}\u2026` : line;
|
|
194630
195129
|
}
|
|
195130
|
+
function childRoundMessageIds(childSessionId, round) {
|
|
195131
|
+
const suffix = round <= 1 ? "" : `:${round}`;
|
|
195132
|
+
return {
|
|
195133
|
+
inputId: deterministicUuid(`delegation-input:${childSessionId}${suffix}`),
|
|
195134
|
+
placeholderId: deterministicUuid(`delegation-placeholder:${childSessionId}${suffix}`)
|
|
195135
|
+
};
|
|
195136
|
+
}
|
|
195137
|
+
async function seedChildRoundRows(chatStore, childSessionId, round, prompt2, now) {
|
|
195138
|
+
const ids2 = childRoundMessageIds(childSessionId, round);
|
|
195139
|
+
await chatStore.appendMessageOnce({
|
|
195140
|
+
id: ids2.inputId,
|
|
195141
|
+
sessionId: childSessionId,
|
|
195142
|
+
role: "user",
|
|
195143
|
+
content: prompt2,
|
|
195144
|
+
createdAt: now
|
|
195145
|
+
});
|
|
195146
|
+
await chatStore.appendMessageOnce({
|
|
195147
|
+
id: ids2.placeholderId,
|
|
195148
|
+
sessionId: childSessionId,
|
|
195149
|
+
role: "assistant",
|
|
195150
|
+
content: "",
|
|
195151
|
+
status: "running",
|
|
195152
|
+
createdAt: now
|
|
195153
|
+
});
|
|
195154
|
+
}
|
|
194631
195155
|
var import_node_crypto32, DelegationError, TITLE_TASK_CHARS, DIGEST_CHARS, PENDING_QUEUE_OWNER, DISPATCH_LEASE_MS, CONSECUTIVE_FAIL_LIMIT, DelegationService;
|
|
194632
195156
|
var init_service3 = __esm({
|
|
194633
195157
|
"../server/src/domains/delegations/service.ts"() {
|
|
@@ -195053,21 +195577,7 @@ var init_service3 = __esm({
|
|
|
195053
195577
|
createdAt: now
|
|
195054
195578
|
});
|
|
195055
195579
|
}
|
|
195056
|
-
await chatStore.
|
|
195057
|
-
id: deterministicUuid(`delegation-input:${record8.childSessionId}`),
|
|
195058
|
-
sessionId: record8.childSessionId,
|
|
195059
|
-
role: "user",
|
|
195060
|
-
content: task,
|
|
195061
|
-
createdAt: now
|
|
195062
|
-
});
|
|
195063
|
-
await chatStore.appendMessageOnce({
|
|
195064
|
-
id: deterministicUuid(`delegation-placeholder:${record8.childSessionId}`),
|
|
195065
|
-
sessionId: record8.childSessionId,
|
|
195066
|
-
role: "assistant",
|
|
195067
|
-
content: "",
|
|
195068
|
-
status: "running",
|
|
195069
|
-
createdAt: now
|
|
195070
|
-
});
|
|
195580
|
+
await seedChildRoundRows(chatStore, record8.childSessionId, 1, task, now);
|
|
195071
195581
|
}
|
|
195072
195582
|
/**
|
|
195073
195583
|
* 去程文件:读父会话工作区、翻成 §8.2 的同步错误码。
|
|
@@ -195227,7 +195737,7 @@ var init_service3 = __esm({
|
|
|
195227
195737
|
settledAt: failedAt
|
|
195228
195738
|
}).catch(() => null);
|
|
195229
195739
|
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
195230
|
-
await chatStore?.updateMessage(
|
|
195740
|
+
await chatStore?.updateMessage(childRoundMessageIds(record8.childSessionId, record8.roundSummaries.length + 1).placeholderId, {
|
|
195231
195741
|
content: `\u6D3E\u53D1\u6CA1\u80FD\u8D77\u6765\uFF1A${message}`,
|
|
195232
195742
|
status: "error",
|
|
195233
195743
|
completedAt: failedAt
|
|
@@ -195276,7 +195786,7 @@ var init_service3 = __esm({
|
|
|
195276
195786
|
const pending = await store.countPendingTouches(fresh.id).catch(() => 0);
|
|
195277
195787
|
const stopLoss = consecutiveFails >= CONSECUTIVE_FAIL_LIMIT;
|
|
195278
195788
|
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
195279
|
-
await chatStore?.updateMessage(
|
|
195789
|
+
await chatStore?.updateMessage(childRoundMessageIds(fresh.childSessionId, round).placeholderId, {
|
|
195280
195790
|
content: text5 || (failureKind ? `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}` : ""),
|
|
195281
195791
|
status: failureKind ? "error" : "done",
|
|
195282
195792
|
completedAt: at
|
|
@@ -195288,11 +195798,13 @@ var init_service3 = __esm({
|
|
|
195288
195798
|
});
|
|
195289
195799
|
return;
|
|
195290
195800
|
}
|
|
195291
|
-
const
|
|
195801
|
+
const settledSeqAtRound = fresh.settledSeq + 1;
|
|
195802
|
+
const settledSummary = { ...newSummary, settledSeq: settledSeqAtRound };
|
|
195803
|
+
const settled = await store.appendRoundSummary(fresh.id, settledSummary, {
|
|
195292
195804
|
consecutiveFails,
|
|
195293
195805
|
state: failureKind ? "failed" : "done",
|
|
195294
195806
|
failureKind: failureKind ?? null,
|
|
195295
|
-
settledSeq:
|
|
195807
|
+
settledSeq: settledSeqAtRound,
|
|
195296
195808
|
settledAt: at
|
|
195297
195809
|
}).catch(() => null);
|
|
195298
195810
|
if (settled) await this.notifySettled(settled, companyId);
|
|
@@ -195321,17 +195833,19 @@ var init_service3 = __esm({
|
|
|
195321
195833
|
const record8 = await store.getByChildSession(childSessionId).catch(() => null);
|
|
195322
195834
|
if (!record8 || record8.state !== "running") return record8;
|
|
195323
195835
|
const at = this.now();
|
|
195836
|
+
const settledSeqAtRound = record8.settledSeq + 1;
|
|
195324
195837
|
const newSummary = {
|
|
195325
195838
|
round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
|
|
195326
195839
|
text: `\u8FD9\u4E00\u8F6E\u88AB\u65F6\u949F\u95ED\u5408\uFF1A${reason}`,
|
|
195327
|
-
at
|
|
195840
|
+
at,
|
|
195841
|
+
settledSeq: settledSeqAtRound
|
|
195328
195842
|
};
|
|
195329
195843
|
const settled = await store.appendRoundSummary(record8.id, newSummary, {
|
|
195330
195844
|
state: "failed",
|
|
195331
195845
|
// 退出帧丢失后被时钟闭合 = `interrupted`(§9),不是 `not-started`——它确实起来过。
|
|
195332
195846
|
failureKind: "interrupted",
|
|
195333
195847
|
consecutiveFails: record8.consecutiveFails + 1,
|
|
195334
|
-
settledSeq:
|
|
195848
|
+
settledSeq: settledSeqAtRound,
|
|
195335
195849
|
settledAt: at
|
|
195336
195850
|
}).catch(() => null);
|
|
195337
195851
|
if (settled) await this.notifySettled(settled, companyId);
|
|
@@ -195417,6 +195931,15 @@ var init_service3 = __esm({
|
|
|
195417
195931
|
onDispatched: async () => {
|
|
195418
195932
|
await queue.confirm();
|
|
195419
195933
|
await store.markTouchesConsumed(record8.id, touches.map((touch) => touch.messageId), this.now());
|
|
195934
|
+
await seedChildRoundRows(
|
|
195935
|
+
chatStore,
|
|
195936
|
+
record8.childSessionId,
|
|
195937
|
+
record8.roundSummaries.length + 1,
|
|
195938
|
+
body2,
|
|
195939
|
+
this.now()
|
|
195940
|
+
).catch((err) => {
|
|
195941
|
+
console.warn(`[delegation] \u5B50\u4F1A\u8BDD\u8865\u8F6E\u6B21\u884C\u5931\u8D25\uFF08${record8.id} round=${record8.roundSummaries.length + 1}\uFF09: ${String(err)}`);
|
|
195942
|
+
});
|
|
195420
195943
|
},
|
|
195421
195944
|
/**
|
|
195422
195945
|
* **没送到就把正文放回可读态**(见 `dispatchExpert` 的 `onNotDispatched`)。
|
|
@@ -195515,6 +196038,10 @@ var init_service3 = __esm({
|
|
|
195515
196038
|
const inbound = await this.readInboundFiles(parent, files);
|
|
195516
196039
|
const inboundBundle = toBundleFiles(inbound.prepared);
|
|
195517
196040
|
let delivered = false;
|
|
196041
|
+
let deliveryReasonCode;
|
|
196042
|
+
if (!this.options.appendToChild) {
|
|
196043
|
+
deliveryReasonCode = "no-append-port";
|
|
196044
|
+
}
|
|
195518
196045
|
if (this.options.appendToChild) {
|
|
195519
196046
|
const result = await this.options.appendToChild(record8.childSessionId, {
|
|
195520
196047
|
// 文件是静默落盘的,append 这条路没有 TASK.md 可改——**必须自己在正文里说一声**,
|
|
@@ -195530,6 +196057,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195530
196057
|
...Object.keys(inboundBundle.binaryFiles).length ? { binaryFiles: inboundBundle.binaryFiles } : {}
|
|
195531
196058
|
}).catch(() => ({ accepted: false, reason: "append-threw" }));
|
|
195532
196059
|
delivered = result.accepted === true;
|
|
196060
|
+
if (!delivered) deliveryReasonCode = result.reason ?? "rejected";
|
|
195533
196061
|
}
|
|
195534
196062
|
if (!delivered) {
|
|
195535
196063
|
if (chatStore.enqueuePendingMessage) {
|
|
@@ -195588,6 +196116,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195588
196116
|
}
|
|
195589
196117
|
return {
|
|
195590
196118
|
delivered,
|
|
196119
|
+
...delivered ? {} : { deliveryReasonCode: deliveryReasonCode ?? "rejected" },
|
|
195591
196120
|
startedRound,
|
|
195592
196121
|
pendingInputs: await this.pendingInputs(store, record8.id),
|
|
195593
196122
|
// 起了新的一轮 = 快照与文件都随那一轮重新送过去了,没有「等下一轮再刷新」这回事。
|
|
@@ -195710,6 +196239,34 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195710
196239
|
}
|
|
195711
196240
|
});
|
|
195712
196241
|
|
|
196242
|
+
// ../server/src/domains/delegations/continue-outcome.ts
|
|
196243
|
+
function describeContinueOutcome(input) {
|
|
196244
|
+
if (input.delivered) return { outcome: "inserted" };
|
|
196245
|
+
if (input.startedRound) return { outcome: "started-round" };
|
|
196246
|
+
const code2 = input.deliveryReasonCode ?? "rejected";
|
|
196247
|
+
return {
|
|
196248
|
+
outcome: "queued",
|
|
196249
|
+
// 认不出的码**原样带出来**,不折成「未知原因」——排障时那串字符本身就是线索。
|
|
196250
|
+
reason: REASON_TEXT[code2] ?? `\u672A\u80FD\u63D2\u5165\uFF08${code2}\uFF09`,
|
|
196251
|
+
reasonCode: code2
|
|
196252
|
+
};
|
|
196253
|
+
}
|
|
196254
|
+
var REASON_TEXT;
|
|
196255
|
+
var init_continue_outcome = __esm({
|
|
196256
|
+
"../server/src/domains/delegations/continue-outcome.ts"() {
|
|
196257
|
+
"use strict";
|
|
196258
|
+
REASON_TEXT = {
|
|
196259
|
+
"no-live-turn": "\u4E13\u5BB6\u6B64\u523B\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\uFF08\u591A\u534A\u662F\u521A\u6536\u5C3E\u3001\u4E0B\u4E00\u8F6E\u8FD8\u6CA1\u8D77\u6765\uFF09",
|
|
196260
|
+
unsupported: "\u8FD9\u4E2A\u4E13\u5BB6\u7528\u7684\u6A21\u578B\u4E0D\u652F\u6301\u4E2D\u9014\u63D2\u8BDD\uFF08\u8981\u7B49\u5B83\u8FD9\u4E00\u8F6E\u8DD1\u5B8C\uFF09",
|
|
196261
|
+
"session-closing": "\u4E13\u5BB6\u90A3\u4E00\u8F6E\u6B63\u5728\u6536\u5C3E\uFF0C\u8FD9\u53E5\u8BDD\u6765\u665A\u4E86\u4E00\u6B65",
|
|
196262
|
+
"turn-finished": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u4E13\u5BB6\u90A3\u4E00\u8F6E\u5DF2\u7ECF\u6536\u5C3E\u4E86",
|
|
196263
|
+
rejected: "\u4E13\u5BB6\u90A3\u4E00\u4FA7\u62D2\u6536\u4E86\u8FD9\u6B21\u63D2\u5165",
|
|
196264
|
+
"append-threw": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u51FA\u9519\uFF08\u7F51\u7EDC\u6216\u8282\u70B9\u5F02\u5E38\uFF09",
|
|
196265
|
+
"no-append-port": "\u8FD9\u5957\u90E8\u7F72\u6CA1\u6709\u63A5\u4E2D\u9014\u63D2\u5165\u901A\u9053"
|
|
196266
|
+
};
|
|
196267
|
+
}
|
|
196268
|
+
});
|
|
196269
|
+
|
|
195713
196270
|
// ../server/src/domains/delegations/routes.ts
|
|
195714
196271
|
function trustedDelegationInvocationId(body2, turn) {
|
|
195715
196272
|
const scope = turn?.artifactId ?? turn?.dispatchId;
|
|
@@ -195778,8 +196335,16 @@ function delegationRoutes(service) {
|
|
|
195778
196335
|
const text5 = requireString(raw["text"], "--text");
|
|
195779
196336
|
const files = requireStringArray(raw["files"], "--file");
|
|
195780
196337
|
try {
|
|
195781
|
-
await service.continueDelegation(req.params.id, text5, callerOf(req), files);
|
|
195782
|
-
|
|
196338
|
+
const res = await service.continueDelegation(req.params.id, text5, callerOf(req), files);
|
|
196339
|
+
const view = describeContinueOutcome(res);
|
|
196340
|
+
const body2 = {
|
|
196341
|
+
outcome: view.outcome,
|
|
196342
|
+
...view.reason ? { reason: view.reason } : {},
|
|
196343
|
+
...view.reasonCode ? { reasonCode: view.reasonCode } : {},
|
|
196344
|
+
pendingInputs: res.pendingInputs,
|
|
196345
|
+
...res.attachedFiles.length ? { attachedFiles: res.attachedFiles } : {}
|
|
196346
|
+
};
|
|
196347
|
+
return { status: 200, body: body2 };
|
|
195783
196348
|
} catch (error2) {
|
|
195784
196349
|
rethrow(error2);
|
|
195785
196350
|
}
|
|
@@ -195807,6 +196372,7 @@ var init_routes2 = __esm({
|
|
|
195807
196372
|
"use strict";
|
|
195808
196373
|
import_node_crypto33 = require("node:crypto");
|
|
195809
196374
|
init_router();
|
|
196375
|
+
init_continue_outcome();
|
|
195810
196376
|
init_service3();
|
|
195811
196377
|
}
|
|
195812
196378
|
});
|
|
@@ -196178,6 +196744,9 @@ function delegationReturnText(terminal, opts) {
|
|
|
196178
196744
|
if (opts.movedPaths.length > 0) {
|
|
196179
196745
|
lines.push("", "\u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6\uFF08\u5DF2\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...opts.movedPaths.map((p2) => `- ${p2}`));
|
|
196180
196746
|
}
|
|
196747
|
+
if ((opts.unchangedPaths?.length ?? 0) > 0) {
|
|
196748
|
+
lines.push("", `\u53E6\u6709 ${opts.unchangedPaths.length} \u4E2A\u6587\u4EF6\u4E0E\u4E0A\u6B21\u4EA4\u56DE\u65F6\u9010\u5B57\u4E00\u81F4\uFF0C\u672C\u8F6E\u6CA1\u6709\u91CD\u4F20\uFF08\u4ECD\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u8DEF\u5F84\u4E0D\u53D8\uFF09\u3002`);
|
|
196749
|
+
}
|
|
196181
196750
|
const locationFailures = opts.skipped.filter((s2) => s2.code !== void 0 && isLocationFailureCode(s2.code));
|
|
196182
196751
|
const contentSkipped = opts.skipped.filter((s2) => s2.code === void 0 || !isLocationFailureCode(s2.code));
|
|
196183
196752
|
if (locationFailures.length > 0) {
|
|
@@ -196217,17 +196786,21 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196217
196786
|
};
|
|
196218
196787
|
}
|
|
196219
196788
|
const dirName = delegationDirName(record8.label, record8.expertActorId, record8.id);
|
|
196789
|
+
const known = delegationOutboxFingerprints(record8.roundSummaries);
|
|
196220
196790
|
const outbound = await prepareOutboundFiles(
|
|
196221
196791
|
deps.workdir,
|
|
196222
196792
|
// 子会话与父会话同公司(§6.1.2 隔离二),所以这里的 companyId 取父会话那个。
|
|
196223
196793
|
{ chatSessionId: record8.childSessionId, ...parentSession.companyId ? { companyId: parentSession.companyId } : {} },
|
|
196224
|
-
dirName
|
|
196794
|
+
dirName,
|
|
196795
|
+
void 0,
|
|
196796
|
+
known
|
|
196225
196797
|
).catch((err) => {
|
|
196226
196798
|
log3(`[delegation-return] \u4EA7\u7269\u642C\u8FD0\u5931\u8D25\uFF08delegation=${record8.id}\uFF09: ${String(err)}`);
|
|
196227
196799
|
return {
|
|
196228
196800
|
prepared: [],
|
|
196229
196801
|
skipped: [{ source: "outputs/", code: "INTERNAL", message: "\u642C\u8FD0\u5931\u8D25" }],
|
|
196230
196802
|
totalBytes: 0,
|
|
196803
|
+
unchanged: [],
|
|
196231
196804
|
listDirCode: "INTERNAL"
|
|
196232
196805
|
};
|
|
196233
196806
|
});
|
|
@@ -196247,8 +196820,18 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196247
196820
|
const text5 = delegationReturnText(terminal, {
|
|
196248
196821
|
movedPaths: bundleFiles.paths,
|
|
196249
196822
|
skipped: outbound.skipped,
|
|
196250
|
-
recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS
|
|
196823
|
+
recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS,
|
|
196824
|
+
unchangedPaths: outbound.unchanged
|
|
196251
196825
|
});
|
|
196826
|
+
const movedFileRefs = outbound.prepared.map((f2) => ({
|
|
196827
|
+
name: f2.rel,
|
|
196828
|
+
...typeof f2.srcSize === "number" ? { size: f2.srcSize } : {},
|
|
196829
|
+
...f2.srcMtime ? { mtime: f2.srcMtime } : {}
|
|
196830
|
+
}));
|
|
196831
|
+
const fingerprints = {};
|
|
196832
|
+
for (const f2 of movedFileRefs) {
|
|
196833
|
+
if (typeof f2.size === "number" || f2.mtime) fingerprints[f2.name] = { ...typeof f2.size === "number" ? { size: f2.size } : {}, ...f2.mtime ? { mtime: f2.mtime } : {} };
|
|
196834
|
+
}
|
|
196252
196835
|
const source = delegationReturnSource(record8);
|
|
196253
196836
|
const messageId = deriveSystemMessageId(source);
|
|
196254
196837
|
let written;
|
|
@@ -196279,6 +196862,8 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196279
196862
|
const bundle = bundleFiles.paths.length ? {
|
|
196280
196863
|
files: bundleFiles.files,
|
|
196281
196864
|
binaryFiles: bundleFiles.binaryFiles,
|
|
196865
|
+
// queued 那一档要靠它把指纹带到 flush 落账那一刻(`PendingChatAttachment.srcSize`)。
|
|
196866
|
+
fingerprints,
|
|
196282
196867
|
taskLines: [
|
|
196283
196868
|
"",
|
|
196284
196869
|
"## \u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6",
|
|
@@ -196313,6 +196898,8 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196313
196898
|
via: delivery.via,
|
|
196314
196899
|
movedFiles: bundleFiles.paths.length,
|
|
196315
196900
|
movedPaths: bundleFiles.paths,
|
|
196901
|
+
movedFileRefs,
|
|
196902
|
+
unchangedPaths: outbound.unchanged,
|
|
196316
196903
|
skippedFiles: outbound.skipped.length
|
|
196317
196904
|
};
|
|
196318
196905
|
}
|
|
@@ -196324,6 +196911,7 @@ var init_return_flow = __esm({
|
|
|
196324
196911
|
init_chat_system_message();
|
|
196325
196912
|
init_chat_broadcast();
|
|
196326
196913
|
init_files();
|
|
196914
|
+
init_src();
|
|
196327
196915
|
}
|
|
196328
196916
|
});
|
|
196329
196917
|
|
|
@@ -196467,8 +197055,10 @@ function assembleDelegationsDomain(deps) {
|
|
|
196467
197055
|
);
|
|
196468
197056
|
const targetRound = record8.roundSummaries[record8.roundSummaries.length - 1]?.round;
|
|
196469
197057
|
let movedPaths = null;
|
|
197058
|
+
let movedRefs = [];
|
|
196470
197059
|
if (outcome.status === "delivered" && (outcome.via === "turn" || outcome.via === "append") && outcome.movedPaths.length > 0) {
|
|
196471
197060
|
movedPaths = outcome.movedPaths;
|
|
197061
|
+
movedRefs = outcome.movedFileRefs;
|
|
196472
197062
|
}
|
|
196473
197063
|
if (movedPaths === null || targetRound === void 0) return;
|
|
196474
197064
|
const store = await deps.resolveStore(companyId).catch(() => null);
|
|
@@ -196481,7 +197071,7 @@ function assembleDelegationsDomain(deps) {
|
|
|
196481
197071
|
`[delegation-return] \u4EA7\u7269\u6E05\u5355\u843D\u8D26\u672A\u6210 (immediate) delegation=${record8.id} round=${targetRound} status=resolve-failed moved=${movedPaths.length} paths=${pathsField}`
|
|
196482
197072
|
);
|
|
196483
197073
|
} else {
|
|
196484
|
-
outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedPaths);
|
|
197074
|
+
outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedRefs.length ? movedRefs : movedPaths);
|
|
196485
197075
|
if (outcomeCode === "ok") return;
|
|
196486
197076
|
deps.metrics?.recordFilesRegisterFailure?.(outcomeCode);
|
|
196487
197077
|
warn(
|
|
@@ -196495,6 +197085,11 @@ function assembleDelegationsDomain(deps) {
|
|
|
196495
197085
|
chatSessionId: record8.parentSessionId,
|
|
196496
197086
|
delegationRef: { delegationId: record8.id, round: targetRound },
|
|
196497
197087
|
paths: movedPaths,
|
|
197088
|
+
// 补账那一趟同样要带指纹,否则「首次登记失败 → 补账成功」的路径会留下无指纹条目。
|
|
197089
|
+
fingerprints: Object.fromEntries(movedRefs.map((f2) => [f2.name, {
|
|
197090
|
+
...typeof f2.size === "number" ? { size: f2.size } : {},
|
|
197091
|
+
...f2.mtime ? { mtime: f2.mtime } : {}
|
|
197092
|
+
}])),
|
|
196498
197093
|
reason: `immediate-register-failed:${outcomeCode}`
|
|
196499
197094
|
}).catch((err) => {
|
|
196500
197095
|
warn(
|
|
@@ -196517,16 +197112,20 @@ async function registerDelegationFilesOnFlush(deps, row, companyId) {
|
|
|
196517
197112
|
const warn = deps.log ?? ((m2) => console.warn(m2));
|
|
196518
197113
|
const ref2 = row.delegationRef;
|
|
196519
197114
|
if (!ref2) return "no-ref";
|
|
196520
|
-
const
|
|
196521
|
-
if (
|
|
197115
|
+
const rows = (row.attachments ?? []).filter((a) => !!a.path);
|
|
197116
|
+
if (rows.length === 0) return "no-files";
|
|
196522
197117
|
const store = await deps.resolveStore(companyId).catch(() => null);
|
|
196523
197118
|
if (!store) return "failed";
|
|
196524
|
-
const files =
|
|
197119
|
+
const files = rows.map((a) => ({
|
|
197120
|
+
name: a.path,
|
|
197121
|
+
...typeof a.srcSize === "number" ? { size: a.srcSize } : {},
|
|
197122
|
+
...a.srcMtime ? { mtime: a.srcMtime } : {}
|
|
197123
|
+
}));
|
|
196525
197124
|
const outcome = await registerRoundFiles(store, ref2.delegationId, ref2.round, files);
|
|
196526
197125
|
if (outcome !== "ok") {
|
|
196527
197126
|
deps.metrics?.recordFilesRegisterFailure?.(outcome);
|
|
196528
197127
|
warn(
|
|
196529
|
-
`[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${
|
|
197128
|
+
`[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${files.length}`
|
|
196530
197129
|
);
|
|
196531
197130
|
}
|
|
196532
197131
|
return outcome;
|
|
@@ -196549,6 +197148,31 @@ var init_assembly = __esm({
|
|
|
196549
197148
|
}
|
|
196550
197149
|
});
|
|
196551
197150
|
|
|
197151
|
+
// ../server/src/domains/delegations/child-live-turn.ts
|
|
197152
|
+
function registerDelegatedChildTurn(liveChat, childSessionId, session) {
|
|
197153
|
+
const appendInput = session.appendInput;
|
|
197154
|
+
if (typeof appendInput !== "function" || session.canAppendInput === false) return;
|
|
197155
|
+
const ctrl = liveChat.start(childSessionId, {
|
|
197156
|
+
runtimeSessionId: session.id,
|
|
197157
|
+
...session.runId ? { runId: session.runId } : {},
|
|
197158
|
+
kill: () => {
|
|
197159
|
+
void session.kill?.();
|
|
197160
|
+
},
|
|
197161
|
+
appendInput: (input) => appendInput.call(session, input),
|
|
197162
|
+
// **每次现读**,不快照:一轮跑到收尾时 stdin 会先关,那之后 runtime 自己会回
|
|
197163
|
+
// `session-closing`,判断权本就该留在它那儿(同 `/api/chat` 那条路的写法)。
|
|
197164
|
+
get canAppendInput() {
|
|
197165
|
+
return session.canAppendInput !== false;
|
|
197166
|
+
}
|
|
197167
|
+
});
|
|
197168
|
+
void session.done.then(() => ctrl.finish("done"), () => ctrl.finish("error"));
|
|
197169
|
+
}
|
|
197170
|
+
var init_child_live_turn = __esm({
|
|
197171
|
+
"../server/src/domains/delegations/child-live-turn.ts"() {
|
|
197172
|
+
"use strict";
|
|
197173
|
+
}
|
|
197174
|
+
});
|
|
197175
|
+
|
|
196552
197176
|
// ../connectors/src/_base/wrapper-assets.ts
|
|
196553
197177
|
function isUsableWrapper(scriptPath) {
|
|
196554
197178
|
if (!(0, import_node_fs7.existsSync)(scriptPath)) return false;
|
|
@@ -201298,7 +201922,7 @@ function authorizedCommandReceipt(command, args, actor) {
|
|
|
201298
201922
|
case "promote":
|
|
201299
201923
|
return { message: "promoted to persistent" };
|
|
201300
201924
|
case "resolveEscalation":
|
|
201301
|
-
return { message: `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
|
|
201925
|
+
return { message: optStr(args, "gapId") ? `\u5DF2\u786E\u8BA4\u6062\u590D\u65B9\u6848\uFF0C\u89E3\u51B3\u7F3A\u53E3 ${str(args, "gapId")}\u3001\u5173\u95ED\u5173\u8054\u4E0A\u62A5 ${str(args, "escalationId")}\uFF1B\u8C03\u5EA6\u5668\u5C06\u5728\u5176\u4ED6\u963B\u585E\u89E3\u9664\u540E\u7EE7\u7EED\u6267\u884C\u3002` : `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
|
|
201302
201926
|
default:
|
|
201303
201927
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u65E0\u56DE\u6267\u6784\u9020 ${command}`);
|
|
201304
201928
|
}
|
|
@@ -201337,7 +201961,11 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
201337
201961
|
);
|
|
201338
201962
|
}
|
|
201339
201963
|
}
|
|
201964
|
+
const linkedGapId = escalationById(kernel.model, escalationId)?.gapId;
|
|
201965
|
+
const linkedGap = linkedGapId ? (kernel.model.gaps.get(artifactId) ?? []).find((g2) => g2.gapId === linkedGapId) : void 0;
|
|
201966
|
+
if (linkedGap && !why?.trim()) throw new Error("\u8BF7\u8BF4\u660E\u91C7\u7528\u4EC0\u4E48\u6062\u590D\u65B9\u6848\uFF0C\u518D\u63D0\u4EA4\u7F3A\u53E3\u4E0E\u4E0A\u62A5\u7684\u8054\u5408\u786E\u8BA4");
|
|
201340
201967
|
const normalizedArgs = {
|
|
201968
|
+
...linkedGap ? { gapId: linkedGap.gapId, continueExecution: true } : {},
|
|
201341
201969
|
escalationId,
|
|
201342
201970
|
// ← 已四级解析出的具体一条(绝非 undefined、绝非「全清」)
|
|
201343
201971
|
artifactId,
|
|
@@ -201935,10 +202563,10 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
201935
202563
|
const note = optStr(args, "note");
|
|
201936
202564
|
const via = optStr(args, "via");
|
|
201937
202565
|
const target = kernel.model.annotations.get(annotationId);
|
|
201938
|
-
if (target && target.state !== "open") {
|
|
202566
|
+
if (target && target.state !== "open" && resolution !== "acknowledged") {
|
|
201939
202567
|
return { message: `annotation \u5DF2\u5173\u95ED\uFF08${target.state}\uFF09\u2014\u2014\u65E0\u9700\u518D\u5904\u7406${isReactivatedLetter(target, kernel.model.convergenceUnified) ? "\uFF1B\u8FD9\u5C01\u4FE1\u5728\u590D\u6D3B\u4E2D\uFF0C\u56DE\u5E16\u7B54\u590D\u7528 `oasis reply`\uFF0C\u6216\u6539\u5185\u5BB9 propose\uFF08\u7533\u62A5\u4F1A\u5E26\u4E0A\u5B83\uFF09" : ""}` };
|
|
201940
202568
|
}
|
|
201941
|
-
await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {} });
|
|
202569
|
+
await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {}, ...ctx.sessionArtifactId ? { viaNode: ctx.sessionArtifactId } : {} });
|
|
201942
202570
|
return { message: `annotation \u2192 ${resolution}${via ? `\uFF08via ${via}\uFF09` : ""}${isAgent(actor) && target?.author.startsWith("actor:human:") ? "\uFF1B\u4F5C\u8005\u4F1A\u6536\u5230\u901A\u77E5\uFF0C\u82E5\u4E0D\u8BA4\u53EF\u4F1A\u5728\u4FE1\u91CC\u56DE\u5E16\u5524\u4F60" : ""}` };
|
|
201943
202571
|
}
|
|
201944
202572
|
case "gap": {
|
|
@@ -202489,7 +203117,15 @@ async function startOasisServer(opts) {
|
|
|
202489
203117
|
resolveStore: opts.delegationStoreFor,
|
|
202490
203118
|
resolveChatSessions: resolveChatSessionsForDelegation,
|
|
202491
203119
|
resolveActors: async (companyId) => (await actorsDomain2.resolveCtx(companyId)).service,
|
|
202492
|
-
|
|
203120
|
+
// **派发完顺手把子会话这一轮登记进 live 注册表**(v2 bug 0088)。缺这一步时下面那行
|
|
203121
|
+
// `appendToChild` 恒回 `no-live-turn`——注册表里从来没有以 `childSessionId` 为键的轮,
|
|
203122
|
+
// 于是每一条 `--continue` 转达都落队列,专家要等本轮跑完才看见「手上这段作废」。
|
|
203123
|
+
// 理由与两条纪律见 `child-live-turn.ts` 的文件头。
|
|
203124
|
+
dispatchChat: async (request2) => {
|
|
203125
|
+
const session = await dispatchChat(request2);
|
|
203126
|
+
registerDelegatedChildTurn(liveChat, request2.chatSessionId, session);
|
|
203127
|
+
return session;
|
|
203128
|
+
},
|
|
202493
203129
|
appendToChild: (childSessionId, text5, extra) => liveChat.append(childSessionId, text5, extra),
|
|
202494
203130
|
// 用 lambda 转一层:`broadcastDepsFor` 在本文件里声明得更靠后(回流是异步发生的,
|
|
202495
203131
|
// 那时它早就赋过值了)。直接把值递进去会撞 TDZ。
|
|
@@ -202730,16 +203366,25 @@ async function startOasisServer(opts) {
|
|
|
202730
203366
|
...store?.enqueuePendingMessage ? {
|
|
202731
203367
|
enqueuePending: async (input) => {
|
|
202732
203368
|
const bundle = input.bundle;
|
|
203369
|
+
const fp = (p2) => {
|
|
203370
|
+
const f2 = bundle?.fingerprints?.[p2];
|
|
203371
|
+
return {
|
|
203372
|
+
...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
|
|
203373
|
+
...f2?.mtime ? { srcMtime: f2.mtime } : {}
|
|
203374
|
+
};
|
|
203375
|
+
};
|
|
202733
203376
|
const attachments = bundle ? [
|
|
202734
203377
|
...Object.entries(bundle.files ?? {}).map(([p2, text5]) => ({
|
|
202735
203378
|
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
202736
203379
|
text: text5,
|
|
202737
|
-
path: p2
|
|
203380
|
+
path: p2,
|
|
203381
|
+
...fp(p2)
|
|
202738
203382
|
})),
|
|
202739
203383
|
...Object.entries(bundle.binaryFiles ?? {}).map(([p2, b64]) => ({
|
|
202740
203384
|
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
202741
203385
|
bytesBase64: b64,
|
|
202742
|
-
path: p2
|
|
203386
|
+
path: p2,
|
|
203387
|
+
...fp(p2)
|
|
202743
203388
|
}))
|
|
202744
203389
|
] : [];
|
|
202745
203390
|
const { randomUUID: randomUUID40 } = await import("node:crypto");
|
|
@@ -202769,7 +203414,15 @@ async function startOasisServer(opts) {
|
|
|
202769
203414
|
// ——生产 `deliverToAgent:512` 会开轮/append,用户听到空消息且下一场 UPDATE 再把已完成
|
|
202770
203415
|
// 的行改回 delivering,违背「只补账、不投递」语义。
|
|
202771
203416
|
enqueueRegisterPending: async (input) => {
|
|
202772
|
-
const attachments = input.paths.map((p2) =>
|
|
203417
|
+
const attachments = input.paths.map((p2) => {
|
|
203418
|
+
const f2 = input.fingerprints?.[p2];
|
|
203419
|
+
return {
|
|
203420
|
+
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
203421
|
+
path: p2,
|
|
203422
|
+
...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
|
|
203423
|
+
...f2?.mtime ? { srcMtime: f2.mtime } : {}
|
|
203424
|
+
};
|
|
203425
|
+
});
|
|
202773
203426
|
const { randomUUID: randomUUID40 } = await import("node:crypto");
|
|
202774
203427
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
202775
203428
|
try {
|
|
@@ -203203,6 +203856,22 @@ async function startOasisServer(opts) {
|
|
|
203203
203856
|
}));
|
|
203204
203857
|
return;
|
|
203205
203858
|
}
|
|
203859
|
+
if (url.pathname.startsWith("/api/files/")) {
|
|
203860
|
+
const company = opts.resolveCompanyContext ? await opts.resolveCompanyContext(actor, req.headers, url.pathname) : void 0;
|
|
203861
|
+
if (company && company.kind !== "ok") {
|
|
203862
|
+
res.writeHead(403, { "content-type": "application/json" }).end(JSON.stringify({
|
|
203863
|
+
error: { code: "FORBIDDEN", message: "\u65E0\u6743\u8BFB\u53D6\u8BE5\u516C\u53F8\u7684\u6587\u4EF6" }
|
|
203864
|
+
}));
|
|
203865
|
+
return;
|
|
203866
|
+
}
|
|
203867
|
+
await serveFileRequest(
|
|
203868
|
+
req,
|
|
203869
|
+
res,
|
|
203870
|
+
url,
|
|
203871
|
+
await resolveEngine(company?.kind === "ok" ? company.companyId : void 0)
|
|
203872
|
+
);
|
|
203873
|
+
return;
|
|
203874
|
+
}
|
|
203206
203875
|
if (url.pathname === "/api/whoami" && req.method === "GET") {
|
|
203207
203876
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ actor }));
|
|
203208
203877
|
return;
|
|
@@ -204909,6 +205578,22 @@ ${composed}`;
|
|
|
204909
205578
|
const itemLedger = new ChatItemLedger({
|
|
204910
205579
|
...itemStore ? { items: itemStore } : {},
|
|
204911
205580
|
versionSeed,
|
|
205581
|
+
/* 用户那条消息一落库就推上 v3 流(ADR-0510 D2 的收口)。
|
|
205582
|
+
不推的话:乐观条没有服务端号、被钉在列表末尾,而 agent 的正文带着更大的号从
|
|
205583
|
+
直播流到达、进「有号」段 ⇒ **回复显示在用户消息上面**,直到下一次快照才跳回去
|
|
205584
|
+
(2026-09-09 用户现场)。这里把窗口从「等下一次快照」压到「一次 INSERT 往返」。
|
|
205585
|
+
只推 user:assistant 的每一条本来就走 `emitV3`。 */
|
|
205586
|
+
onPersisted: (info) => {
|
|
205587
|
+
if (info.role !== "user") return;
|
|
205588
|
+
opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
|
|
205589
|
+
itemId: info.itemId,
|
|
205590
|
+
ord: info.ord,
|
|
205591
|
+
version: info.version,
|
|
205592
|
+
text: info.text,
|
|
205593
|
+
...clientSubmitId ? { clientSubmitId } : {},
|
|
205594
|
+
...effectiveAttachments.length ? { attachments: effectiveAttachments } : {}
|
|
205595
|
+
});
|
|
205596
|
+
},
|
|
204912
205597
|
sessionId: persistTarget?.id ?? session.id,
|
|
204913
205598
|
// 优先账本 id(`chat_session_turns.id`,跨进程唯一、重启不失忆);账本没接入才兜底。
|
|
204914
205599
|
turnId: heldTurn?.id ?? fallbackTurnId(session.runId),
|
|
@@ -206219,6 +206904,7 @@ var init_server3 = __esm({
|
|
|
206219
206904
|
init_src();
|
|
206220
206905
|
init_src();
|
|
206221
206906
|
init_src();
|
|
206907
|
+
init_file_http();
|
|
206222
206908
|
init_build_info();
|
|
206223
206909
|
init_artifact_content2();
|
|
206224
206910
|
init_command_policy();
|
|
@@ -206260,6 +206946,7 @@ var init_server3 = __esm({
|
|
|
206260
206946
|
init_workorder_terminal();
|
|
206261
206947
|
init_knowledge2();
|
|
206262
206948
|
init_assembly();
|
|
206949
|
+
init_child_live_turn();
|
|
206263
206950
|
init_src8();
|
|
206264
206951
|
init_continuation();
|
|
206265
206952
|
githubAppPending = new PendingAppCreations();
|
|
@@ -209346,6 +210033,27 @@ function foldSingleNodeTasks(items) {
|
|
|
209346
210033
|
}
|
|
209347
210034
|
return folded;
|
|
209348
210035
|
}
|
|
210036
|
+
function fillFolderSizes(items) {
|
|
210037
|
+
const folders = items.filter((i) => i.kind === "folder" && i.path);
|
|
210038
|
+
if (folders.length === 0) return;
|
|
210039
|
+
const files = items.filter((i) => i.kind !== "folder" && i.path);
|
|
210040
|
+
for (const folder of folders) {
|
|
210041
|
+
const prefix = `${folder.path}/`;
|
|
210042
|
+
let sum = 0;
|
|
210043
|
+
let seen = 0;
|
|
210044
|
+
let complete = true;
|
|
210045
|
+
for (const file of files) {
|
|
210046
|
+
if (!file.path.startsWith(prefix)) continue;
|
|
210047
|
+
seen += 1;
|
|
210048
|
+
if (file.size == null) {
|
|
210049
|
+
complete = false;
|
|
210050
|
+
break;
|
|
210051
|
+
}
|
|
210052
|
+
sum += file.size;
|
|
210053
|
+
}
|
|
210054
|
+
folder.size = seen > 0 && complete ? sum : null;
|
|
210055
|
+
}
|
|
210056
|
+
}
|
|
209349
210057
|
function engineContentKind(k2) {
|
|
209350
210058
|
return k2 === "manifest" ? "manifest" : k2 === "external" ? "external-pin" : "inline-blob";
|
|
209351
210059
|
}
|
|
@@ -210243,6 +210951,7 @@ var init_service4 = __esm({
|
|
|
210243
210951
|
const projectByWorkOrder = new Map(bindings.map((b2) => [b2.workOrderId, b2.projectId]));
|
|
210244
210952
|
const workorderSummaries = await this.listProjectWorkorders(projectId2);
|
|
210245
210953
|
const workorderTitleById = new Map(workorderSummaries.map((w2) => [w2.id, w2.title]));
|
|
210954
|
+
const workorderOwnerById = new Map(workorderSummaries.map((w2) => [w2.id, w2.owner?.id ?? null]));
|
|
210246
210955
|
const engineRows = await this.collectEngineFilesForProject(
|
|
210247
210956
|
projectId2,
|
|
210248
210957
|
workorderSummaries.map((w2) => w2.id),
|
|
@@ -210261,6 +210970,8 @@ var init_service4 = __esm({
|
|
|
210261
210970
|
const taskId = record8.createdFromWorkOrderId ?? artifact?.workspace ?? null;
|
|
210262
210971
|
const taskTitle = taskId ? workorderTitleById.get(taskId) ?? null : null;
|
|
210263
210972
|
const creatorId = revision.author || null;
|
|
210973
|
+
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
210974
|
+
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210264
210975
|
const folderPath = taskId ? `wo:${taskId}/node:${record8.artifactId}` : null;
|
|
210265
210976
|
if (folderPath) items.push({
|
|
210266
210977
|
kind: "folder",
|
|
@@ -210273,13 +210984,14 @@ var init_service4 = __esm({
|
|
|
210273
210984
|
size: null,
|
|
210274
210985
|
taskId,
|
|
210275
210986
|
taskTitle,
|
|
210276
|
-
creatorKind
|
|
210277
|
-
creatorId
|
|
210278
|
-
creatorName
|
|
210987
|
+
creatorKind,
|
|
210988
|
+
creatorId,
|
|
210989
|
+
creatorName,
|
|
210279
210990
|
updatedAt: record8.updatedAt
|
|
210280
210991
|
});
|
|
210281
210992
|
const content3 = revision.contentKind === "manifest" ? await this.readContentRefText(revision.contentRef) : null;
|
|
210282
210993
|
const files2 = content3 !== null ? tryParseManifestFiles(content3) : void 0;
|
|
210994
|
+
const wholeSize = files2?.length ? null : content3 !== null ? Buffer.byteLength(content3, "utf8") : await this.contentByteSize(revision.contentRef, revision.contentKind);
|
|
210283
210995
|
for (const file of files2?.length ? files2 : [null]) {
|
|
210284
210996
|
items.push({
|
|
210285
210997
|
kind: "artifact",
|
|
@@ -210289,15 +211001,15 @@ var init_service4 = __esm({
|
|
|
210289
211001
|
filePath: file?.path ?? null,
|
|
210290
211002
|
depth: folderPath ? 2 : 0,
|
|
210291
211003
|
type: record8.type,
|
|
210292
|
-
size: file
|
|
211004
|
+
size: file ? file.size ?? null : wholeSize,
|
|
210293
211005
|
taskId,
|
|
210294
211006
|
taskTitle,
|
|
210295
211007
|
// 内容形态/引用只挂在**整份产物**那一行;装箱单摊出来的成员行有自己的后缀,
|
|
210296
211008
|
// 按后缀走图标即可,挂上 manifest 反而会把 `Dockerfile` 这种无后缀成员画成压缩包。
|
|
210297
211009
|
...file ? {} : { contentKind: revision.contentKind, contentRef: revision.contentRef },
|
|
210298
|
-
creatorKind
|
|
211010
|
+
creatorKind,
|
|
210299
211011
|
creatorId,
|
|
210300
|
-
creatorName
|
|
211012
|
+
creatorName,
|
|
210301
211013
|
updatedAt: record8.updatedAt
|
|
210302
211014
|
});
|
|
210303
211015
|
}
|
|
@@ -210309,6 +211021,7 @@ var init_service4 = __esm({
|
|
|
210309
211021
|
if (foldedTasks.has(item.taskId)) continue;
|
|
210310
211022
|
taskFolders.add(item.taskId);
|
|
210311
211023
|
const path41 = `wo:${item.taskId}`;
|
|
211024
|
+
const ownerId = workorderOwnerById.get(item.taskId) ?? null;
|
|
210312
211025
|
items.push({
|
|
210313
211026
|
kind: "folder",
|
|
210314
211027
|
id: `folder:${path41}`,
|
|
@@ -210320,9 +211033,9 @@ var init_service4 = __esm({
|
|
|
210320
211033
|
size: null,
|
|
210321
211034
|
taskId: item.taskId,
|
|
210322
211035
|
taskTitle: item.taskTitle,
|
|
210323
|
-
creatorKind: null,
|
|
210324
|
-
creatorId:
|
|
210325
|
-
creatorName: null,
|
|
211036
|
+
creatorKind: ownerId ? ownerId.startsWith("actor:human:") ? "human" : "agent" : null,
|
|
211037
|
+
creatorId: ownerId,
|
|
211038
|
+
creatorName: ownerId ? this.resolveActorName(ownerId) : null,
|
|
210326
211039
|
updatedAt: item.updatedAt
|
|
210327
211040
|
});
|
|
210328
211041
|
}
|
|
@@ -210348,6 +211061,7 @@ var init_service4 = __esm({
|
|
|
210348
211061
|
updatedAt: file.uploadedAt
|
|
210349
211062
|
});
|
|
210350
211063
|
}
|
|
211064
|
+
fillFolderSizes(items);
|
|
210351
211065
|
items.sort((a, b2) => {
|
|
210352
211066
|
const pathA = a.path ?? "";
|
|
210353
211067
|
const pathB = b2.path ?? "";
|
|
@@ -210391,6 +211105,9 @@ var init_service4 = __esm({
|
|
|
210391
211105
|
const work = worksById.get(acceptedId);
|
|
210392
211106
|
const folderPath = `wo:${workOrderId}/node:${node2.id}`;
|
|
210393
211107
|
const folderUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
|
|
211108
|
+
const creatorId = work?.assigneeActorId ?? null;
|
|
211109
|
+
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
211110
|
+
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210394
211111
|
out.push({
|
|
210395
211112
|
kind: "folder",
|
|
210396
211113
|
id: `folder:${folderPath}`,
|
|
@@ -210402,14 +211119,11 @@ var init_service4 = __esm({
|
|
|
210402
211119
|
size: null,
|
|
210403
211120
|
taskId: workOrderId,
|
|
210404
211121
|
taskTitle,
|
|
210405
|
-
creatorKind
|
|
210406
|
-
creatorId
|
|
210407
|
-
creatorName
|
|
211122
|
+
creatorKind,
|
|
211123
|
+
creatorId,
|
|
211124
|
+
creatorName,
|
|
210408
211125
|
updatedAt: folderUpdatedAt
|
|
210409
211126
|
});
|
|
210410
|
-
const creatorId = work?.assigneeActorId ?? null;
|
|
210411
|
-
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
210412
|
-
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210413
211127
|
const sortedArts = arts.slice().sort((a, b2) => a.ordinal - b2.ordinal);
|
|
210414
211128
|
for (const a of sortedArts) {
|
|
210415
211129
|
const artifactId = node2.id;
|
|
@@ -210448,7 +211162,7 @@ var init_service4 = __esm({
|
|
|
210448
211162
|
filePath: null,
|
|
210449
211163
|
depth: 2,
|
|
210450
211164
|
type: artifactType || "",
|
|
210451
|
-
size:
|
|
211165
|
+
size: await this.contentByteSize(a.contentRef, engineContentKind(a.contentKind)),
|
|
210452
211166
|
contentKind: engineContentKind(a.contentKind),
|
|
210453
211167
|
...a.contentRef ? { contentRef: a.contentRef } : {},
|
|
210454
211168
|
taskId: workOrderId,
|
|
@@ -210867,6 +211581,19 @@ var init_service4 = __esm({
|
|
|
210867
211581
|
}
|
|
210868
211582
|
return { ...withKind, size: Buffer.byteLength(content3, "utf8") };
|
|
210869
211583
|
}
|
|
211584
|
+
/**
|
|
211585
|
+
* 一份产物正文的字节数——**与任务页「相关产物」共用同一条判据**(见 {@link enrichNodeOutputDocument}):
|
|
211586
|
+
* 不看 `contentKind` 名义,只看正文能不能从 BlobStore 读到;读不到就是 null。
|
|
211587
|
+
*
|
|
211588
|
+
* 于是 git 提交号 / Figma version 这类真·外部钉、未纳管、`empty` 都诚实无大小(前端显「—」),
|
|
211589
|
+
* 而 `inline-blob` 的任务书、PRD、ADR 会报出真实字节数——此前 files-view 这条路径**恒发 null**,
|
|
211590
|
+
* 同一份任务书在任务页有 8.0 KB、在项目页却是「—」。
|
|
211591
|
+
*/
|
|
211592
|
+
async contentByteSize(contentRef, contentKind) {
|
|
211593
|
+
if (contentKind === "empty" || !contentRef?.trim()) return null;
|
|
211594
|
+
const content3 = await this.readContentRefText(contentRef);
|
|
211595
|
+
return content3 === null ? null : Buffer.byteLength(content3, "utf8");
|
|
211596
|
+
}
|
|
210870
211597
|
/** 正文读取的唯一入口:只认 BlobStore;没配 / 没纳管 / 读失败一律 null(调用方据此诚实缺 size)。 */
|
|
210871
211598
|
async readContentRefText(contentRef) {
|
|
210872
211599
|
if (!this.blobs) return null;
|
|
@@ -224806,6 +225533,146 @@ var init_node_store = __esm({
|
|
|
224806
225533
|
}
|
|
224807
225534
|
});
|
|
224808
225535
|
|
|
225536
|
+
// ../server/src/domains/collab/review-activity.ts
|
|
225537
|
+
function reviewCardId(targetWorkId, reviewerActorId) {
|
|
225538
|
+
return `${REVIEW_CARD_PREFIX}${encodeURIComponent(targetWorkId)}:${encodeURIComponent(reviewerActorId)}`;
|
|
225539
|
+
}
|
|
225540
|
+
function activityReviews(snap, events) {
|
|
225541
|
+
const rows = /* @__PURE__ */ new Map();
|
|
225542
|
+
for (const r of snap.reviews) rows.set(r.id, { ...r });
|
|
225543
|
+
const authoritative = new Set(snap.reviews.filter((r) => r.status).map((r) => r.id));
|
|
225544
|
+
for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
|
|
225545
|
+
const e = rec.event;
|
|
225546
|
+
if (e.kind === "review.create" && !rows.has(e.reviewId)) {
|
|
225547
|
+
rows.set(e.reviewId, {
|
|
225548
|
+
id: e.reviewId,
|
|
225549
|
+
workorderId: snap.workorder.id,
|
|
225550
|
+
nodeId: e.nodeId,
|
|
225551
|
+
targetWorkId: e.targetWorkId,
|
|
225552
|
+
reviewerActorId: e.reviewerActorId,
|
|
225553
|
+
reviewGroup: e.reviewGroup ?? e.reviewerActorId,
|
|
225554
|
+
createdAt: rec.createdAt,
|
|
225555
|
+
startedAt: null,
|
|
225556
|
+
endedAt: null,
|
|
225557
|
+
cancelledAt: null,
|
|
225558
|
+
verdict: null,
|
|
225559
|
+
note: null,
|
|
225560
|
+
handActorId: null,
|
|
225561
|
+
decidedAt: null,
|
|
225562
|
+
sessionRef: null
|
|
225563
|
+
});
|
|
225564
|
+
}
|
|
225565
|
+
if (e.kind === "review.create" && !authoritative.has(e.reviewId)) {
|
|
225566
|
+
const r = rows.get(e.reviewId);
|
|
225567
|
+
Object.assign(r, {
|
|
225568
|
+
nodeId: e.nodeId,
|
|
225569
|
+
targetWorkId: e.targetWorkId,
|
|
225570
|
+
reviewerActorId: e.reviewerActorId,
|
|
225571
|
+
reviewGroup: e.reviewGroup ?? r.reviewGroup ?? e.reviewerActorId,
|
|
225572
|
+
createdAt: r.createdAt ?? rec.createdAt
|
|
225573
|
+
});
|
|
225574
|
+
}
|
|
225575
|
+
if (!e.kind.startsWith("review.")) continue;
|
|
225576
|
+
const candidates = "reviewId" in e && e.reviewId ? [rows.get(e.reviewId)] : e.kind === "review.response" ? [...rows.values()].filter((r) => r.targetWorkId === e.targetWorkId && r.reviewerActorId === e.reviewerActorId && (!e.reviewGroup || r.reviewGroup === e.reviewGroup)) : [];
|
|
225577
|
+
const matches = e.kind === "review.response" && !e.reviewId ? latestActivityReviews(candidates.filter((r) => !!r && r.createdAt <= rec.createdAt), events.filter((r) => r.seq <= rec.seq)) : candidates;
|
|
225578
|
+
for (const r of matches) {
|
|
225579
|
+
if (!r || authoritative.has(r.id)) continue;
|
|
225580
|
+
if (e.kind === "review.started") r.startedAt = rec.createdAt;
|
|
225581
|
+
if (e.kind === "review.timeout") {
|
|
225582
|
+
r.endedAt = rec.createdAt;
|
|
225583
|
+
}
|
|
225584
|
+
if (e.kind === "review.kill") r.cancelledAt = rec.createdAt;
|
|
225585
|
+
if (e.kind === "review.response") {
|
|
225586
|
+
r.verdict = e.verdict;
|
|
225587
|
+
r.note = e.note ?? null;
|
|
225588
|
+
r.decidedAt = rec.createdAt;
|
|
225589
|
+
r.endedAt = rec.createdAt;
|
|
225590
|
+
r.handActorId = e.handActorId ?? null;
|
|
225591
|
+
}
|
|
225592
|
+
}
|
|
225593
|
+
}
|
|
225594
|
+
const createdSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
|
|
225595
|
+
return [...rows.values()].filter((r) => r.nodeId && r.targetWorkId && r.reviewerActorId && r.createdAt).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (createdSeq.get(a.id) ?? 0) - (createdSeq.get(b2.id) ?? 0) || a.id.localeCompare(b2.id));
|
|
225596
|
+
}
|
|
225597
|
+
function reviewCardTarget(cardId, reviews) {
|
|
225598
|
+
if (cardId.startsWith(REVIEW_CARD_PREFIX)) {
|
|
225599
|
+
const encoded = cardId.slice(REVIEW_CARD_PREFIX.length);
|
|
225600
|
+
const separator = encoded.indexOf(":");
|
|
225601
|
+
if (separator < 1 || separator === encoded.length - 1) return void 0;
|
|
225602
|
+
try {
|
|
225603
|
+
return {
|
|
225604
|
+
targetWorkId: decodeURIComponent(encoded.slice(0, separator)),
|
|
225605
|
+
reviewerActorId: decodeURIComponent(encoded.slice(separator + 1))
|
|
225606
|
+
};
|
|
225607
|
+
} catch {
|
|
225608
|
+
return void 0;
|
|
225609
|
+
}
|
|
225610
|
+
}
|
|
225611
|
+
if (cardId.startsWith("review:")) {
|
|
225612
|
+
const review = reviews.find((r) => r.id === cardId.slice(7));
|
|
225613
|
+
if (review) return { targetWorkId: review.targetWorkId, reviewerActorId: review.reviewerActorId };
|
|
225614
|
+
}
|
|
225615
|
+
return void 0;
|
|
225616
|
+
}
|
|
225617
|
+
function latestActivityReviews(reviews, events) {
|
|
225618
|
+
const createSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
|
|
225619
|
+
const rows = [...reviews].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (createSeq.get(a.id) ?? 0) - (createSeq.get(b2.id) ?? 0) || a.id.localeCompare(b2.id));
|
|
225620
|
+
const latest = /* @__PURE__ */ new Map();
|
|
225621
|
+
for (const r of rows) latest.set(JSON.stringify([r.reviewGroup, r.reviewerActorId]), r);
|
|
225622
|
+
return [...latest.values()];
|
|
225623
|
+
}
|
|
225624
|
+
function reviewLabel(r) {
|
|
225625
|
+
const state = reviewState(r);
|
|
225626
|
+
if (state === "accept") return "\u5BA1\u6838\u901A\u8FC7";
|
|
225627
|
+
if (state === "reject") return "\u5BA1\u6838\u4E0D\u901A\u8FC7";
|
|
225628
|
+
if (state === "dead") return "\u5BA1\u6838\u5DF2\u505C\u6B62";
|
|
225629
|
+
if (state === "failed") return "\u5BA1\u6838\u8FD0\u884C\u5931\u8D25";
|
|
225630
|
+
if (state === "retry") return "\u6B63\u5728\u91CD\u8BD5\u5BA1\u6838";
|
|
225631
|
+
return r.reviewerActorId.startsWith("actor:human:") ? "\u5F85\u5BA1\u6838" : "\u6B63\u5728\u5BA1\u6838";
|
|
225632
|
+
}
|
|
225633
|
+
function reviewSummary(snap, reviews, events) {
|
|
225634
|
+
const first = reviews[0];
|
|
225635
|
+
const work = snap.works.find((w2) => w2.id === first.targetWorkId);
|
|
225636
|
+
const node2 = snap.nodes.find((n) => n.id === first.nodeId);
|
|
225637
|
+
const name = node2?.title ?? first.nodeId;
|
|
225638
|
+
const latest = latestActivityReviews(reviews, events);
|
|
225639
|
+
const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
|
|
225640
|
+
const judgement = judgeWork({ requirements: requirements.length ? requirements : reviews, reviews });
|
|
225641
|
+
const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
|
|
225642
|
+
if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
|
|
225643
|
+
return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
|
|
225644
|
+
}
|
|
225645
|
+
if (work?.acceptanceState === "rejected" || judgement === "rejected") {
|
|
225646
|
+
return { phase: "done", status: "\u5BA1\u6838\u4E0D\u901A\u8FC7\u3002", latest, pending: [] };
|
|
225647
|
+
}
|
|
225648
|
+
const endedWork = !!(work?.cancelledAt || work?.deadAt);
|
|
225649
|
+
if (endedWork && latest.every((r) => !!r.cancelledAt && reviewState(r) !== "retry")) {
|
|
225650
|
+
return { phase: "done", status: `\u300A${name}\u300B\u7684\u672C\u6B21\u5BA1\u6838\u5DF2\u53D6\u6D88\u3002`, latest, pending: [] };
|
|
225651
|
+
}
|
|
225652
|
+
const interrupted = latest.some((r) => ["failed", "dead"].includes(reviewState(r)));
|
|
225653
|
+
if (interrupted) return { phase: "stuck", status: latest.length === 1 ? reviewState(latest[0]) === "dead" ? events.some((r) => r.event.kind === "review.kill" && r.event.reviewId === latest[0].id) ? `\u300A${name}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7EED\u5931\u8D25\uFF0C\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u672A\u5728\u65F6\u9650\u5185\u5B8C\u6210\u3002` : `\u300A${name}\u300B\u7684\u90E8\u5206\u5BA1\u6838\u6267\u884C\u4E2D\u65AD\uFF0C\u5C1A\u672A\u5B8C\u6210\u5168\u90E8\u5BA1\u6838\u3002`, latest, pending };
|
|
225654
|
+
if (latest.some((r) => r.reviewerActorId.startsWith("actor:agent:") && ["running", "retry"].includes(reviewState(r)))) {
|
|
225655
|
+
return { phase: "running", status: `\u6B63\u5728\u5BA1\u6838 \u300A${name}\u300B\u3002`, latest, pending };
|
|
225656
|
+
}
|
|
225657
|
+
return { phase: "not_started", status: `\u8BF7\u786E\u8BA4\u300A${name}\u300B\u662F\u5426\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending };
|
|
225658
|
+
}
|
|
225659
|
+
var REVIEW_CARD_PREFIX, REVIEW_TRACE_EVENT_KINDS;
|
|
225660
|
+
var init_review_activity = __esm({
|
|
225661
|
+
"../server/src/domains/collab/review-activity.ts"() {
|
|
225662
|
+
"use strict";
|
|
225663
|
+
init_src4();
|
|
225664
|
+
REVIEW_CARD_PREFIX = "review-work:";
|
|
225665
|
+
REVIEW_TRACE_EVENT_KINDS = [
|
|
225666
|
+
"review.create",
|
|
225667
|
+
"review.started",
|
|
225668
|
+
"review.response",
|
|
225669
|
+
"review.timeout",
|
|
225670
|
+
"review.kill",
|
|
225671
|
+
"plan.node_retry"
|
|
225672
|
+
];
|
|
225673
|
+
}
|
|
225674
|
+
});
|
|
225675
|
+
|
|
224809
225676
|
// ../server/src/domains/collab/workorder-manager.ts
|
|
224810
225677
|
function resolveWorkorderManager(snap) {
|
|
224811
225678
|
const nodes = snap.nodes;
|
|
@@ -224844,8 +225711,29 @@ function buildWorkorderActivity(input) {
|
|
|
224844
225711
|
if (!snap) return { workorderId, cards: [], truncated: false };
|
|
224845
225712
|
const nodeById = new Map(snap.nodes.map((n) => [n.id, n]));
|
|
224846
225713
|
const workById = new Map(snap.works.map((w2) => [w2.id, w2]));
|
|
224847
|
-
const reviewById = new Map(snap.reviews.map((r) => [r.id, r]));
|
|
224848
225714
|
const issueById = new Map(snap.issues.map((i) => [i.id, i]));
|
|
225715
|
+
const activeWorks = /* @__PURE__ */ new Set();
|
|
225716
|
+
const gapOrigins = /* @__PURE__ */ new Map();
|
|
225717
|
+
const knownStarts = new Set(events.flatMap((r) => r.event.kind === "work.create" || r.event.kind === "work.started" ? [r.event.workId] : []));
|
|
225718
|
+
for (const rec of events) {
|
|
225719
|
+
const event = rec.event;
|
|
225720
|
+
if (event.kind === "work.create" || event.kind === "work.started") activeWorks.add(event.workId);
|
|
225721
|
+
if (event.kind === "work.response" || event.kind === "work.timeout" || event.kind === "work.kill") activeWorks.delete(event.workId);
|
|
225722
|
+
if (event.kind !== "issue.create" || event.issueKind !== "gap") continue;
|
|
225723
|
+
const nodeId = event.raisedByNodeId ?? event.aboutNodeId;
|
|
225724
|
+
const matches = (id) => {
|
|
225725
|
+
const work = workById.get(id);
|
|
225726
|
+
return work?.nodeId === nodeId && work.assigneeActorId === event.authorActorId;
|
|
225727
|
+
};
|
|
225728
|
+
if (event.aboutWorkId && matches(event.aboutWorkId)) {
|
|
225729
|
+
gapOrigins.set(event.issueId, [event.aboutWorkId]);
|
|
225730
|
+
} else if (!event.aboutWorkId) {
|
|
225731
|
+
const candidates = [...activeWorks].filter(matches);
|
|
225732
|
+
const at = Date.parse(rec.createdAt);
|
|
225733
|
+
const unobserved = snap.works.some((work) => !knownStarts.has(work.id) && matches(work.id) && Date.parse(work.createdAt) <= at && (!work.endedAt || Date.parse(work.endedAt) >= at) && (!work.deadAt || Date.parse(work.deadAt) >= at) && (!work.cancelledAt || Date.parse(work.cancelledAt) >= at));
|
|
225734
|
+
if (candidates.length === 1 && !unobserved) gapOrigins.set(event.issueId, candidates);
|
|
225735
|
+
}
|
|
225736
|
+
}
|
|
224849
225737
|
const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
|
|
224850
225738
|
const rootBriefNodeIds = new Set(
|
|
224851
225739
|
snap.nodes.filter((n) => n.type === "brief" && !nodesWithInEdges.has(n.id)).map((n) => n.id)
|
|
@@ -224896,7 +225784,6 @@ function buildWorkorderActivity(input) {
|
|
|
224896
225784
|
const groupIndexOf = /* @__PURE__ */ new Map();
|
|
224897
225785
|
const cardOfWork = /* @__PURE__ */ new Map();
|
|
224898
225786
|
const groupKey = (nodeId) => `${nodeId}#${groupIndexOf.get(nodeId) ?? 0}`;
|
|
224899
|
-
const reviewCards = /* @__PURE__ */ new Map();
|
|
224900
225787
|
const issueCards = /* @__PURE__ */ new Map();
|
|
224901
225788
|
const workOfIssue = /* @__PURE__ */ new Map();
|
|
224902
225789
|
const plainCommentIssueIds = /* @__PURE__ */ new Set();
|
|
@@ -225118,72 +226005,6 @@ function buildWorkorderActivity(input) {
|
|
|
225118
226005
|
}
|
|
225119
226006
|
break;
|
|
225120
226007
|
}
|
|
225121
|
-
/* ── 审核(review)──────────────────────────────────────────── */
|
|
225122
|
-
case "review.create": {
|
|
225123
|
-
const reviewId = String(ev.reviewId ?? "");
|
|
225124
|
-
const nodeId = String(ev.nodeId ?? "");
|
|
225125
|
-
const targetWorkId = String(ev.targetWorkId ?? "");
|
|
225126
|
-
const reviewerId = str4(ev.reviewerActorId) ?? rec.actorId;
|
|
225127
|
-
const isAgent2 = ev.reviewerIsAgent === true;
|
|
225128
|
-
const name = nodeName(nodeId);
|
|
225129
|
-
reviewCards.set(reviewId, {
|
|
225130
|
-
id: `review:${reviewId}`,
|
|
225131
|
-
seq: rec.seq,
|
|
225132
|
-
at: rec.createdAt,
|
|
225133
|
-
updatedAt: rec.createdAt,
|
|
225134
|
-
nodeId,
|
|
225135
|
-
executorId: reviewerId,
|
|
225136
|
-
...rec.handActorId ? { handActorId: rec.handActorId } : {},
|
|
225137
|
-
// agent 评审是「机器在跑」= 进行中;人审是待办 = 待开始(人没有「开始看了」这个事件)。
|
|
225138
|
-
phase: isAgent2 ? "running" : "not_started",
|
|
225139
|
-
status: isAgent2 ? ACTIVITY_COPY.reviewing(name) : ACTIVITY_COPY.reviewPending(name),
|
|
225140
|
-
/* 挂上被审那一次交付的**全部产物**(发起人 2026-09-08 定):要人点「通过/不通过」,
|
|
225141
|
-
就得让他在同一张卡上看到到底在审什么。此前这里恒空,产物只挂在验收卡上——
|
|
225142
|
-
而验收卡与审核卡是同一件事的两张(见下方 `acceptCards` 那段的跳过判据)。 */
|
|
225143
|
-
artifacts: artifactsByWork.get(targetWorkId) ?? [],
|
|
225144
|
-
// 人审才出底栏按钮;agent 审无按钮(原型同)。
|
|
225145
|
-
actions: isAgent2 ? [] : [
|
|
225146
|
-
{ kind: "review", label: BUTTON.approve, target: nodeId, verdict: "approve" },
|
|
225147
|
-
{ kind: "review", label: BUTTON.reject, target: nodeId, verdict: "request_changes" }
|
|
225148
|
-
]
|
|
225149
|
-
});
|
|
225150
|
-
break;
|
|
225151
|
-
}
|
|
225152
|
-
case "review.started": {
|
|
225153
|
-
const d = reviewCards.get(String(ev.reviewId ?? ""));
|
|
225154
|
-
if (d) touch(d, rec);
|
|
225155
|
-
break;
|
|
225156
|
-
}
|
|
225157
|
-
case "review.response": {
|
|
225158
|
-
const reviewId = str4(ev.reviewId);
|
|
225159
|
-
const targetWorkId = String(ev.targetWorkId ?? "");
|
|
225160
|
-
const d = reviewId ? reviewCards.get(reviewId) : [...reviewCards.values()].find((c) => reviewById.get(c.id.slice("review:".length))?.targetWorkId === targetWorkId);
|
|
225161
|
-
if (!d) break;
|
|
225162
|
-
touch(d, rec);
|
|
225163
|
-
d.phase = "done";
|
|
225164
|
-
d.executorId = str4(ev.reviewerActorId) ?? d.executorId;
|
|
225165
|
-
d.actions = [];
|
|
225166
|
-
const nodeId = d.nodeId;
|
|
225167
|
-
const name = nodeName(nodeId);
|
|
225168
|
-
if (ev.verdict === "approve") {
|
|
225169
|
-
d.status = ACTIVITY_COPY.reviewApproved(name);
|
|
225170
|
-
} else {
|
|
225171
|
-
d.status = ACTIVITY_COPY.reviewRejected;
|
|
225172
|
-
const note = str4(ev.note);
|
|
225173
|
-
if (note) d.detail = ACTIVITY_COPY.reviewRejectedDetail(note);
|
|
225174
|
-
}
|
|
225175
|
-
break;
|
|
225176
|
-
}
|
|
225177
|
-
case "review.timeout":
|
|
225178
|
-
case "review.kill": {
|
|
225179
|
-
const d = reviewCards.get(String(ev.reviewId ?? ""));
|
|
225180
|
-
if (!d) break;
|
|
225181
|
-
touch(d, rec);
|
|
225182
|
-
d.phase = "stuck";
|
|
225183
|
-
d.status = ev.kind === "review.kill" ? ACTIVITY_COPY.reviewKilled(nodeName(d.nodeId)) : ACTIVITY_COPY.reviewTimedOut(nodeName(d.nodeId));
|
|
225184
|
-
d.actions = [];
|
|
225185
|
-
break;
|
|
225186
|
-
}
|
|
225187
226008
|
/* ── 沟通与异常(issue)────────────────────────────────────── */
|
|
225188
226009
|
case "issue.create": {
|
|
225189
226010
|
const issueId = String(ev.issueId ?? "");
|
|
@@ -225327,24 +226148,14 @@ function buildWorkorderActivity(input) {
|
|
|
225327
226148
|
if (issue2?.kind === "escalation") {
|
|
225328
226149
|
const linkedGap = str4(issue2.gapId);
|
|
225329
226150
|
const stuckActor = (linkedGap ? issueById.get(linkedGap)?.authorActorId : void 0) ?? issue2.authorActorId;
|
|
226151
|
+
touch(d, rec);
|
|
226152
|
+
d.phase = "done";
|
|
226153
|
+
d.executorId = resolverId;
|
|
226154
|
+
d.handActorId = rec.handActorId ?? void 0;
|
|
226155
|
+
d.status = ACTIVITY_COPY.handledEscalation(nameOf(stuckActor));
|
|
225330
226156
|
d.actions = [];
|
|
225331
226157
|
const raisedCard = issueCards.get(`${issueId}:raised`);
|
|
225332
226158
|
if (raisedCard) raisedCard.actions = [];
|
|
225333
|
-
standalone.push({
|
|
225334
|
-
id: `issue:${issueId}:resolved`,
|
|
225335
|
-
seq: rec.seq,
|
|
225336
|
-
at: rec.createdAt,
|
|
225337
|
-
updatedAt: rec.createdAt,
|
|
225338
|
-
...d.nodeId ? { nodeId: d.nodeId } : {},
|
|
225339
|
-
executorId: resolverId,
|
|
225340
|
-
...rec.handActorId ? { handActorId: rec.handActorId } : {},
|
|
225341
|
-
phase: "done",
|
|
225342
|
-
/* 收口正文不上卡(与开口那一支同一条口径):`ws:wo-635d7add` 的收口 note 是一整段
|
|
225343
|
-
处置说明,铺回展开区又是一屏字。 */
|
|
225344
|
-
status: ACTIVITY_COPY.handledEscalation(nameOf(stuckActor)),
|
|
225345
|
-
artifacts: [],
|
|
225346
|
-
actions: []
|
|
225347
|
-
});
|
|
225348
226159
|
break;
|
|
225349
226160
|
}
|
|
225350
226161
|
touch(d, rec);
|
|
@@ -225371,9 +226182,7 @@ function buildWorkorderActivity(input) {
|
|
|
225371
226182
|
break;
|
|
225372
226183
|
}
|
|
225373
226184
|
}
|
|
225374
|
-
const reviewedWorkIds = new Set(
|
|
225375
|
-
[...reviewCards.keys()].map((rid) => reviewById.get(rid)?.targetWorkId).filter(Boolean)
|
|
225376
|
-
);
|
|
226185
|
+
const reviewedWorkIds = new Set([...snap.reviews, ...activityReviews(snap, events)].map((r) => r.targetWorkId));
|
|
225377
226186
|
for (const w2 of snap.works) {
|
|
225378
226187
|
if (acceptCards.has(w2.id)) continue;
|
|
225379
226188
|
if (reviewedWorkIds.has(w2.id)) continue;
|
|
@@ -225397,15 +226206,57 @@ function buildWorkorderActivity(input) {
|
|
|
225397
226206
|
status: ACTIVITY_COPY.acceptPending,
|
|
225398
226207
|
artifacts: artifactsByWork.get(w2.id) ?? [],
|
|
225399
226208
|
actions: [
|
|
225400
|
-
{ kind: "accept", label: BUTTON.acceptPass, target: node2.id, verdict: "approve" },
|
|
225401
|
-
{ kind: "accept", label: BUTTON.acceptFail, target: node2.id, verdict: "request_changes" }
|
|
226209
|
+
{ kind: "accept", label: BUTTON.acceptPass, target: node2.id, revisionId: w2.id, verdict: "approve" },
|
|
226210
|
+
{ kind: "accept", label: BUTTON.acceptFail, target: node2.id, revisionId: w2.id, verdict: "request_changes" }
|
|
225402
226211
|
]
|
|
225403
226212
|
});
|
|
225404
226213
|
}
|
|
226214
|
+
const visibleReviews = /* @__PURE__ */ new Map();
|
|
226215
|
+
const allReviews = activityReviews(snap, events);
|
|
226216
|
+
const reviewWorks = /* @__PURE__ */ new Map();
|
|
226217
|
+
const reviewCards = /* @__PURE__ */ new Map();
|
|
226218
|
+
for (const r of allReviews) {
|
|
226219
|
+
const rows = reviewWorks.get(r.targetWorkId) ?? [];
|
|
226220
|
+
rows.push(r);
|
|
226221
|
+
reviewWorks.set(r.targetWorkId, rows);
|
|
226222
|
+
const cardId = reviewCardId(r.targetWorkId, r.reviewerActorId);
|
|
226223
|
+
const group = reviewCards.get(cardId) ?? { workId: r.targetWorkId, reviewerId: r.reviewerActorId, reviews: [] };
|
|
226224
|
+
group.reviews.push(r);
|
|
226225
|
+
reviewCards.set(cardId, group);
|
|
226226
|
+
}
|
|
226227
|
+
for (const [cardId, { workId, reviewerId, reviews }] of reviewCards) {
|
|
226228
|
+
const summary = reviewSummary(snap, reviews, events);
|
|
226229
|
+
const first = reviews[0];
|
|
226230
|
+
const ids2 = new Set(reviews.map((r) => r.id));
|
|
226231
|
+
const created = events.filter((r) => r.event.kind === "review.create" && ids2.has(r.event.reviewId));
|
|
226232
|
+
const dates = summary.latest.flatMap((r) => [r.createdAt, r.startedAt, r.endedAt, r.decidedAt, r.cancelledAt, r.retryAt].filter((v2) => !!v2));
|
|
226233
|
+
const pending = summary.pending.filter((r) => r.reviewerActorId.startsWith("actor:human:") && (!input.viewerActorId || r.reviewerActorId === input.viewerActorId));
|
|
226234
|
+
visibleReviews.set(cardId, {
|
|
226235
|
+
id: cardId,
|
|
226236
|
+
reviewWorkId: workId,
|
|
226237
|
+
reviewerId,
|
|
226238
|
+
reviewerIds: [reviewerId],
|
|
226239
|
+
traceAvailable: true,
|
|
226240
|
+
seq: created.length ? Math.min(...created.map((r) => r.seq)) : 0,
|
|
226241
|
+
at: first.createdAt,
|
|
226242
|
+
updatedAt: dates.sort().at(-1) ?? first.createdAt,
|
|
226243
|
+
nodeId: first.nodeId,
|
|
226244
|
+
executorId: reviewerId,
|
|
226245
|
+
phase: summary.phase,
|
|
226246
|
+
status: summary.status,
|
|
226247
|
+
detail: summary.latest.length === 1 ? summary.latest[0].note ? `${summary.latest[0].verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${summary.latest[0].note}` : void 0 : summary.latest.map((r) => `${nameOf(r.reviewerActorId)}\uFF08\u8BC4\u5BA1\u7EC4 ${[...new Set(summary.latest.map((r2) => r2.reviewGroup))].indexOf(r.reviewGroup) + 1}\uFF09\uFF1A${reviewLabel(r)}${r.note ? `
|
|
226248
|
+
${r.verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${r.note}` : ""}`).join("\n\n"),
|
|
226249
|
+
artifacts: artifactsByWork.get(workId) ?? [],
|
|
226250
|
+
actions: pending.length ? [
|
|
226251
|
+
{ kind: "review", label: BUTTON.approve, target: first.nodeId, revisionId: workId, verdict: "approve" },
|
|
226252
|
+
{ kind: "review", label: BUTTON.reject, target: first.nodeId, revisionId: workId, verdict: "request_changes" }
|
|
226253
|
+
] : []
|
|
226254
|
+
});
|
|
226255
|
+
}
|
|
225405
226256
|
const drafts = [
|
|
225406
226257
|
...standalone,
|
|
225407
226258
|
...groupCards.values(),
|
|
225408
|
-
...
|
|
226259
|
+
...visibleReviews.values(),
|
|
225409
226260
|
...issueCards.values(),
|
|
225410
226261
|
...acceptCards.values()
|
|
225411
226262
|
].sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
|
|
@@ -225429,7 +226280,14 @@ function buildWorkorderActivity(input) {
|
|
|
225429
226280
|
}
|
|
225430
226281
|
}
|
|
225431
226282
|
for (const d of drafts) {
|
|
225432
|
-
if (d.id.startsWith("issue:"))
|
|
226283
|
+
if (!d.id.startsWith("issue:")) continue;
|
|
226284
|
+
const issueId = d.id.slice("issue:".length);
|
|
226285
|
+
if (issueById.get(issueId)?.kind === "gap") {
|
|
226286
|
+
d.traceAvailable = true;
|
|
226287
|
+
d.traceWorkIds = gapOrigins.get(issueId);
|
|
226288
|
+
} else {
|
|
226289
|
+
d.traceWorkIds = issueWorks.get(issueId);
|
|
226290
|
+
}
|
|
225433
226291
|
}
|
|
225434
226292
|
const runIdOfCard = (d) => {
|
|
225435
226293
|
const map = input.runIdByTarget;
|
|
@@ -225439,7 +226297,14 @@ function buildWorkorderActivity(input) {
|
|
|
225439
226297
|
if (run) return run;
|
|
225440
226298
|
}
|
|
225441
226299
|
}
|
|
225442
|
-
|
|
226300
|
+
if (d.reviewWorkId) {
|
|
226301
|
+
for (const r of [...reviewWorks.get(d.reviewWorkId) ?? []].reverse()) {
|
|
226302
|
+
if (d.reviewerId && r.reviewerActorId !== d.reviewerId) continue;
|
|
226303
|
+
const run = map?.get(`review:${r.id}`);
|
|
226304
|
+
if (run) return run;
|
|
226305
|
+
}
|
|
226306
|
+
}
|
|
226307
|
+
return void 0;
|
|
225443
226308
|
};
|
|
225444
226309
|
const cards = drafts.map((d) => ({
|
|
225445
226310
|
id: d.id,
|
|
@@ -225448,12 +226313,14 @@ function buildWorkorderActivity(input) {
|
|
|
225448
226313
|
updatedAt: d.updatedAt,
|
|
225449
226314
|
...d.nodeId ? { nodeId: d.nodeId, nodeTitle: nodeName(d.nodeId) } : {},
|
|
225450
226315
|
executor: ref2(d.executorId),
|
|
226316
|
+
...d.reviewWorkId ? { reviewWorkId: d.reviewWorkId, reviewers: d.reviewerIds?.map(ref2) } : {},
|
|
225451
226317
|
...d.handActorId ? { handActor: ref2(d.handActorId) } : {},
|
|
225452
226318
|
phase: d.phase,
|
|
225453
226319
|
status: d.status,
|
|
225454
226320
|
...d.detail ? { detail: d.detail } : {},
|
|
225455
226321
|
artifacts: d.artifacts,
|
|
225456
226322
|
actions: d.actions,
|
|
226323
|
+
...d.traceAvailable ? { traceAvailable: true } : {},
|
|
225457
226324
|
...d.traceWorkIds?.length ? { traceWorkIds: d.traceWorkIds, traceAvailable: true } : {},
|
|
225458
226325
|
.../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(runIdOfCard(d))
|
|
225459
226326
|
}));
|
|
@@ -225465,6 +226332,7 @@ var init_activity2 = __esm({
|
|
|
225465
226332
|
"use strict";
|
|
225466
226333
|
init_src();
|
|
225467
226334
|
init_src4();
|
|
226335
|
+
init_review_activity();
|
|
225468
226336
|
init_workorder_manager();
|
|
225469
226337
|
ACTIVITY_EVENT_KINDS = [
|
|
225470
226338
|
"plan.changed",
|
|
@@ -225629,12 +226497,21 @@ function buildWorkorderActivityTrace(input) {
|
|
|
225629
226497
|
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
225630
226498
|
const empty2 = { workorderId, cardId, attempts: [] };
|
|
225631
226499
|
if (!snap) return empty2;
|
|
226500
|
+
const reviews = activityReviews(snap, input.events ?? []);
|
|
226501
|
+
const reviewTarget = reviewCardTarget(cardId, reviews);
|
|
226502
|
+
if (reviewTarget) return buildReviewHistory(input, reviewTarget.targetWorkId, reviewTarget.reviewerActorId, reviews);
|
|
225632
226503
|
const card2 = input.events ? buildWorkorderActivity({
|
|
225633
226504
|
workorderId,
|
|
225634
226505
|
snap,
|
|
225635
226506
|
events: input.events,
|
|
225636
226507
|
ref: ref2
|
|
225637
226508
|
}).cards.find((c) => c.id === cardId) : void 0;
|
|
226509
|
+
const gap = cardId.startsWith("issue:") ? snap.issues.find((i) => i.id === cardId.slice(6) && i.kind === "gap") : void 0;
|
|
226510
|
+
if (gap && !card2?.traceWorkIds?.length) return {
|
|
226511
|
+
...empty2,
|
|
226512
|
+
works: [],
|
|
226513
|
+
notice: "\u7F3A\u53E3\u8BB0\u5F55\u6CA1\u6709\u660E\u786E\u5173\u8054\u5230\u67D0\u6B21\u6267\u884C\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u8F68\u8FF9\u3002"
|
|
226514
|
+
};
|
|
225638
226515
|
const workIds = card2?.traceWorkIds ?? (cardId.startsWith("work:") ? [cardId.slice(5)] : []);
|
|
225639
226516
|
if (workIds.length > 0) {
|
|
225640
226517
|
let firstBriefing;
|
|
@@ -225666,6 +226543,7 @@ function buildWorkorderActivityTrace(input) {
|
|
|
225666
226543
|
cardId,
|
|
225667
226544
|
briefing: firstBriefing,
|
|
225668
226545
|
works,
|
|
226546
|
+
...gap && !gap.aboutWorkId ? { notice: "\u6839\u636E\u7F3A\u53E3\u63D0\u51FA\u65F6\u7684\u8BB0\u5F55\uFF0C\u5173\u8054\u5230\u8BE5\u8282\u70B9\u3001\u8BE5\u6267\u884C\u4EBA\u5F53\u65F6\u552F\u4E00\u7684\u6267\u884C\u3002" } : {},
|
|
225669
226547
|
attempts: works.flatMap((w2) => w2.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId))
|
|
225670
226548
|
};
|
|
225671
226549
|
}
|
|
@@ -225781,10 +226659,98 @@ function buildSingleTrace(input) {
|
|
|
225781
226659
|
});
|
|
225782
226660
|
return { workorderId, cardId, briefing, attempts };
|
|
225783
226661
|
}
|
|
226662
|
+
function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
|
|
226663
|
+
const { workorderId, cardId } = input;
|
|
226664
|
+
const snap = input.snap;
|
|
226665
|
+
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
226666
|
+
const rows = allReviews.filter((r) => r.targetWorkId === targetWorkId && r.reviewerActorId === reviewerActorId);
|
|
226667
|
+
if (!rows.length) return { workorderId, cardId, attempts: [] };
|
|
226668
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
226669
|
+
const current = new Set(latestActivityReviews(rows, input.events ?? []).map((r) => r.id));
|
|
226670
|
+
const work = snap.works.find((w2) => w2.id === targetWorkId);
|
|
226671
|
+
const nodeId = rows[0].nodeId;
|
|
226672
|
+
const events = [];
|
|
226673
|
+
for (const rec of [...input.events ?? []].sort((a, b2) => a.seq - b2.seq)) {
|
|
226674
|
+
const e = rec.event;
|
|
226675
|
+
const r = "reviewId" in e && e.reviewId ? byId.get(e.reviewId) : void 0;
|
|
226676
|
+
const direct = e.kind === "review.response" && e.targetWorkId === targetWorkId && e.reviewerActorId === reviewerActorId;
|
|
226677
|
+
let retry = false;
|
|
226678
|
+
if (e.kind === "plan.node_retry" && e.nodeId === nodeId) {
|
|
226679
|
+
const preceding = snap.works.filter((w2) => w2.nodeId === nodeId && w2.createdAt <= rec.createdAt).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
226680
|
+
retry = preceding[0]?.id === targetWorkId && rows.some((r2) => r2.createdAt <= rec.createdAt);
|
|
226681
|
+
}
|
|
226682
|
+
if (!r && !direct && !retry) continue;
|
|
226683
|
+
let label;
|
|
226684
|
+
let detail;
|
|
226685
|
+
switch (e.kind) {
|
|
226686
|
+
case "review.create":
|
|
226687
|
+
label = "\u521B\u5EFA\u5BA1\u6838";
|
|
226688
|
+
break;
|
|
226689
|
+
case "review.started":
|
|
226690
|
+
label = "\u5F00\u59CB\u5BA1\u6838";
|
|
226691
|
+
break;
|
|
226692
|
+
case "review.timeout":
|
|
226693
|
+
label = "\u5BA1\u6838\u6267\u884C\u8D85\u65F6 / \u5931\u8D25";
|
|
226694
|
+
break;
|
|
226695
|
+
case "review.kill":
|
|
226696
|
+
label = "\u5BA1\u6838\u5DF2\u505C\u6B62";
|
|
226697
|
+
detail = e.reason;
|
|
226698
|
+
break;
|
|
226699
|
+
case "review.response":
|
|
226700
|
+
label = e.verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : "\u5BA1\u6838\u4E0D\u901A\u8FC7";
|
|
226701
|
+
detail = e.note ?? void 0;
|
|
226702
|
+
break;
|
|
226703
|
+
case "plan.node_retry":
|
|
226704
|
+
label = "\u4ECB\u5165\u91CD\u8BD5";
|
|
226705
|
+
break;
|
|
226706
|
+
default:
|
|
226707
|
+
continue;
|
|
226708
|
+
}
|
|
226709
|
+
const actorId = e.kind === "plan.node_retry" ? e.by : "reviewerActorId" in e ? e.reviewerActorId : rec.actorId;
|
|
226710
|
+
events.push({
|
|
226711
|
+
seq: rec.seq,
|
|
226712
|
+
at: rec.createdAt,
|
|
226713
|
+
kind: e.kind,
|
|
226714
|
+
label,
|
|
226715
|
+
actor: ref2(actorId),
|
|
226716
|
+
...r ? { reviewId: r.id, reviewGroup: r.reviewGroup } : {},
|
|
226717
|
+
...detail ? { detail } : {}
|
|
226718
|
+
});
|
|
226719
|
+
}
|
|
226720
|
+
const reviews = rows.map((r) => {
|
|
226721
|
+
const single = buildSingleTrace({ ...input, snap: { ...snap, reviews: allReviews }, cardId: `review:${r.id}` });
|
|
226722
|
+
return {
|
|
226723
|
+
reviewId: r.id,
|
|
226724
|
+
reviewGroup: r.reviewGroup,
|
|
226725
|
+
actor: ref2(r.reviewerActorId),
|
|
226726
|
+
at: r.createdAt,
|
|
226727
|
+
...r.endedAt ?? r.decidedAt ?? r.cancelledAt ? { endedAt: r.endedAt ?? r.decidedAt ?? r.cancelledAt } : {},
|
|
226728
|
+
status: reviewLabel(r),
|
|
226729
|
+
current: current.has(r.id),
|
|
226730
|
+
...r.note ? { note: r.note } : {},
|
|
226731
|
+
attempts: single.attempts
|
|
226732
|
+
};
|
|
226733
|
+
});
|
|
226734
|
+
return {
|
|
226735
|
+
workorderId,
|
|
226736
|
+
cardId,
|
|
226737
|
+
attempts: reviews.flatMap((r) => r.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId)),
|
|
226738
|
+
reviewHistory: {
|
|
226739
|
+
targetWorkId,
|
|
226740
|
+
nodeTitle: snap.nodes.find((n) => n.id === nodeId)?.title ?? nodeId,
|
|
226741
|
+
...work?.outputVersionNo != null ? { outputVersionNo: work.outputVersionNo } : {},
|
|
226742
|
+
artifacts: work ? artifactsOfWork(work, input) : [],
|
|
226743
|
+
reviews,
|
|
226744
|
+
events,
|
|
226745
|
+
...input.dispatches === void 0 ? { recordsUnavailable: true } : {}
|
|
226746
|
+
}
|
|
226747
|
+
};
|
|
226748
|
+
}
|
|
225784
226749
|
var init_activity_trace = __esm({
|
|
225785
226750
|
"../server/src/domains/collab/activity-trace.ts"() {
|
|
225786
226751
|
"use strict";
|
|
225787
226752
|
init_src4();
|
|
226753
|
+
init_review_activity();
|
|
225788
226754
|
init_activity2();
|
|
225789
226755
|
init_workorder_manager();
|
|
225790
226756
|
}
|
|
@@ -227870,6 +228836,7 @@ function collabDomain(opts) {
|
|
|
227870
228836
|
return {
|
|
227871
228837
|
status: 200,
|
|
227872
228838
|
body: buildWorkorderActivity({
|
|
228839
|
+
viewerActorId: req.auth.actor,
|
|
227873
228840
|
workorderId,
|
|
227874
228841
|
snap,
|
|
227875
228842
|
events,
|
|
@@ -227905,7 +228872,12 @@ function collabDomain(opts) {
|
|
|
227905
228872
|
};
|
|
227906
228873
|
const [snap, events] = engineStore ? await engineStore.transaction(async (tx) => [
|
|
227907
228874
|
await tx.loadWorkorder(workorderId),
|
|
227908
|
-
await tx.listEvents(
|
|
228875
|
+
await tx.listEvents(
|
|
228876
|
+
workorderId,
|
|
228877
|
+
0,
|
|
228878
|
+
cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? void 0 : ACTIVITY_EVENT_LIMIT,
|
|
228879
|
+
cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? REVIEW_TRACE_EVENT_KINDS : ACTIVITY_EVENT_KINDS
|
|
228880
|
+
)
|
|
227909
228881
|
]) : [null, []];
|
|
227910
228882
|
const manifestFiles = snap ? await resolveActivityManifestFiles(snap.artifacts, blobs) : void 0;
|
|
227911
228883
|
const dispatches = await listDispatchRows(workorderId, opts.dispatchesOfWorkorder);
|
|
@@ -228026,6 +228998,9 @@ function collabDomain(opts) {
|
|
|
228026
228998
|
return { status: 201, body: { item } };
|
|
228027
228999
|
});
|
|
228028
229000
|
router.post("/api/workorders/:id/hold", async (req) => {
|
|
229001
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
229002
|
+
return { status: 403, body: { error: { code: "forbidden", message: "\u6682\u505C\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
|
|
229003
|
+
}
|
|
228029
229004
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
228030
229005
|
const ws = req.params.id;
|
|
228031
229006
|
const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
|
|
@@ -228065,6 +229040,9 @@ function collabDomain(opts) {
|
|
|
228065
229040
|
return { status: 200, body: { cancelled } };
|
|
228066
229041
|
});
|
|
228067
229042
|
router.post("/api/workorders/:id/dispatch", async (req) => {
|
|
229043
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
229044
|
+
return { status: 403, body: { error: { code: "forbidden", message: "\u6062\u590D\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
|
|
229045
|
+
}
|
|
228068
229046
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
228069
229047
|
const ws = req.params.id;
|
|
228070
229048
|
const already = !kernel.model.pausedWorkorders.has(ws);
|
|
@@ -228390,6 +229368,7 @@ var init_collab = __esm({
|
|
|
228390
229368
|
init_workorder_detail();
|
|
228391
229369
|
init_workorders();
|
|
228392
229370
|
init_activity2();
|
|
229371
|
+
init_review_activity();
|
|
228393
229372
|
init_activity_trace();
|
|
228394
229373
|
init_inbox();
|
|
228395
229374
|
init_escalation_attention();
|
|
@@ -231745,7 +232724,8 @@ function createChatSessionsDomain(opts) {
|
|
|
231745
232724
|
...Object.prototype.hasOwnProperty.call(body2, "projectId") ? { projectId: body2.projectId } : {},
|
|
231746
232725
|
// 会话级模型覆盖:显式传 null / 空串 = 清除覆盖(回到旧解析);不传这一项 = 不动它。
|
|
231747
232726
|
...Object.prototype.hasOwnProperty.call(body2, "model") ? { model: normalizeModel(body2.model) } : {},
|
|
231748
|
-
|
|
232727
|
+
// 只有显式传了才写——改标题 / 换项目 / 换模型都不是「有新动静」,不该顶掉列表顺序与已读水位。
|
|
232728
|
+
...body2.touchedAt !== void 0 ? { touchedAt: body2.touchedAt } : {}
|
|
231749
232729
|
});
|
|
231750
232730
|
return { status: 200, body: await store2.getSession(req.params.id) };
|
|
231751
232731
|
});
|
|
@@ -231964,7 +232944,11 @@ function createChatSessionsDomain(opts) {
|
|
|
231964
232944
|
...record8.label ? { label: record8.label } : {},
|
|
231965
232945
|
...record8.taskDigest ? { taskDigest: record8.taskDigest } : {},
|
|
231966
232946
|
parentMessageId: record8.parentMessageId,
|
|
231967
|
-
|
|
232947
|
+
/* `messageId` 必须过 `touchAnchorMessageId`:流水行的主键是 `(delegation_id, message_id)`,
|
|
232948
|
+
服务端为此在锚点后缀了一段随机 token(`DelegationService.touchAnchor`)。那段后缀是存储
|
|
232949
|
+
的去重装置,直接发给前端等于给了一条 `messages[]` 里不存在的 id ——追加转达卡全部掉进
|
|
232950
|
+
floating 堆在流末尾,同一条消息上的两条委派还会因后缀不同被拆成两张单专家卡。 */
|
|
232951
|
+
touches: touches.map((t) => ({ messageId: touchAnchorMessageId(t.messageId), kind: t.kind, at: t.at })),
|
|
231968
232952
|
artifacts: collectDelegationArtifacts(record8.roundSummaries),
|
|
231969
232953
|
createdAt: record8.createdAt,
|
|
231970
232954
|
...record8.settledAt ? { settledAt: record8.settledAt } : {}
|
|
@@ -244470,6 +245454,64 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244470
245454
|
async appendMessage(m2, opts) {
|
|
244471
245455
|
return (await this.appendMessageOnce(m2, opts)).message;
|
|
244472
245456
|
}
|
|
245457
|
+
/**
|
|
245458
|
+
* 系统状态条的 `chat_items` 补行(ADR-0510 D3 的收口)。
|
|
245459
|
+
*
|
|
245460
|
+
* ## 为什么需要
|
|
245461
|
+
*
|
|
245462
|
+
* `role='system'` 的通告(委派回报「已完成 / 无法完成」、工单终态播报、授权卡状态条)走的是
|
|
245463
|
+
* `appendChatMessageOnce` → 本 store 直写 `chat_messages` 这条路,**不经 `ChatItemLedger`**
|
|
245464
|
+
* (`chat-system-message.ts` 文件头原话:「非 live 直写……不产生 chat_items」)。
|
|
245465
|
+
*
|
|
245466
|
+
* 在 D3 之前这没关系:`/items` 会给没有 item 行的消息合成一条快照条。D3 把历史收成
|
|
245467
|
+
* **只从 `chat_items` 出**、删掉了那条合成路之后,这些通告就从对话页上**凭空消失**了。
|
|
245468
|
+
*
|
|
245469
|
+
* 后果不只是「少看见一条通告」——委派产物的附件卡是**锚在那条回报通告之后的第一条 assistant**
|
|
245470
|
+
* 上的(ws:wo-4a2cd648 §①)。通告不在流里 ⇒ 找不到锚点 ⇒ 卡掉进 floating、渲染到末尾。
|
|
245471
|
+
* 2026-09-09 用户现场:`--continue` 续跑两个子会话,回报通告写进了 `chat_messages`
|
|
245472
|
+
* (07:29:56 / 07:30:12)但 `chat_items` 一行都没有,产物卡因此挂错位置。
|
|
245473
|
+
*
|
|
245474
|
+
* ## 为什么补在这一层
|
|
245475
|
+
*
|
|
245476
|
+
* `appendChatMessageOnce` 是所有系统消息的唯一入口,但那三个调用点(return-flow /
|
|
245477
|
+
* chat-broadcast / server.ts 授权卡)**都拿不到 `ChatItemStore`**,从上面递下来要动三份 deps 契约。
|
|
245478
|
+
* 而这里本来就握着同一个 schema 的连接池——把「用户能看见的消息必有 item 行」这条不变量
|
|
245479
|
+
* 落在存储边界上,比在三个业务处各接一次线更难漏。
|
|
245480
|
+
*
|
|
245481
|
+
* ## 失败了怎么办:吞掉
|
|
245482
|
+
*
|
|
245483
|
+
* item 写失败**不许**把消息本身带走——消息已经 INSERT 成功、幂等键已经占住,抛出去会让调用方
|
|
245484
|
+
* 当成「没写成」而重试,而重试会撞主键。所以这里只记日志。代价是那条通告在页面上仍然看不见,
|
|
245485
|
+
* 与修复前一致,不会更坏。
|
|
245486
|
+
*/
|
|
245487
|
+
async writeSystemMessageItem(m2) {
|
|
245488
|
+
try {
|
|
245489
|
+
await this.pool.query(
|
|
245490
|
+
`INSERT INTO ${this.s}.chat_items
|
|
245491
|
+
(id, session_id, message_id, turn_id, run_id, seq, kind, role, status,
|
|
245492
|
+
version, turn_version, provider_item_key, payload, metadata,
|
|
245493
|
+
created_at, updated_at, completed_at)
|
|
245494
|
+
VALUES ($1, $2, $3, $4, NULL, 1, 'text', $5, 'completed',
|
|
245495
|
+
COALESCE((SELECT MAX(version) FROM ${this.s}.chat_items WHERE session_id = $2), 0) + 1,
|
|
245496
|
+
1, NULL, $6::jsonb, '{"legacyContent": false}'::jsonb,
|
|
245497
|
+
$7::timestamptz, $7::timestamptz, $7::timestamptz)
|
|
245498
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
245499
|
+
[
|
|
245500
|
+
// 与 D3 之前 `/items` 合成条、以及 backfill 补的行**同一套身份**:同一条消息永远只有
|
|
245501
|
+
// 这一个 itemId,重复调用(幂等重写、backfill 再跑)不会造出第二条。
|
|
245502
|
+
`chat-message:${m2.id}`,
|
|
245503
|
+
m2.sessionId,
|
|
245504
|
+
m2.id,
|
|
245505
|
+
`chat-message-turn:${m2.id}`,
|
|
245506
|
+
m2.role,
|
|
245507
|
+
JSON.stringify({ role: m2.role, text: m2.content ?? "" }),
|
|
245508
|
+
m2.createdAt
|
|
245509
|
+
]
|
|
245510
|
+
);
|
|
245511
|
+
} catch (err) {
|
|
245512
|
+
console.warn(`[chat-items] \u7CFB\u7EDF\u72B6\u6001\u6761\u8865\u884C\u5931\u8D25\uFF08message=${m2.id} session=${m2.sessionId}\uFF09: ${String(err)}`);
|
|
245513
|
+
}
|
|
245514
|
+
}
|
|
244473
245515
|
async appendMessageOnce(m2, opts) {
|
|
244474
245516
|
await this.assertSessionInScope(m2.sessionId);
|
|
244475
245517
|
const insert = async () => {
|
|
@@ -244486,7 +245528,9 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244486
245528
|
};
|
|
244487
245529
|
for (let attempt = 0; ; attempt++) {
|
|
244488
245530
|
try {
|
|
244489
|
-
|
|
245531
|
+
const written = { message: { ...m2, seq: await insert() }, inserted: true };
|
|
245532
|
+
if ((opts?.source ?? "live") === "system") await this.writeSystemMessageItem(written.message);
|
|
245533
|
+
return written;
|
|
244490
245534
|
} catch (err) {
|
|
244491
245535
|
const isUniqueViolation5 = err && typeof err === "object" && err.code === "23505";
|
|
244492
245536
|
const constraint = String(err.constraint ?? "");
|
|
@@ -245138,11 +246182,17 @@ var init_postgres_chat_sessions = __esm({
|
|
|
245138
246182
|
model: row.model ?? null
|
|
245139
246183
|
});
|
|
245140
246184
|
rowToSessionWithQuality = (row) => {
|
|
246185
|
+
const status = row.last_assistant_status ?? void 0;
|
|
245141
246186
|
const quality = deriveLastTurnQuality({
|
|
245142
|
-
status
|
|
246187
|
+
status,
|
|
245143
246188
|
content: row.last_assistant_content ?? ""
|
|
245144
246189
|
});
|
|
245145
|
-
|
|
246190
|
+
const running = deriveSessionRunning({ status });
|
|
246191
|
+
return {
|
|
246192
|
+
...rowToSession(row),
|
|
246193
|
+
...quality ? { lastTurnQuality: quality } : {},
|
|
246194
|
+
...running ? { running: true } : {}
|
|
246195
|
+
};
|
|
245146
246196
|
};
|
|
245147
246197
|
rowToMessage = (row) => ({
|
|
245148
246198
|
id: row.id,
|
|
@@ -263050,7 +264100,7 @@ var COMMAND_DECLS = {
|
|
|
263050
264100
|
{ name: "as", desc: "\u7ED9\u8FD9\u6B21\u59D4\u6D3E\u8D77\u4E2A\u77ED\u540D\uFF0C\u4E4B\u540E\u7528\u5B83 --continue\uFF1B\u649E\u540D\u670D\u52A1\u7AEF\u81EA\u52A8\u52A0\u540E\u7F00\uFF0C**\u4EE5\u8F93\u51FA\u91CC\u90A3\u4E2A\u4E3A\u51C6**" },
|
|
263051
264101
|
{ name: "file", desc: "\u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF08\u4F60\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u7684\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u53EF\u7ED9\u591A\u6B21\uFF1B\u5355\u4E2A \u22642MB\u3001\u4E00\u6B21 \u226420 \u4E2A / 20MB\uFF09" },
|
|
263052
264102
|
{ name: "continue", desc: "\u5F80\u5DF2\u6709\u59D4\u6D3E\u91CC\u518D\u8F6C\u8FBE\u4E00\u53E5\uFF1A\u7ED9 label / \u4E13\u5BB6\u540D / delegationId \u524D\u7F00" },
|
|
263053
|
-
{ name: "text", desc: "\u8F6C\u8FBE\u5185\u5BB9\uFF08\u914D --continue\uFF09\u3002\u6539\u65B9\u5411\u5C31\u660E\u8BF4\u300C\u6539\u65B9\u5411\u300D\uFF0C\u4E13\u5BB6\u4F1A\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u653E\u5F03\u624B\u4E0A\u90A3\u6BB5" },
|
|
264103
|
+
{ name: "text", desc: "\u8F6C\u8FBE\u5185\u5BB9\uFF08\u914D --continue\uFF09\u3002\u6539\u65B9\u5411\u5C31\u660E\u8BF4\u300C\u6539\u65B9\u5411\u300D\uFF0C\u4E13\u5BB6\u4F1A\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u653E\u5F03\u624B\u4E0A\u90A3\u6BB5\u3002**\u770B\u56DE\u6267**\uFF1A\u63D2\u8FDB\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E / \u8D77\u4E86\u65B0\u7684\u4E00\u8F6E / \u8FD8\u6392\u5728\u961F\u5217\u91CC\u2014\u2014\u6392\u961F\u65F6\u4E13\u5BB6\u8981\u7B49\u672C\u8F6E\u8DD1\u5B8C\u624D\u770B\u89C1\uFF0C\u8BE5\u8DDF\u7528\u6237\u8BF4\u4E00\u58F0" },
|
|
263054
264104
|
{ name: "status", desc: "\u770B\u4E00\u4E2A\u59D4\u6D3E\u7684\u8BE6\u60C5\uFF1A\u5168\u91CF\u5404\u8F6E\u5C0F\u7ED3 + \u4EA7\u7269\u6E05\u5355" },
|
|
263055
264105
|
{ name: "list", desc: "\u5217\u51FA\u8FD9\u6761\u5BF9\u8BDD\u6D3E\u51FA\u53BB\u7684\u6D3B\uFF1B\u9ED8\u8BA4\u53EA\u7ED9\u8FDB\u884C\u4E2D + \u6700\u8FD1 10 \u6761", boolean: true },
|
|
263056
264106
|
{ name: "all", desc: "\u914D --list\uFF1A\u8FDE\u5386\u53F2\u59D4\u6D3E\u4E00\u8D77\u5217", boolean: true },
|
|
@@ -265536,15 +266586,26 @@ ${round.text}`);
|
|
|
265536
266586
|
const text5 = flags.get("text");
|
|
265537
266587
|
if (!text5) throw new Error('--continue \u8981\u914D --text "<\u8981\u8F6C\u8FBE\u7684\u8BDD>"');
|
|
265538
266588
|
const target = await resolveRef2(continueRef);
|
|
265539
|
-
await api.post(
|
|
266589
|
+
const outcome = await api.post(
|
|
265540
266590
|
`/api/delegations/${encodeURIComponent(target.id)}/messages`,
|
|
265541
266591
|
{ text: text5, ...fileArgs.length ? { files: fileArgs } : {} }
|
|
265542
266592
|
);
|
|
265543
266593
|
if (asJson2) {
|
|
265544
|
-
println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs }, null, 2));
|
|
266594
|
+
println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs, ...outcome }, null, 2));
|
|
265545
266595
|
break;
|
|
265546
266596
|
}
|
|
265547
|
-
|
|
266597
|
+
const who = `${target.expertName ?? target.expertActorId}\uFF08${target.label ?? target.id.slice(0, 8)}\uFF09`;
|
|
266598
|
+
if (outcome?.outcome === "inserted") {
|
|
266599
|
+
println(`\u5DF2\u63D2\u8FDB ${who} \u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\u2014\u2014\u5B83\u628A\u5F53\u524D\u8FD9\u4E00\u6B65\u7B54\u5B8C\u5C31\u4F1A\u770B\u5230\u3002`);
|
|
266600
|
+
} else if (outcome?.outcome === "started-round") {
|
|
266601
|
+
println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\uFF1A\u5B83\u4E0A\u4E00\u8F6E\u5DF2\u7ECF\u7ED3\u675F\uFF0C\u8FD9\u53E5\u8BDD\u5F53\u573A\u8D77\u4E86\u65B0\u7684\u4E00\u8F6E\u3002`);
|
|
266602
|
+
} else if (outcome?.outcome === "queued") {
|
|
266603
|
+
println(`\u5DF2\u6536\u4E0B\uFF0C\u4F46${who}**\u73B0\u5728\u8FD8\u770B\u4E0D\u5230**\uFF1A${outcome.reason ?? "\u63D2\u4E0D\u8FDB\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E"}\u3002`);
|
|
266604
|
+
println(` \u8FD9\u53E5\u8BDD\u6392\u5728\u5F85\u8F6C\u8FBE\u961F\u5217\u91CC\uFF08\u8FD8\u6709 ${outcome.pendingInputs} \u53E5\u6CA1\u9001\u5230\uFF09\uFF0C\u672C\u8F6E\u8DD1\u5B8C\u4F1A\u4F5C\u4E3A\u4E0B\u4E00\u8F6E\u53D1\u51FA\u3002`);
|
|
266605
|
+
println(" \u5B83\u8FD9\u4E00\u8F6E\u53EF\u80FD\u8FD8\u5728\u6309\u65E7\u65B9\u5411\u505A\u2014\u2014\u8981\u4E0D\u8981\u5728\u8FD9\u6761\u5BF9\u8BDD\u91CC\u5148\u8DDF\u7528\u6237\u8BF4\u660E\uFF0C\u4F60\u81EA\u5DF1\u5224\u65AD\u3002");
|
|
266606
|
+
} else {
|
|
266607
|
+
println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\u3002`);
|
|
266608
|
+
}
|
|
265548
266609
|
if (fileArgs.length) println(` \u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF1A${fileArgs.join("\u3001")}`);
|
|
265549
266610
|
println("\u8FD9\u4E00\u8F6E\u5230\u6B64\u4E3A\u6B62\uFF1A\u7ED3\u679C\u4F1A\u81EA\u5DF1\u56DE\u5230\u8FD9\u6761\u5BF9\u8BDD\uFF0C\u4E0D\u8981\u5728\u8FD9\u91CC\u7B49\u3002");
|
|
265550
266611
|
break;
|
|
@@ -268340,7 +269401,7 @@ function shimScript() {
|
|
|
268340
269401
|
}
|
|
268341
269402
|
|
|
268342
269403
|
// src/index.ts
|
|
268343
|
-
var PKG_VERSION = true ? "2.2.
|
|
269404
|
+
var PKG_VERSION = true ? "2.2.3" : "dev";
|
|
268344
269405
|
var LOCAL_BIN = localBin();
|
|
268345
269406
|
var NPM_PREFIX = npmPrefix();
|
|
268346
269407
|
var INSTANCE = DEFAULT_INSTANCE;
|