oasis_test_v2 2.2.2 → 2.2.4
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 +1578 -338
- 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",
|
|
@@ -23588,6 +24008,13 @@ function applyModelOverrides(base, overrides) {
|
|
|
23588
24008
|
}
|
|
23589
24009
|
return kept;
|
|
23590
24010
|
}
|
|
24011
|
+
function clearModelDiscoveryCache(runtime, executablePath) {
|
|
24012
|
+
if (!runtime) {
|
|
24013
|
+
modelCache.clear();
|
|
24014
|
+
return;
|
|
24015
|
+
}
|
|
24016
|
+
modelCache.delete(discoveryCacheKey(runtime, executablePath));
|
|
24017
|
+
}
|
|
23591
24018
|
function discoveryCacheKey(runtime, executablePath) {
|
|
23592
24019
|
return `${runtime}:${executablePath ?? ""}`;
|
|
23593
24020
|
}
|
|
@@ -23612,8 +24039,11 @@ async function withFallback(runtime, executablePath, discover, fallback) {
|
|
|
23612
24039
|
}
|
|
23613
24040
|
function claudeStaticModels() {
|
|
23614
24041
|
return [
|
|
23615
|
-
{ id: "claude-opus-
|
|
24042
|
+
{ id: "claude-opus-5", label: "Claude Opus 5", provider: "Anthropic" },
|
|
24043
|
+
{ id: "claude-fable-5-1", label: "Claude Fable 5.1", provider: "Anthropic" },
|
|
24044
|
+
{ id: "claude-fable-5", label: "Claude Fable 5", provider: "Anthropic" },
|
|
23616
24045
|
{ id: "claude-opus-4-8", label: "Claude Opus 4.8", provider: "Anthropic" },
|
|
24046
|
+
{ id: "claude-opus-4-7", label: "Claude Opus 4.7", provider: "Anthropic" },
|
|
23617
24047
|
{ id: "claude-opus-4-6", label: "Claude Opus 4.6", provider: "Anthropic" },
|
|
23618
24048
|
{ id: "claude-sonnet-5", label: "Claude Sonnet 5", provider: "Anthropic" },
|
|
23619
24049
|
{ id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6", provider: "Anthropic" },
|
|
@@ -23623,14 +24053,12 @@ function claudeStaticModels() {
|
|
|
23623
24053
|
}
|
|
23624
24054
|
function codexStaticModels() {
|
|
23625
24055
|
return [
|
|
23626
|
-
{ id: "gpt-
|
|
23627
|
-
{ id: "gpt-5.
|
|
23628
|
-
{ id: "gpt-5.
|
|
23629
|
-
{ id: "gpt-5.
|
|
23630
|
-
{ id: "gpt-5.
|
|
23631
|
-
{ id: "gpt-5", label: "GPT-5", provider: "OpenAI" }
|
|
23632
|
-
{ id: "o3", label: "o3", provider: "OpenAI" },
|
|
23633
|
-
{ id: "o3-mini", label: "o3-mini", provider: "OpenAI" }
|
|
24056
|
+
{ id: "gpt-6-astra", label: "GPT-6-Astra", provider: "OpenAI", default: true },
|
|
24057
|
+
{ id: "gpt-5.6-sol", label: "GPT-5.6-Sol", provider: "OpenAI" },
|
|
24058
|
+
{ id: "gpt-5.6-terra", label: "GPT-5.6-Terra", provider: "OpenAI" },
|
|
24059
|
+
{ id: "gpt-5.6-luna", label: "GPT-5.6-Luna", provider: "OpenAI" },
|
|
24060
|
+
{ id: "gpt-5.5", label: "GPT-5.5", provider: "OpenAI" },
|
|
24061
|
+
{ id: "gpt-5.2", label: "GPT-5.2", provider: "OpenAI" }
|
|
23634
24062
|
];
|
|
23635
24063
|
}
|
|
23636
24064
|
function geminiStaticModels() {
|
|
@@ -28452,6 +28880,16 @@ var init_chat_item_ledger = __esm({
|
|
|
28452
28880
|
version: writeVersion
|
|
28453
28881
|
});
|
|
28454
28882
|
row.ord = created.ord;
|
|
28883
|
+
try {
|
|
28884
|
+
this.deps.onPersisted?.({
|
|
28885
|
+
itemId: id,
|
|
28886
|
+
ord: created.ord,
|
|
28887
|
+
version: writeVersion,
|
|
28888
|
+
role: input.role,
|
|
28889
|
+
text: input.text
|
|
28890
|
+
});
|
|
28891
|
+
} catch {
|
|
28892
|
+
}
|
|
28455
28893
|
}, `create:${input.kind}`);
|
|
28456
28894
|
return id;
|
|
28457
28895
|
}
|
|
@@ -28580,17 +29018,21 @@ async function openAssistantRunningRow(deps) {
|
|
|
28580
29018
|
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28581
29019
|
if (typeof deps.chatStore.appendMessage !== "function") return void 0;
|
|
28582
29020
|
const id = (0, import_node_crypto16.randomUUID)();
|
|
29021
|
+
const at = now();
|
|
28583
29022
|
try {
|
|
28584
29023
|
await deps.chatStore.appendMessage({
|
|
28585
29024
|
id,
|
|
28586
29025
|
sessionId: deps.chatSessionId,
|
|
28587
29026
|
role: "assistant",
|
|
28588
29027
|
content: "",
|
|
28589
|
-
createdAt:
|
|
29028
|
+
createdAt: at,
|
|
28590
29029
|
status: "running",
|
|
28591
29030
|
...deps.runId ? { runId: deps.runId } : {},
|
|
28592
29031
|
...deps.turnId ? { turnId: deps.turnId } : {}
|
|
28593
29032
|
});
|
|
29033
|
+
if (typeof deps.chatStore.updateSession === "function") {
|
|
29034
|
+
await deps.chatStore.updateSession(deps.chatSessionId, { touchedAt: at }).catch(() => void 0);
|
|
29035
|
+
}
|
|
28594
29036
|
return id;
|
|
28595
29037
|
} catch {
|
|
28596
29038
|
return void 0;
|
|
@@ -141794,7 +142236,7 @@ function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
|
141794
142236
|
if (w2.nodeId !== artifactId) continue;
|
|
141795
142237
|
if (isReplyWork(w2)) continue;
|
|
141796
142238
|
nodeWorkIds.add(w2.id);
|
|
141797
|
-
const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
142239
|
+
const status = w2.status ?? (w2.retryAt && w2.deadAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
141798
142240
|
items.push({
|
|
141799
142241
|
type: "work",
|
|
141800
142242
|
id: w2.id,
|
|
@@ -159475,6 +159917,58 @@ var init_live_chat = __esm({
|
|
|
159475
159917
|
}
|
|
159476
159918
|
}
|
|
159477
159919
|
}
|
|
159920
|
+
/**
|
|
159921
|
+
* 把**用户自己那条消息**推上 v3 流(ADR-0510 D2 的收口)。
|
|
159922
|
+
*
|
|
159923
|
+
* ## 为什么需要
|
|
159924
|
+
*
|
|
159925
|
+
* 用户点发送时前端先画一条「乐观条」,它**没有服务端展示序**(D2:不许前端发明号),
|
|
159926
|
+
* 渲染时钉在列表末尾。这条规矩对「刚发完、还没有任何回复」是对的。
|
|
159927
|
+
*
|
|
159928
|
+
* 但服务端落账本时**立刻**给了这条 user item 一个 ord(它是这一轮第一个建的行,号最小),
|
|
159929
|
+
* 而这个号此前**只走快照**——`emitV3` 只服务 provider 事件(`server.ts` 那句注释:
|
|
159930
|
+
* 「不走 v3 流帧(那条只服务 assistant)」)。于是有个几秒的窗口:
|
|
159931
|
+
* agent 的正文带着更大的号从直播流到了、进入「有号」段,而用户那条还在「无号」段被钉在最后
|
|
159932
|
+
* ⇒ **agent 的回复显示在用户消息上面**,等下一次快照才跳回去。
|
|
159933
|
+
* 2026-09-09 用户现场原话:「过几秒钟页面刷新一下,agent 回复内容会回到用户消息下面」。
|
|
159934
|
+
*
|
|
159935
|
+
* 修法就是把这条也推上去:前端按 itemId(`oasis-user-optim:<csid>`,与乐观桶位同名)覆盖,
|
|
159936
|
+
* 拿到真号立刻归位,窗口从「到下一次快照」压到「一次 INSERT 往返」。
|
|
159937
|
+
*
|
|
159938
|
+
* ## 为什么不在前端算
|
|
159939
|
+
*
|
|
159940
|
+
* 试过的三种都被证伪(见 `chat-items-merge.ts` 里那段注释)。这次也不能用「记下创建时的最大号」:
|
|
159941
|
+
* 空对话首次发送时那个最大号是 0,随后到达的历史(号更大)会全部排到乐观条**后面**——
|
|
159942
|
+
* 正是 ADR D9 点名必须验的「空桶先发送」那一档。号只能由服务端给。
|
|
159943
|
+
*
|
|
159944
|
+
* ## 拿不到轮就静默跳过
|
|
159945
|
+
*
|
|
159946
|
+
* 本方法由账本的落库链回调触发,而那一刻这条会话的 live 轮可能还没 `start`
|
|
159947
|
+
* (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
|
|
159948
|
+
* 前端退回「等下一次快照」,与修复前一致,不会更坏。
|
|
159949
|
+
*/
|
|
159950
|
+
publishUserItem(chatSessionId, item) {
|
|
159951
|
+
const turn = this.turns.get(chatSessionId);
|
|
159952
|
+
if (!turn) return false;
|
|
159953
|
+
const payload = { role: "user", text: item.text };
|
|
159954
|
+
if (item.clientSubmitId) payload.clientSubmitId = item.clientSubmitId;
|
|
159955
|
+
if (item.attachments?.length) payload.attachments = item.attachments;
|
|
159956
|
+
const frame = {
|
|
159957
|
+
protocolVersion: 3,
|
|
159958
|
+
streamId: turn.liveStreamId,
|
|
159959
|
+
turnId: this.wireTurnId(turn),
|
|
159960
|
+
itemId: item.itemId,
|
|
159961
|
+
itemType: "message",
|
|
159962
|
+
operation: "set_text",
|
|
159963
|
+
itemVersion: item.version,
|
|
159964
|
+
...item.ord !== null ? { ord: item.ord } : {},
|
|
159965
|
+
offset: 0,
|
|
159966
|
+
payload
|
|
159967
|
+
};
|
|
159968
|
+
this.flushPendingV3(turn);
|
|
159969
|
+
this.publishV3(turn, this.assignV3Seq(turn, frame));
|
|
159970
|
+
return true;
|
|
159971
|
+
}
|
|
159478
159972
|
emitV3TurnTerminal(turn, status) {
|
|
159479
159973
|
if (status !== "done" && status !== "error") return;
|
|
159480
159974
|
const operation = status === "done" ? "turn_completed" : "turn_failed";
|
|
@@ -194349,8 +194843,15 @@ function isTextual(contentType) {
|
|
|
194349
194843
|
const t = contentType.toLowerCase();
|
|
194350
194844
|
return t.startsWith("text/") || t.includes("json") || t.includes("xml") || t.includes("yaml") || t.includes("javascript");
|
|
194351
194845
|
}
|
|
194352
|
-
function toPrepared(rel, source, read) {
|
|
194353
|
-
const base = {
|
|
194846
|
+
function toPrepared(rel, source, read, src) {
|
|
194847
|
+
const base = {
|
|
194848
|
+
rel,
|
|
194849
|
+
source,
|
|
194850
|
+
size: read.size,
|
|
194851
|
+
contentType: read.contentType,
|
|
194852
|
+
...src ? { srcSize: src.size } : {},
|
|
194853
|
+
...src?.mtime ? { srcMtime: src.mtime } : {}
|
|
194854
|
+
};
|
|
194354
194855
|
if (!isTextual(read.contentType)) return { ...base, base64: read.contentBase64 };
|
|
194355
194856
|
return { ...base, text: Buffer.from(read.contentBase64, "base64").toString("utf8") };
|
|
194356
194857
|
}
|
|
@@ -194387,7 +194888,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194387
194888
|
if (!lenient) throw error2;
|
|
194388
194889
|
skipped.push({ source, code: error2.code, message: error2.message });
|
|
194389
194890
|
};
|
|
194390
|
-
if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0 };
|
|
194891
|
+
if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
|
|
194391
194892
|
if (!port) {
|
|
194392
194893
|
const error2 = new DelegationFileError(
|
|
194393
194894
|
"FILE_UNREADABLE",
|
|
@@ -194396,7 +194897,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194396
194897
|
);
|
|
194397
194898
|
if (!lenient) throw error2;
|
|
194398
194899
|
for (const source of wanted) skipped.push({ source, code: error2.code, message: error2.message });
|
|
194399
|
-
return { prepared: [], skipped, totalBytes: 0 };
|
|
194900
|
+
return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
|
|
194400
194901
|
}
|
|
194401
194902
|
let accepted = wanted;
|
|
194402
194903
|
if (wanted.length > limits.maxFiles) {
|
|
@@ -194474,10 +194975,10 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194474
194975
|
used.add(rel);
|
|
194475
194976
|
prepared.push(toPrepared(rel, source, read));
|
|
194476
194977
|
}
|
|
194477
|
-
return { prepared, skipped, totalBytes };
|
|
194978
|
+
return { prepared, skipped, totalBytes, unchanged: [] };
|
|
194478
194979
|
}
|
|
194479
|
-
async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS) {
|
|
194480
|
-
const out = { prepared: [], skipped: [], totalBytes: 0 };
|
|
194980
|
+
async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS, known) {
|
|
194981
|
+
const out = { prepared: [], skipped: [], totalBytes: 0, unchanged: [] };
|
|
194481
194982
|
if (!port) return out;
|
|
194482
194983
|
if (port.locate) {
|
|
194483
194984
|
const located = await port.locate(child).catch((err) => ({
|
|
@@ -194537,7 +195038,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194537
195038
|
for (const entry of level.entries) {
|
|
194538
195039
|
const childPath = `${dir}/${entry.name}`;
|
|
194539
195040
|
if (entry.type === "dir") queue.push(childPath);
|
|
194540
|
-
else files.push({ rel: childPath, size: entry.size });
|
|
195041
|
+
else files.push({ rel: childPath, size: entry.size, ...entry.mtime ? { mtime: entry.mtime } : {} });
|
|
194541
195042
|
}
|
|
194542
195043
|
}
|
|
194543
195044
|
for (const dir of queue) {
|
|
@@ -194553,6 +195054,12 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194553
195054
|
out.skipped.push({ source: file.rel, code: "PATH_REJECTED", message: "\u8DEF\u5F84\u4E0D\u5408\u6CD5\uFF0C\u6CA1\u642C" });
|
|
194554
195055
|
continue;
|
|
194555
195056
|
}
|
|
195057
|
+
const target = `${DELEGATION_OUTBOUND_DIR}/${dirName}/${relative5}`;
|
|
195058
|
+
const seen = known?.get(target);
|
|
195059
|
+
if (seen && file.mtime && seen.size === file.size && seen.mtime === file.mtime) {
|
|
195060
|
+
out.unchanged.push(target);
|
|
195061
|
+
continue;
|
|
195062
|
+
}
|
|
194556
195063
|
if (out.prepared.length >= limits.maxFiles) {
|
|
194557
195064
|
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
195065
|
continue;
|
|
@@ -194571,7 +195078,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194571
195078
|
continue;
|
|
194572
195079
|
}
|
|
194573
195080
|
out.totalBytes += read.size;
|
|
194574
|
-
out.prepared.push(toPrepared(
|
|
195081
|
+
out.prepared.push(toPrepared(target, file.rel, read, { size: file.size, ...file.mtime ? { mtime: file.mtime } : {} }));
|
|
194575
195082
|
}
|
|
194576
195083
|
return out;
|
|
194577
195084
|
}
|
|
@@ -194628,6 +195135,31 @@ function digestOf(text5) {
|
|
|
194628
195135
|
const line = text5.trim().replace(/\s+/g, " ");
|
|
194629
195136
|
return line.length > DIGEST_CHARS ? `${line.slice(0, DIGEST_CHARS)}\u2026` : line;
|
|
194630
195137
|
}
|
|
195138
|
+
function childRoundMessageIds(childSessionId, round) {
|
|
195139
|
+
const suffix = round <= 1 ? "" : `:${round}`;
|
|
195140
|
+
return {
|
|
195141
|
+
inputId: deterministicUuid(`delegation-input:${childSessionId}${suffix}`),
|
|
195142
|
+
placeholderId: deterministicUuid(`delegation-placeholder:${childSessionId}${suffix}`)
|
|
195143
|
+
};
|
|
195144
|
+
}
|
|
195145
|
+
async function seedChildRoundRows(chatStore, childSessionId, round, prompt2, now) {
|
|
195146
|
+
const ids2 = childRoundMessageIds(childSessionId, round);
|
|
195147
|
+
await chatStore.appendMessageOnce({
|
|
195148
|
+
id: ids2.inputId,
|
|
195149
|
+
sessionId: childSessionId,
|
|
195150
|
+
role: "user",
|
|
195151
|
+
content: prompt2,
|
|
195152
|
+
createdAt: now
|
|
195153
|
+
});
|
|
195154
|
+
await chatStore.appendMessageOnce({
|
|
195155
|
+
id: ids2.placeholderId,
|
|
195156
|
+
sessionId: childSessionId,
|
|
195157
|
+
role: "assistant",
|
|
195158
|
+
content: "",
|
|
195159
|
+
status: "running",
|
|
195160
|
+
createdAt: now
|
|
195161
|
+
});
|
|
195162
|
+
}
|
|
194631
195163
|
var import_node_crypto32, DelegationError, TITLE_TASK_CHARS, DIGEST_CHARS, PENDING_QUEUE_OWNER, DISPATCH_LEASE_MS, CONSECUTIVE_FAIL_LIMIT, DelegationService;
|
|
194632
195164
|
var init_service3 = __esm({
|
|
194633
195165
|
"../server/src/domains/delegations/service.ts"() {
|
|
@@ -195053,21 +195585,7 @@ var init_service3 = __esm({
|
|
|
195053
195585
|
createdAt: now
|
|
195054
195586
|
});
|
|
195055
195587
|
}
|
|
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
|
-
});
|
|
195588
|
+
await seedChildRoundRows(chatStore, record8.childSessionId, 1, task, now);
|
|
195071
195589
|
}
|
|
195072
195590
|
/**
|
|
195073
195591
|
* 去程文件:读父会话工作区、翻成 §8.2 的同步错误码。
|
|
@@ -195227,7 +195745,7 @@ var init_service3 = __esm({
|
|
|
195227
195745
|
settledAt: failedAt
|
|
195228
195746
|
}).catch(() => null);
|
|
195229
195747
|
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
195230
|
-
await chatStore?.updateMessage(
|
|
195748
|
+
await chatStore?.updateMessage(childRoundMessageIds(record8.childSessionId, record8.roundSummaries.length + 1).placeholderId, {
|
|
195231
195749
|
content: `\u6D3E\u53D1\u6CA1\u80FD\u8D77\u6765\uFF1A${message}`,
|
|
195232
195750
|
status: "error",
|
|
195233
195751
|
completedAt: failedAt
|
|
@@ -195276,7 +195794,7 @@ var init_service3 = __esm({
|
|
|
195276
195794
|
const pending = await store.countPendingTouches(fresh.id).catch(() => 0);
|
|
195277
195795
|
const stopLoss = consecutiveFails >= CONSECUTIVE_FAIL_LIMIT;
|
|
195278
195796
|
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
195279
|
-
await chatStore?.updateMessage(
|
|
195797
|
+
await chatStore?.updateMessage(childRoundMessageIds(fresh.childSessionId, round).placeholderId, {
|
|
195280
195798
|
content: text5 || (failureKind ? `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}` : ""),
|
|
195281
195799
|
status: failureKind ? "error" : "done",
|
|
195282
195800
|
completedAt: at
|
|
@@ -195288,11 +195806,13 @@ var init_service3 = __esm({
|
|
|
195288
195806
|
});
|
|
195289
195807
|
return;
|
|
195290
195808
|
}
|
|
195291
|
-
const
|
|
195809
|
+
const settledSeqAtRound = fresh.settledSeq + 1;
|
|
195810
|
+
const settledSummary = { ...newSummary, settledSeq: settledSeqAtRound };
|
|
195811
|
+
const settled = await store.appendRoundSummary(fresh.id, settledSummary, {
|
|
195292
195812
|
consecutiveFails,
|
|
195293
195813
|
state: failureKind ? "failed" : "done",
|
|
195294
195814
|
failureKind: failureKind ?? null,
|
|
195295
|
-
settledSeq:
|
|
195815
|
+
settledSeq: settledSeqAtRound,
|
|
195296
195816
|
settledAt: at
|
|
195297
195817
|
}).catch(() => null);
|
|
195298
195818
|
if (settled) await this.notifySettled(settled, companyId);
|
|
@@ -195321,17 +195841,19 @@ var init_service3 = __esm({
|
|
|
195321
195841
|
const record8 = await store.getByChildSession(childSessionId).catch(() => null);
|
|
195322
195842
|
if (!record8 || record8.state !== "running") return record8;
|
|
195323
195843
|
const at = this.now();
|
|
195844
|
+
const settledSeqAtRound = record8.settledSeq + 1;
|
|
195324
195845
|
const newSummary = {
|
|
195325
195846
|
round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
|
|
195326
195847
|
text: `\u8FD9\u4E00\u8F6E\u88AB\u65F6\u949F\u95ED\u5408\uFF1A${reason}`,
|
|
195327
|
-
at
|
|
195848
|
+
at,
|
|
195849
|
+
settledSeq: settledSeqAtRound
|
|
195328
195850
|
};
|
|
195329
195851
|
const settled = await store.appendRoundSummary(record8.id, newSummary, {
|
|
195330
195852
|
state: "failed",
|
|
195331
195853
|
// 退出帧丢失后被时钟闭合 = `interrupted`(§9),不是 `not-started`——它确实起来过。
|
|
195332
195854
|
failureKind: "interrupted",
|
|
195333
195855
|
consecutiveFails: record8.consecutiveFails + 1,
|
|
195334
|
-
settledSeq:
|
|
195856
|
+
settledSeq: settledSeqAtRound,
|
|
195335
195857
|
settledAt: at
|
|
195336
195858
|
}).catch(() => null);
|
|
195337
195859
|
if (settled) await this.notifySettled(settled, companyId);
|
|
@@ -195417,6 +195939,15 @@ var init_service3 = __esm({
|
|
|
195417
195939
|
onDispatched: async () => {
|
|
195418
195940
|
await queue.confirm();
|
|
195419
195941
|
await store.markTouchesConsumed(record8.id, touches.map((touch) => touch.messageId), this.now());
|
|
195942
|
+
await seedChildRoundRows(
|
|
195943
|
+
chatStore,
|
|
195944
|
+
record8.childSessionId,
|
|
195945
|
+
record8.roundSummaries.length + 1,
|
|
195946
|
+
body2,
|
|
195947
|
+
this.now()
|
|
195948
|
+
).catch((err) => {
|
|
195949
|
+
console.warn(`[delegation] \u5B50\u4F1A\u8BDD\u8865\u8F6E\u6B21\u884C\u5931\u8D25\uFF08${record8.id} round=${record8.roundSummaries.length + 1}\uFF09: ${String(err)}`);
|
|
195950
|
+
});
|
|
195420
195951
|
},
|
|
195421
195952
|
/**
|
|
195422
195953
|
* **没送到就把正文放回可读态**(见 `dispatchExpert` 的 `onNotDispatched`)。
|
|
@@ -195515,6 +196046,10 @@ var init_service3 = __esm({
|
|
|
195515
196046
|
const inbound = await this.readInboundFiles(parent, files);
|
|
195516
196047
|
const inboundBundle = toBundleFiles(inbound.prepared);
|
|
195517
196048
|
let delivered = false;
|
|
196049
|
+
let deliveryReasonCode;
|
|
196050
|
+
if (!this.options.appendToChild) {
|
|
196051
|
+
deliveryReasonCode = "no-append-port";
|
|
196052
|
+
}
|
|
195518
196053
|
if (this.options.appendToChild) {
|
|
195519
196054
|
const result = await this.options.appendToChild(record8.childSessionId, {
|
|
195520
196055
|
// 文件是静默落盘的,append 这条路没有 TASK.md 可改——**必须自己在正文里说一声**,
|
|
@@ -195530,6 +196065,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195530
196065
|
...Object.keys(inboundBundle.binaryFiles).length ? { binaryFiles: inboundBundle.binaryFiles } : {}
|
|
195531
196066
|
}).catch(() => ({ accepted: false, reason: "append-threw" }));
|
|
195532
196067
|
delivered = result.accepted === true;
|
|
196068
|
+
if (!delivered) deliveryReasonCode = result.reason ?? "rejected";
|
|
195533
196069
|
}
|
|
195534
196070
|
if (!delivered) {
|
|
195535
196071
|
if (chatStore.enqueuePendingMessage) {
|
|
@@ -195588,6 +196124,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195588
196124
|
}
|
|
195589
196125
|
return {
|
|
195590
196126
|
delivered,
|
|
196127
|
+
...delivered ? {} : { deliveryReasonCode: deliveryReasonCode ?? "rejected" },
|
|
195591
196128
|
startedRound,
|
|
195592
196129
|
pendingInputs: await this.pendingInputs(store, record8.id),
|
|
195593
196130
|
// 起了新的一轮 = 快照与文件都随那一轮重新送过去了,没有「等下一轮再刷新」这回事。
|
|
@@ -195710,6 +196247,34 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195710
196247
|
}
|
|
195711
196248
|
});
|
|
195712
196249
|
|
|
196250
|
+
// ../server/src/domains/delegations/continue-outcome.ts
|
|
196251
|
+
function describeContinueOutcome(input) {
|
|
196252
|
+
if (input.delivered) return { outcome: "inserted" };
|
|
196253
|
+
if (input.startedRound) return { outcome: "started-round" };
|
|
196254
|
+
const code2 = input.deliveryReasonCode ?? "rejected";
|
|
196255
|
+
return {
|
|
196256
|
+
outcome: "queued",
|
|
196257
|
+
// 认不出的码**原样带出来**,不折成「未知原因」——排障时那串字符本身就是线索。
|
|
196258
|
+
reason: REASON_TEXT[code2] ?? `\u672A\u80FD\u63D2\u5165\uFF08${code2}\uFF09`,
|
|
196259
|
+
reasonCode: code2
|
|
196260
|
+
};
|
|
196261
|
+
}
|
|
196262
|
+
var REASON_TEXT;
|
|
196263
|
+
var init_continue_outcome = __esm({
|
|
196264
|
+
"../server/src/domains/delegations/continue-outcome.ts"() {
|
|
196265
|
+
"use strict";
|
|
196266
|
+
REASON_TEXT = {
|
|
196267
|
+
"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",
|
|
196268
|
+
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",
|
|
196269
|
+
"session-closing": "\u4E13\u5BB6\u90A3\u4E00\u8F6E\u6B63\u5728\u6536\u5C3E\uFF0C\u8FD9\u53E5\u8BDD\u6765\u665A\u4E86\u4E00\u6B65",
|
|
196270
|
+
"turn-finished": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u4E13\u5BB6\u90A3\u4E00\u8F6E\u5DF2\u7ECF\u6536\u5C3E\u4E86",
|
|
196271
|
+
rejected: "\u4E13\u5BB6\u90A3\u4E00\u4FA7\u62D2\u6536\u4E86\u8FD9\u6B21\u63D2\u5165",
|
|
196272
|
+
"append-threw": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u51FA\u9519\uFF08\u7F51\u7EDC\u6216\u8282\u70B9\u5F02\u5E38\uFF09",
|
|
196273
|
+
"no-append-port": "\u8FD9\u5957\u90E8\u7F72\u6CA1\u6709\u63A5\u4E2D\u9014\u63D2\u5165\u901A\u9053"
|
|
196274
|
+
};
|
|
196275
|
+
}
|
|
196276
|
+
});
|
|
196277
|
+
|
|
195713
196278
|
// ../server/src/domains/delegations/routes.ts
|
|
195714
196279
|
function trustedDelegationInvocationId(body2, turn) {
|
|
195715
196280
|
const scope = turn?.artifactId ?? turn?.dispatchId;
|
|
@@ -195778,8 +196343,16 @@ function delegationRoutes(service) {
|
|
|
195778
196343
|
const text5 = requireString(raw["text"], "--text");
|
|
195779
196344
|
const files = requireStringArray(raw["files"], "--file");
|
|
195780
196345
|
try {
|
|
195781
|
-
await service.continueDelegation(req.params.id, text5, callerOf(req), files);
|
|
195782
|
-
|
|
196346
|
+
const res = await service.continueDelegation(req.params.id, text5, callerOf(req), files);
|
|
196347
|
+
const view = describeContinueOutcome(res);
|
|
196348
|
+
const body2 = {
|
|
196349
|
+
outcome: view.outcome,
|
|
196350
|
+
...view.reason ? { reason: view.reason } : {},
|
|
196351
|
+
...view.reasonCode ? { reasonCode: view.reasonCode } : {},
|
|
196352
|
+
pendingInputs: res.pendingInputs,
|
|
196353
|
+
...res.attachedFiles.length ? { attachedFiles: res.attachedFiles } : {}
|
|
196354
|
+
};
|
|
196355
|
+
return { status: 200, body: body2 };
|
|
195783
196356
|
} catch (error2) {
|
|
195784
196357
|
rethrow(error2);
|
|
195785
196358
|
}
|
|
@@ -195807,6 +196380,7 @@ var init_routes2 = __esm({
|
|
|
195807
196380
|
"use strict";
|
|
195808
196381
|
import_node_crypto33 = require("node:crypto");
|
|
195809
196382
|
init_router();
|
|
196383
|
+
init_continue_outcome();
|
|
195810
196384
|
init_service3();
|
|
195811
196385
|
}
|
|
195812
196386
|
});
|
|
@@ -196178,6 +196752,9 @@ function delegationReturnText(terminal, opts) {
|
|
|
196178
196752
|
if (opts.movedPaths.length > 0) {
|
|
196179
196753
|
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
196754
|
}
|
|
196755
|
+
if ((opts.unchangedPaths?.length ?? 0) > 0) {
|
|
196756
|
+
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`);
|
|
196757
|
+
}
|
|
196181
196758
|
const locationFailures = opts.skipped.filter((s2) => s2.code !== void 0 && isLocationFailureCode(s2.code));
|
|
196182
196759
|
const contentSkipped = opts.skipped.filter((s2) => s2.code === void 0 || !isLocationFailureCode(s2.code));
|
|
196183
196760
|
if (locationFailures.length > 0) {
|
|
@@ -196217,17 +196794,21 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196217
196794
|
};
|
|
196218
196795
|
}
|
|
196219
196796
|
const dirName = delegationDirName(record8.label, record8.expertActorId, record8.id);
|
|
196797
|
+
const known = delegationOutboxFingerprints(record8.roundSummaries);
|
|
196220
196798
|
const outbound = await prepareOutboundFiles(
|
|
196221
196799
|
deps.workdir,
|
|
196222
196800
|
// 子会话与父会话同公司(§6.1.2 隔离二),所以这里的 companyId 取父会话那个。
|
|
196223
196801
|
{ chatSessionId: record8.childSessionId, ...parentSession.companyId ? { companyId: parentSession.companyId } : {} },
|
|
196224
|
-
dirName
|
|
196802
|
+
dirName,
|
|
196803
|
+
void 0,
|
|
196804
|
+
known
|
|
196225
196805
|
).catch((err) => {
|
|
196226
196806
|
log3(`[delegation-return] \u4EA7\u7269\u642C\u8FD0\u5931\u8D25\uFF08delegation=${record8.id}\uFF09: ${String(err)}`);
|
|
196227
196807
|
return {
|
|
196228
196808
|
prepared: [],
|
|
196229
196809
|
skipped: [{ source: "outputs/", code: "INTERNAL", message: "\u642C\u8FD0\u5931\u8D25" }],
|
|
196230
196810
|
totalBytes: 0,
|
|
196811
|
+
unchanged: [],
|
|
196231
196812
|
listDirCode: "INTERNAL"
|
|
196232
196813
|
};
|
|
196233
196814
|
});
|
|
@@ -196247,8 +196828,18 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196247
196828
|
const text5 = delegationReturnText(terminal, {
|
|
196248
196829
|
movedPaths: bundleFiles.paths,
|
|
196249
196830
|
skipped: outbound.skipped,
|
|
196250
|
-
recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS
|
|
196831
|
+
recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS,
|
|
196832
|
+
unchangedPaths: outbound.unchanged
|
|
196251
196833
|
});
|
|
196834
|
+
const movedFileRefs = outbound.prepared.map((f2) => ({
|
|
196835
|
+
name: f2.rel,
|
|
196836
|
+
...typeof f2.srcSize === "number" ? { size: f2.srcSize } : {},
|
|
196837
|
+
...f2.srcMtime ? { mtime: f2.srcMtime } : {}
|
|
196838
|
+
}));
|
|
196839
|
+
const fingerprints = {};
|
|
196840
|
+
for (const f2 of movedFileRefs) {
|
|
196841
|
+
if (typeof f2.size === "number" || f2.mtime) fingerprints[f2.name] = { ...typeof f2.size === "number" ? { size: f2.size } : {}, ...f2.mtime ? { mtime: f2.mtime } : {} };
|
|
196842
|
+
}
|
|
196252
196843
|
const source = delegationReturnSource(record8);
|
|
196253
196844
|
const messageId = deriveSystemMessageId(source);
|
|
196254
196845
|
let written;
|
|
@@ -196279,6 +196870,8 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196279
196870
|
const bundle = bundleFiles.paths.length ? {
|
|
196280
196871
|
files: bundleFiles.files,
|
|
196281
196872
|
binaryFiles: bundleFiles.binaryFiles,
|
|
196873
|
+
// queued 那一档要靠它把指纹带到 flush 落账那一刻(`PendingChatAttachment.srcSize`)。
|
|
196874
|
+
fingerprints,
|
|
196282
196875
|
taskLines: [
|
|
196283
196876
|
"",
|
|
196284
196877
|
"## \u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6",
|
|
@@ -196313,6 +196906,8 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196313
196906
|
via: delivery.via,
|
|
196314
196907
|
movedFiles: bundleFiles.paths.length,
|
|
196315
196908
|
movedPaths: bundleFiles.paths,
|
|
196909
|
+
movedFileRefs,
|
|
196910
|
+
unchangedPaths: outbound.unchanged,
|
|
196316
196911
|
skippedFiles: outbound.skipped.length
|
|
196317
196912
|
};
|
|
196318
196913
|
}
|
|
@@ -196324,6 +196919,7 @@ var init_return_flow = __esm({
|
|
|
196324
196919
|
init_chat_system_message();
|
|
196325
196920
|
init_chat_broadcast();
|
|
196326
196921
|
init_files();
|
|
196922
|
+
init_src();
|
|
196327
196923
|
}
|
|
196328
196924
|
});
|
|
196329
196925
|
|
|
@@ -196467,8 +197063,10 @@ function assembleDelegationsDomain(deps) {
|
|
|
196467
197063
|
);
|
|
196468
197064
|
const targetRound = record8.roundSummaries[record8.roundSummaries.length - 1]?.round;
|
|
196469
197065
|
let movedPaths = null;
|
|
197066
|
+
let movedRefs = [];
|
|
196470
197067
|
if (outcome.status === "delivered" && (outcome.via === "turn" || outcome.via === "append") && outcome.movedPaths.length > 0) {
|
|
196471
197068
|
movedPaths = outcome.movedPaths;
|
|
197069
|
+
movedRefs = outcome.movedFileRefs;
|
|
196472
197070
|
}
|
|
196473
197071
|
if (movedPaths === null || targetRound === void 0) return;
|
|
196474
197072
|
const store = await deps.resolveStore(companyId).catch(() => null);
|
|
@@ -196481,7 +197079,7 @@ function assembleDelegationsDomain(deps) {
|
|
|
196481
197079
|
`[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
197080
|
);
|
|
196483
197081
|
} else {
|
|
196484
|
-
outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedPaths);
|
|
197082
|
+
outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedRefs.length ? movedRefs : movedPaths);
|
|
196485
197083
|
if (outcomeCode === "ok") return;
|
|
196486
197084
|
deps.metrics?.recordFilesRegisterFailure?.(outcomeCode);
|
|
196487
197085
|
warn(
|
|
@@ -196495,6 +197093,11 @@ function assembleDelegationsDomain(deps) {
|
|
|
196495
197093
|
chatSessionId: record8.parentSessionId,
|
|
196496
197094
|
delegationRef: { delegationId: record8.id, round: targetRound },
|
|
196497
197095
|
paths: movedPaths,
|
|
197096
|
+
// 补账那一趟同样要带指纹,否则「首次登记失败 → 补账成功」的路径会留下无指纹条目。
|
|
197097
|
+
fingerprints: Object.fromEntries(movedRefs.map((f2) => [f2.name, {
|
|
197098
|
+
...typeof f2.size === "number" ? { size: f2.size } : {},
|
|
197099
|
+
...f2.mtime ? { mtime: f2.mtime } : {}
|
|
197100
|
+
}])),
|
|
196498
197101
|
reason: `immediate-register-failed:${outcomeCode}`
|
|
196499
197102
|
}).catch((err) => {
|
|
196500
197103
|
warn(
|
|
@@ -196517,16 +197120,20 @@ async function registerDelegationFilesOnFlush(deps, row, companyId) {
|
|
|
196517
197120
|
const warn = deps.log ?? ((m2) => console.warn(m2));
|
|
196518
197121
|
const ref2 = row.delegationRef;
|
|
196519
197122
|
if (!ref2) return "no-ref";
|
|
196520
|
-
const
|
|
196521
|
-
if (
|
|
197123
|
+
const rows = (row.attachments ?? []).filter((a) => !!a.path);
|
|
197124
|
+
if (rows.length === 0) return "no-files";
|
|
196522
197125
|
const store = await deps.resolveStore(companyId).catch(() => null);
|
|
196523
197126
|
if (!store) return "failed";
|
|
196524
|
-
const files =
|
|
197127
|
+
const files = rows.map((a) => ({
|
|
197128
|
+
name: a.path,
|
|
197129
|
+
...typeof a.srcSize === "number" ? { size: a.srcSize } : {},
|
|
197130
|
+
...a.srcMtime ? { mtime: a.srcMtime } : {}
|
|
197131
|
+
}));
|
|
196525
197132
|
const outcome = await registerRoundFiles(store, ref2.delegationId, ref2.round, files);
|
|
196526
197133
|
if (outcome !== "ok") {
|
|
196527
197134
|
deps.metrics?.recordFilesRegisterFailure?.(outcome);
|
|
196528
197135
|
warn(
|
|
196529
|
-
`[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${
|
|
197136
|
+
`[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${files.length}`
|
|
196530
197137
|
);
|
|
196531
197138
|
}
|
|
196532
197139
|
return outcome;
|
|
@@ -196549,6 +197156,31 @@ var init_assembly = __esm({
|
|
|
196549
197156
|
}
|
|
196550
197157
|
});
|
|
196551
197158
|
|
|
197159
|
+
// ../server/src/domains/delegations/child-live-turn.ts
|
|
197160
|
+
function registerDelegatedChildTurn(liveChat, childSessionId, session) {
|
|
197161
|
+
const appendInput = session.appendInput;
|
|
197162
|
+
if (typeof appendInput !== "function" || session.canAppendInput === false) return;
|
|
197163
|
+
const ctrl = liveChat.start(childSessionId, {
|
|
197164
|
+
runtimeSessionId: session.id,
|
|
197165
|
+
...session.runId ? { runId: session.runId } : {},
|
|
197166
|
+
kill: () => {
|
|
197167
|
+
void session.kill?.();
|
|
197168
|
+
},
|
|
197169
|
+
appendInput: (input) => appendInput.call(session, input),
|
|
197170
|
+
// **每次现读**,不快照:一轮跑到收尾时 stdin 会先关,那之后 runtime 自己会回
|
|
197171
|
+
// `session-closing`,判断权本就该留在它那儿(同 `/api/chat` 那条路的写法)。
|
|
197172
|
+
get canAppendInput() {
|
|
197173
|
+
return session.canAppendInput !== false;
|
|
197174
|
+
}
|
|
197175
|
+
});
|
|
197176
|
+
void session.done.then(() => ctrl.finish("done"), () => ctrl.finish("error"));
|
|
197177
|
+
}
|
|
197178
|
+
var init_child_live_turn = __esm({
|
|
197179
|
+
"../server/src/domains/delegations/child-live-turn.ts"() {
|
|
197180
|
+
"use strict";
|
|
197181
|
+
}
|
|
197182
|
+
});
|
|
197183
|
+
|
|
196552
197184
|
// ../connectors/src/_base/wrapper-assets.ts
|
|
196553
197185
|
function isUsableWrapper(scriptPath) {
|
|
196554
197186
|
if (!(0, import_node_fs7.existsSync)(scriptPath)) return false;
|
|
@@ -201298,7 +201930,7 @@ function authorizedCommandReceipt(command, args, actor) {
|
|
|
201298
201930
|
case "promote":
|
|
201299
201931
|
return { message: "promoted to persistent" };
|
|
201300
201932
|
case "resolveEscalation":
|
|
201301
|
-
return { message: `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
|
|
201933
|
+
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
201934
|
default:
|
|
201303
201935
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u65E0\u56DE\u6267\u6784\u9020 ${command}`);
|
|
201304
201936
|
}
|
|
@@ -201337,7 +201969,11 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
201337
201969
|
);
|
|
201338
201970
|
}
|
|
201339
201971
|
}
|
|
201972
|
+
const linkedGapId = escalationById(kernel.model, escalationId)?.gapId;
|
|
201973
|
+
const linkedGap = linkedGapId ? (kernel.model.gaps.get(artifactId) ?? []).find((g2) => g2.gapId === linkedGapId) : void 0;
|
|
201974
|
+
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
201975
|
const normalizedArgs = {
|
|
201976
|
+
...linkedGap ? { gapId: linkedGap.gapId, continueExecution: true } : {},
|
|
201341
201977
|
escalationId,
|
|
201342
201978
|
// ← 已四级解析出的具体一条(绝非 undefined、绝非「全清」)
|
|
201343
201979
|
artifactId,
|
|
@@ -201935,10 +202571,10 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
201935
202571
|
const note = optStr(args, "note");
|
|
201936
202572
|
const via = optStr(args, "via");
|
|
201937
202573
|
const target = kernel.model.annotations.get(annotationId);
|
|
201938
|
-
if (target && target.state !== "open") {
|
|
202574
|
+
if (target && target.state !== "open" && resolution !== "acknowledged") {
|
|
201939
202575
|
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
202576
|
}
|
|
201941
|
-
await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {} });
|
|
202577
|
+
await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {}, ...ctx.sessionArtifactId ? { viaNode: ctx.sessionArtifactId } : {} });
|
|
201942
202578
|
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
202579
|
}
|
|
201944
202580
|
case "gap": {
|
|
@@ -202489,7 +203125,15 @@ async function startOasisServer(opts) {
|
|
|
202489
203125
|
resolveStore: opts.delegationStoreFor,
|
|
202490
203126
|
resolveChatSessions: resolveChatSessionsForDelegation,
|
|
202491
203127
|
resolveActors: async (companyId) => (await actorsDomain2.resolveCtx(companyId)).service,
|
|
202492
|
-
|
|
203128
|
+
// **派发完顺手把子会话这一轮登记进 live 注册表**(v2 bug 0088)。缺这一步时下面那行
|
|
203129
|
+
// `appendToChild` 恒回 `no-live-turn`——注册表里从来没有以 `childSessionId` 为键的轮,
|
|
203130
|
+
// 于是每一条 `--continue` 转达都落队列,专家要等本轮跑完才看见「手上这段作废」。
|
|
203131
|
+
// 理由与两条纪律见 `child-live-turn.ts` 的文件头。
|
|
203132
|
+
dispatchChat: async (request2) => {
|
|
203133
|
+
const session = await dispatchChat(request2);
|
|
203134
|
+
registerDelegatedChildTurn(liveChat, request2.chatSessionId, session);
|
|
203135
|
+
return session;
|
|
203136
|
+
},
|
|
202493
203137
|
appendToChild: (childSessionId, text5, extra) => liveChat.append(childSessionId, text5, extra),
|
|
202494
203138
|
// 用 lambda 转一层:`broadcastDepsFor` 在本文件里声明得更靠后(回流是异步发生的,
|
|
202495
203139
|
// 那时它早就赋过值了)。直接把值递进去会撞 TDZ。
|
|
@@ -202730,16 +203374,25 @@ async function startOasisServer(opts) {
|
|
|
202730
203374
|
...store?.enqueuePendingMessage ? {
|
|
202731
203375
|
enqueuePending: async (input) => {
|
|
202732
203376
|
const bundle = input.bundle;
|
|
203377
|
+
const fp = (p2) => {
|
|
203378
|
+
const f2 = bundle?.fingerprints?.[p2];
|
|
203379
|
+
return {
|
|
203380
|
+
...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
|
|
203381
|
+
...f2?.mtime ? { srcMtime: f2.mtime } : {}
|
|
203382
|
+
};
|
|
203383
|
+
};
|
|
202733
203384
|
const attachments = bundle ? [
|
|
202734
203385
|
...Object.entries(bundle.files ?? {}).map(([p2, text5]) => ({
|
|
202735
203386
|
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
202736
203387
|
text: text5,
|
|
202737
|
-
path: p2
|
|
203388
|
+
path: p2,
|
|
203389
|
+
...fp(p2)
|
|
202738
203390
|
})),
|
|
202739
203391
|
...Object.entries(bundle.binaryFiles ?? {}).map(([p2, b64]) => ({
|
|
202740
203392
|
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
202741
203393
|
bytesBase64: b64,
|
|
202742
|
-
path: p2
|
|
203394
|
+
path: p2,
|
|
203395
|
+
...fp(p2)
|
|
202743
203396
|
}))
|
|
202744
203397
|
] : [];
|
|
202745
203398
|
const { randomUUID: randomUUID40 } = await import("node:crypto");
|
|
@@ -202769,7 +203422,15 @@ async function startOasisServer(opts) {
|
|
|
202769
203422
|
// ——生产 `deliverToAgent:512` 会开轮/append,用户听到空消息且下一场 UPDATE 再把已完成
|
|
202770
203423
|
// 的行改回 delivering,违背「只补账、不投递」语义。
|
|
202771
203424
|
enqueueRegisterPending: async (input) => {
|
|
202772
|
-
const attachments = input.paths.map((p2) =>
|
|
203425
|
+
const attachments = input.paths.map((p2) => {
|
|
203426
|
+
const f2 = input.fingerprints?.[p2];
|
|
203427
|
+
return {
|
|
203428
|
+
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
203429
|
+
path: p2,
|
|
203430
|
+
...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
|
|
203431
|
+
...f2?.mtime ? { srcMtime: f2.mtime } : {}
|
|
203432
|
+
};
|
|
203433
|
+
});
|
|
202773
203434
|
const { randomUUID: randomUUID40 } = await import("node:crypto");
|
|
202774
203435
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
202775
203436
|
try {
|
|
@@ -203203,6 +203864,22 @@ async function startOasisServer(opts) {
|
|
|
203203
203864
|
}));
|
|
203204
203865
|
return;
|
|
203205
203866
|
}
|
|
203867
|
+
if (url.pathname.startsWith("/api/files/")) {
|
|
203868
|
+
const company = opts.resolveCompanyContext ? await opts.resolveCompanyContext(actor, req.headers, url.pathname) : void 0;
|
|
203869
|
+
if (company && company.kind !== "ok") {
|
|
203870
|
+
res.writeHead(403, { "content-type": "application/json" }).end(JSON.stringify({
|
|
203871
|
+
error: { code: "FORBIDDEN", message: "\u65E0\u6743\u8BFB\u53D6\u8BE5\u516C\u53F8\u7684\u6587\u4EF6" }
|
|
203872
|
+
}));
|
|
203873
|
+
return;
|
|
203874
|
+
}
|
|
203875
|
+
await serveFileRequest(
|
|
203876
|
+
req,
|
|
203877
|
+
res,
|
|
203878
|
+
url,
|
|
203879
|
+
await resolveEngine(company?.kind === "ok" ? company.companyId : void 0)
|
|
203880
|
+
);
|
|
203881
|
+
return;
|
|
203882
|
+
}
|
|
203206
203883
|
if (url.pathname === "/api/whoami" && req.method === "GET") {
|
|
203207
203884
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ actor }));
|
|
203208
203885
|
return;
|
|
@@ -204909,6 +205586,22 @@ ${composed}`;
|
|
|
204909
205586
|
const itemLedger = new ChatItemLedger({
|
|
204910
205587
|
...itemStore ? { items: itemStore } : {},
|
|
204911
205588
|
versionSeed,
|
|
205589
|
+
/* 用户那条消息一落库就推上 v3 流(ADR-0510 D2 的收口)。
|
|
205590
|
+
不推的话:乐观条没有服务端号、被钉在列表末尾,而 agent 的正文带着更大的号从
|
|
205591
|
+
直播流到达、进「有号」段 ⇒ **回复显示在用户消息上面**,直到下一次快照才跳回去
|
|
205592
|
+
(2026-09-09 用户现场)。这里把窗口从「等下一次快照」压到「一次 INSERT 往返」。
|
|
205593
|
+
只推 user:assistant 的每一条本来就走 `emitV3`。 */
|
|
205594
|
+
onPersisted: (info) => {
|
|
205595
|
+
if (info.role !== "user") return;
|
|
205596
|
+
opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
|
|
205597
|
+
itemId: info.itemId,
|
|
205598
|
+
ord: info.ord,
|
|
205599
|
+
version: info.version,
|
|
205600
|
+
text: info.text,
|
|
205601
|
+
...clientSubmitId ? { clientSubmitId } : {},
|
|
205602
|
+
...effectiveAttachments.length ? { attachments: effectiveAttachments } : {}
|
|
205603
|
+
});
|
|
205604
|
+
},
|
|
204912
205605
|
sessionId: persistTarget?.id ?? session.id,
|
|
204913
205606
|
// 优先账本 id(`chat_session_turns.id`,跨进程唯一、重启不失忆);账本没接入才兜底。
|
|
204914
205607
|
turnId: heldTurn?.id ?? fallbackTurnId(session.runId),
|
|
@@ -206219,6 +206912,7 @@ var init_server3 = __esm({
|
|
|
206219
206912
|
init_src();
|
|
206220
206913
|
init_src();
|
|
206221
206914
|
init_src();
|
|
206915
|
+
init_file_http();
|
|
206222
206916
|
init_build_info();
|
|
206223
206917
|
init_artifact_content2();
|
|
206224
206918
|
init_command_policy();
|
|
@@ -206260,6 +206954,7 @@ var init_server3 = __esm({
|
|
|
206260
206954
|
init_workorder_terminal();
|
|
206261
206955
|
init_knowledge2();
|
|
206262
206956
|
init_assembly();
|
|
206957
|
+
init_child_live_turn();
|
|
206263
206958
|
init_src8();
|
|
206264
206959
|
init_continuation();
|
|
206265
206960
|
githubAppPending = new PendingAppCreations();
|
|
@@ -209346,6 +210041,27 @@ function foldSingleNodeTasks(items) {
|
|
|
209346
210041
|
}
|
|
209347
210042
|
return folded;
|
|
209348
210043
|
}
|
|
210044
|
+
function fillFolderSizes(items) {
|
|
210045
|
+
const folders = items.filter((i) => i.kind === "folder" && i.path);
|
|
210046
|
+
if (folders.length === 0) return;
|
|
210047
|
+
const files = items.filter((i) => i.kind !== "folder" && i.path);
|
|
210048
|
+
for (const folder of folders) {
|
|
210049
|
+
const prefix = `${folder.path}/`;
|
|
210050
|
+
let sum = 0;
|
|
210051
|
+
let seen = 0;
|
|
210052
|
+
let complete = true;
|
|
210053
|
+
for (const file of files) {
|
|
210054
|
+
if (!file.path.startsWith(prefix)) continue;
|
|
210055
|
+
seen += 1;
|
|
210056
|
+
if (file.size == null) {
|
|
210057
|
+
complete = false;
|
|
210058
|
+
break;
|
|
210059
|
+
}
|
|
210060
|
+
sum += file.size;
|
|
210061
|
+
}
|
|
210062
|
+
folder.size = seen > 0 && complete ? sum : null;
|
|
210063
|
+
}
|
|
210064
|
+
}
|
|
209349
210065
|
function engineContentKind(k2) {
|
|
209350
210066
|
return k2 === "manifest" ? "manifest" : k2 === "external" ? "external-pin" : "inline-blob";
|
|
209351
210067
|
}
|
|
@@ -210243,6 +210959,7 @@ var init_service4 = __esm({
|
|
|
210243
210959
|
const projectByWorkOrder = new Map(bindings.map((b2) => [b2.workOrderId, b2.projectId]));
|
|
210244
210960
|
const workorderSummaries = await this.listProjectWorkorders(projectId2);
|
|
210245
210961
|
const workorderTitleById = new Map(workorderSummaries.map((w2) => [w2.id, w2.title]));
|
|
210962
|
+
const workorderOwnerById = new Map(workorderSummaries.map((w2) => [w2.id, w2.owner?.id ?? null]));
|
|
210246
210963
|
const engineRows = await this.collectEngineFilesForProject(
|
|
210247
210964
|
projectId2,
|
|
210248
210965
|
workorderSummaries.map((w2) => w2.id),
|
|
@@ -210261,6 +210978,8 @@ var init_service4 = __esm({
|
|
|
210261
210978
|
const taskId = record8.createdFromWorkOrderId ?? artifact?.workspace ?? null;
|
|
210262
210979
|
const taskTitle = taskId ? workorderTitleById.get(taskId) ?? null : null;
|
|
210263
210980
|
const creatorId = revision.author || null;
|
|
210981
|
+
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
210982
|
+
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210264
210983
|
const folderPath = taskId ? `wo:${taskId}/node:${record8.artifactId}` : null;
|
|
210265
210984
|
if (folderPath) items.push({
|
|
210266
210985
|
kind: "folder",
|
|
@@ -210273,13 +210992,14 @@ var init_service4 = __esm({
|
|
|
210273
210992
|
size: null,
|
|
210274
210993
|
taskId,
|
|
210275
210994
|
taskTitle,
|
|
210276
|
-
creatorKind
|
|
210277
|
-
creatorId
|
|
210278
|
-
creatorName
|
|
210995
|
+
creatorKind,
|
|
210996
|
+
creatorId,
|
|
210997
|
+
creatorName,
|
|
210279
210998
|
updatedAt: record8.updatedAt
|
|
210280
210999
|
});
|
|
210281
211000
|
const content3 = revision.contentKind === "manifest" ? await this.readContentRefText(revision.contentRef) : null;
|
|
210282
211001
|
const files2 = content3 !== null ? tryParseManifestFiles(content3) : void 0;
|
|
211002
|
+
const wholeSize = files2?.length ? null : content3 !== null ? Buffer.byteLength(content3, "utf8") : await this.contentByteSize(revision.contentRef, revision.contentKind);
|
|
210283
211003
|
for (const file of files2?.length ? files2 : [null]) {
|
|
210284
211004
|
items.push({
|
|
210285
211005
|
kind: "artifact",
|
|
@@ -210289,15 +211009,15 @@ var init_service4 = __esm({
|
|
|
210289
211009
|
filePath: file?.path ?? null,
|
|
210290
211010
|
depth: folderPath ? 2 : 0,
|
|
210291
211011
|
type: record8.type,
|
|
210292
|
-
size: file
|
|
211012
|
+
size: file ? file.size ?? null : wholeSize,
|
|
210293
211013
|
taskId,
|
|
210294
211014
|
taskTitle,
|
|
210295
211015
|
// 内容形态/引用只挂在**整份产物**那一行;装箱单摊出来的成员行有自己的后缀,
|
|
210296
211016
|
// 按后缀走图标即可,挂上 manifest 反而会把 `Dockerfile` 这种无后缀成员画成压缩包。
|
|
210297
211017
|
...file ? {} : { contentKind: revision.contentKind, contentRef: revision.contentRef },
|
|
210298
|
-
creatorKind
|
|
211018
|
+
creatorKind,
|
|
210299
211019
|
creatorId,
|
|
210300
|
-
creatorName
|
|
211020
|
+
creatorName,
|
|
210301
211021
|
updatedAt: record8.updatedAt
|
|
210302
211022
|
});
|
|
210303
211023
|
}
|
|
@@ -210309,6 +211029,7 @@ var init_service4 = __esm({
|
|
|
210309
211029
|
if (foldedTasks.has(item.taskId)) continue;
|
|
210310
211030
|
taskFolders.add(item.taskId);
|
|
210311
211031
|
const path41 = `wo:${item.taskId}`;
|
|
211032
|
+
const ownerId = workorderOwnerById.get(item.taskId) ?? null;
|
|
210312
211033
|
items.push({
|
|
210313
211034
|
kind: "folder",
|
|
210314
211035
|
id: `folder:${path41}`,
|
|
@@ -210320,9 +211041,9 @@ var init_service4 = __esm({
|
|
|
210320
211041
|
size: null,
|
|
210321
211042
|
taskId: item.taskId,
|
|
210322
211043
|
taskTitle: item.taskTitle,
|
|
210323
|
-
creatorKind: null,
|
|
210324
|
-
creatorId:
|
|
210325
|
-
creatorName: null,
|
|
211044
|
+
creatorKind: ownerId ? ownerId.startsWith("actor:human:") ? "human" : "agent" : null,
|
|
211045
|
+
creatorId: ownerId,
|
|
211046
|
+
creatorName: ownerId ? this.resolveActorName(ownerId) : null,
|
|
210326
211047
|
updatedAt: item.updatedAt
|
|
210327
211048
|
});
|
|
210328
211049
|
}
|
|
@@ -210348,6 +211069,7 @@ var init_service4 = __esm({
|
|
|
210348
211069
|
updatedAt: file.uploadedAt
|
|
210349
211070
|
});
|
|
210350
211071
|
}
|
|
211072
|
+
fillFolderSizes(items);
|
|
210351
211073
|
items.sort((a, b2) => {
|
|
210352
211074
|
const pathA = a.path ?? "";
|
|
210353
211075
|
const pathB = b2.path ?? "";
|
|
@@ -210391,6 +211113,9 @@ var init_service4 = __esm({
|
|
|
210391
211113
|
const work = worksById.get(acceptedId);
|
|
210392
211114
|
const folderPath = `wo:${workOrderId}/node:${node2.id}`;
|
|
210393
211115
|
const folderUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
|
|
211116
|
+
const creatorId = work?.assigneeActorId ?? null;
|
|
211117
|
+
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
211118
|
+
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210394
211119
|
out.push({
|
|
210395
211120
|
kind: "folder",
|
|
210396
211121
|
id: `folder:${folderPath}`,
|
|
@@ -210402,14 +211127,11 @@ var init_service4 = __esm({
|
|
|
210402
211127
|
size: null,
|
|
210403
211128
|
taskId: workOrderId,
|
|
210404
211129
|
taskTitle,
|
|
210405
|
-
creatorKind
|
|
210406
|
-
creatorId
|
|
210407
|
-
creatorName
|
|
211130
|
+
creatorKind,
|
|
211131
|
+
creatorId,
|
|
211132
|
+
creatorName,
|
|
210408
211133
|
updatedAt: folderUpdatedAt
|
|
210409
211134
|
});
|
|
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
211135
|
const sortedArts = arts.slice().sort((a, b2) => a.ordinal - b2.ordinal);
|
|
210414
211136
|
for (const a of sortedArts) {
|
|
210415
211137
|
const artifactId = node2.id;
|
|
@@ -210448,7 +211170,7 @@ var init_service4 = __esm({
|
|
|
210448
211170
|
filePath: null,
|
|
210449
211171
|
depth: 2,
|
|
210450
211172
|
type: artifactType || "",
|
|
210451
|
-
size:
|
|
211173
|
+
size: await this.contentByteSize(a.contentRef, engineContentKind(a.contentKind)),
|
|
210452
211174
|
contentKind: engineContentKind(a.contentKind),
|
|
210453
211175
|
...a.contentRef ? { contentRef: a.contentRef } : {},
|
|
210454
211176
|
taskId: workOrderId,
|
|
@@ -210867,6 +211589,19 @@ var init_service4 = __esm({
|
|
|
210867
211589
|
}
|
|
210868
211590
|
return { ...withKind, size: Buffer.byteLength(content3, "utf8") };
|
|
210869
211591
|
}
|
|
211592
|
+
/**
|
|
211593
|
+
* 一份产物正文的字节数——**与任务页「相关产物」共用同一条判据**(见 {@link enrichNodeOutputDocument}):
|
|
211594
|
+
* 不看 `contentKind` 名义,只看正文能不能从 BlobStore 读到;读不到就是 null。
|
|
211595
|
+
*
|
|
211596
|
+
* 于是 git 提交号 / Figma version 这类真·外部钉、未纳管、`empty` 都诚实无大小(前端显「—」),
|
|
211597
|
+
* 而 `inline-blob` 的任务书、PRD、ADR 会报出真实字节数——此前 files-view 这条路径**恒发 null**,
|
|
211598
|
+
* 同一份任务书在任务页有 8.0 KB、在项目页却是「—」。
|
|
211599
|
+
*/
|
|
211600
|
+
async contentByteSize(contentRef, contentKind) {
|
|
211601
|
+
if (contentKind === "empty" || !contentRef?.trim()) return null;
|
|
211602
|
+
const content3 = await this.readContentRefText(contentRef);
|
|
211603
|
+
return content3 === null ? null : Buffer.byteLength(content3, "utf8");
|
|
211604
|
+
}
|
|
210870
211605
|
/** 正文读取的唯一入口:只认 BlobStore;没配 / 没纳管 / 读失败一律 null(调用方据此诚实缺 size)。 */
|
|
210871
211606
|
async readContentRefText(contentRef) {
|
|
210872
211607
|
if (!this.blobs) return null;
|
|
@@ -217756,6 +218491,31 @@ ${input.description}
|
|
|
217756
218491
|
listActorConnectorConnections(actorId) {
|
|
217757
218492
|
return this.opts.store.listActorConnectorConnections(actorId);
|
|
217758
218493
|
}
|
|
218494
|
+
/**
|
|
218495
|
+
* 移除某员工与某连接器的连接(组织页连接器详情的「移除连接」,2026-09-09 原型 1600:5765)。
|
|
218496
|
+
*
|
|
218497
|
+
* 删两样东西,缺一不可:
|
|
218498
|
+
* ① 这名员工**自己**那份连接器凭据(`scope=personal` 且 `connectorId` 命中的变量行)——
|
|
218499
|
+
* 不删的话,「移除」之后 `actorConnected` 里还有它,卡片照样在,读作「删不掉」;
|
|
218500
|
+
* ② 「员工×连接器」的连接记录——不删的话 `effective` 里还有它(enabled=false),
|
|
218501
|
+
* 卡片变成一张「已停用」的僵尸卡,而人要的是它消失。
|
|
218502
|
+
*
|
|
218503
|
+
* **不碰组织那条 connector 行**(决策 0080 修订五的同一条理由):组织级连接是别人配的资产,
|
|
218504
|
+
* 一名员工点「移除连接」不该把全组织的连接拆掉。组织级的断开在「管理 > 连接器」。
|
|
218505
|
+
* 于是:组织已连接时,移除个人连接后这名员工会**回落到组织默认**——卡片仍在,但身份那一行
|
|
218506
|
+
* 变回组织账号。这是对的,不是没删干净。
|
|
218507
|
+
*
|
|
218508
|
+
* 幂等:没有个人凭据、没有记录时照样返回成功(`variablesDeleted: 0`)——重复点、并发点都不该报错。
|
|
218509
|
+
*/
|
|
218510
|
+
async removeActorConnector(actorId, connectorId) {
|
|
218511
|
+
const rows = await this.opts.store.listVariables(actorId);
|
|
218512
|
+
const mine = rows.filter(
|
|
218513
|
+
(v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId === connectorId
|
|
218514
|
+
);
|
|
218515
|
+
for (const v2 of mine) await this.opts.store.deleteVariable(v2.key, actorId);
|
|
218516
|
+
await this.opts.store.deleteActorConnectorConnection(actorId, connectorId);
|
|
218517
|
+
return { removed: true, variablesDeleted: mine.length };
|
|
218518
|
+
}
|
|
217759
218519
|
/**
|
|
217760
218520
|
* 员工连接器面板所需状态:连接记录 + 组织级已连接集合 + 该员工已授权(有个人 connector 变量)集合。
|
|
217761
218521
|
* 前端据此算每个 connector 的 hasRecord / 有效 enabled / connected(actor 或 global)。
|
|
@@ -218555,15 +219315,28 @@ var init_model_pricing = __esm({
|
|
|
218555
219315
|
"gpt-4.1-nano": usd(0.1, 0.4, 0.025),
|
|
218556
219316
|
"gpt-4.1-nano-2025-04-14": usd(0.1, 0.4, 0.025),
|
|
218557
219317
|
"o4-mini": usd(1.1, 4.4, 0.275),
|
|
219318
|
+
// o3 / o3-mini 在 codex 静态目录里可选,却一直不在价目快照里(费用记 0)。
|
|
219319
|
+
// 这两行按 OpenAI 公开价填,不是智增增快照里的行;供应商价目表同步到时以 DB 为准。
|
|
219320
|
+
"o3": usd(2, 8),
|
|
219321
|
+
"o3-mini": usd(1.1, 4.4),
|
|
219322
|
+
"claude-fable-5-1": usd(10, 50),
|
|
218558
219323
|
"claude-fable-5": usd(10, 50),
|
|
219324
|
+
"claude-mythos-5-1": usd(10, 50),
|
|
218559
219325
|
"claude-mythos-5": usd(10, 50),
|
|
219326
|
+
// claude-opus-5 是 Claude Code 当代默认款,却一直不在这张快照里:查不到价 → 费用记 0
|
|
219327
|
+
// (与 2026-09-04 那次 `claude-opus-5[1m]` 记 0 同一类坑)。按 Opus 档 $5/$25 补上。
|
|
219328
|
+
"claude-opus-5": usd(5, 25),
|
|
218560
219329
|
"claude-opus-4-8": usd(5, 25),
|
|
218561
219330
|
"claude-opus-4-7": usd(5, 25),
|
|
218562
219331
|
"claude-sonnet-5": usd(2, 10),
|
|
218563
219332
|
"claude-sonnet-4-6": usd(3, 15),
|
|
218564
219333
|
"claude-opus-4-6": usd(5, 25),
|
|
218565
219334
|
"claude-opus-4-5-20251101": usd(5, 25),
|
|
219335
|
+
"claude-opus-4-5": usd(5, 25),
|
|
218566
219336
|
"claude-sonnet-4-5-20250929": usd(3, 15),
|
|
219337
|
+
// 短名同样要有:前缀比对是「表里的键是模型名的前缀」,带日期的那行匹配不上不带日期的短名。
|
|
219338
|
+
// claude-sonnet-4-5 从 2026-07-04 起就在目录里可选,却一直查不到价(费用记 0),本次一并补。
|
|
219339
|
+
"claude-sonnet-4-5": usd(3, 15),
|
|
218567
219340
|
"claude-opus-4-1-20250805": usd(15, 75),
|
|
218568
219341
|
"claude-opus-4-20250514": usd(15, 75),
|
|
218569
219342
|
"claude-sonnet-4-20250514": usd(3, 15),
|
|
@@ -218572,6 +219345,14 @@ var init_model_pricing = __esm({
|
|
|
218572
219345
|
"claude-3-5-sonnet-20240620": usd(3, 15),
|
|
218573
219346
|
"claude-3-sonnet-20240229": usd(3, 15),
|
|
218574
219347
|
"claude-haiku-4-5-20251001": usd(1, 5),
|
|
219348
|
+
// codebuddy 目录用点号写法(claude-sonnet-4.6),与上面的连字符写法是同一批模型,
|
|
219349
|
+
// 但字符串对不上就查不到价 → 记 0。同价另起别名行。
|
|
219350
|
+
"claude-sonnet-4.6": usd(3, 15),
|
|
219351
|
+
"claude-opus-4.7": usd(5, 25),
|
|
219352
|
+
"gemini-3.1-pro": usd(2, 12),
|
|
219353
|
+
"gemini-3-pro-preview": usd(2, 12),
|
|
219354
|
+
// Claude Code 回报的可能是不带日期的短名,短名匹配不上带日期的那行(前缀比对是「表里的键是模型名的前缀」)。
|
|
219355
|
+
"claude-haiku-4-5": usd(1, 5),
|
|
218575
219356
|
"gemini-3.5-flash": usd(1.5, 9),
|
|
218576
219357
|
"gemini-3.1-pro-preview": usd(2, 12),
|
|
218577
219358
|
"gemini-3.1-pro-preview-customtools": usd(2, 12),
|
|
@@ -219705,6 +220486,11 @@ function actorsDomain(opts) {
|
|
|
219705
220486
|
const conn = await service.setActorConnectorEnabled(req.params.id, req.params.connectorId, b2.enabled);
|
|
219706
220487
|
return { status: 200, body: conn };
|
|
219707
220488
|
});
|
|
220489
|
+
router.delete("/api/actors/:id/connectors/:connectorId", async (req) => {
|
|
220490
|
+
const { service } = await resolveCtx(req.auth.companyId);
|
|
220491
|
+
const r = await service.removeActorConnector(req.params.id, req.params.connectorId);
|
|
220492
|
+
return { status: 200, body: r };
|
|
220493
|
+
});
|
|
219708
220494
|
const requireVariableManager = (req) => {
|
|
219709
220495
|
if (!isHumanActor(req.auth.actor)) {
|
|
219710
220496
|
throw new ApiError(
|
|
@@ -223996,11 +224782,13 @@ async function resolveRuntimeModels(deps, runtimeId) {
|
|
|
223996
224782
|
const rt = (await deps.nodeStore.listRuntimes()).find((r) => r.id === runtimeId);
|
|
223997
224783
|
if (!rt) return [];
|
|
223998
224784
|
try {
|
|
223999
|
-
const
|
|
224000
|
-
|
|
224785
|
+
const reported = deps.nodeReportedModels?.(rt.nodeId, rt.kind);
|
|
224786
|
+
const [discovered, overrides] = await Promise.all([
|
|
224787
|
+
// 节点报过就不必再在控制面上白 spawn 一次(跨机时那次 spawn 注定 ENOENT)。
|
|
224788
|
+
reported && reported.length > 0 ? Promise.resolve(reported) : listModels(rt.kind, rt.binary),
|
|
224001
224789
|
deps.registry.listRuntimeModelOverrides(runtimeId)
|
|
224002
224790
|
]);
|
|
224003
|
-
return applyModelOverrides(
|
|
224791
|
+
return applyModelOverrides(discovered, overrides);
|
|
224004
224792
|
} catch {
|
|
224005
224793
|
const overrides = await deps.registry.listRuntimeModelOverrides(runtimeId).catch(() => []);
|
|
224006
224794
|
return applyModelOverrides([], overrides);
|
|
@@ -224392,6 +225180,21 @@ function nodesDomain(deps) {
|
|
|
224392
225180
|
if (!rt) return { status: 404, body: { error: "runtime not found" } };
|
|
224393
225181
|
return { status: 200, body: { items: await resolveRuntimeModels(deps, runtimeId) } };
|
|
224394
225182
|
});
|
|
225183
|
+
router.post("/api/runtimes/:runtimeId/models/refresh", async (req) => {
|
|
225184
|
+
const runtimeId = req.params["runtimeId"];
|
|
225185
|
+
if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
|
|
225186
|
+
const rt = (await deps.nodeStore.listRuntimes()).find((r) => r.id === runtimeId);
|
|
225187
|
+
if (!rt) return { status: 404, body: { error: "runtime not found" } };
|
|
225188
|
+
const outcome = deps.refreshNodeModels ? await deps.refreshNodeModels(rt.nodeId) : { ok: false, reason: "\u672C\u90E8\u7F72\u6CA1\u6709\u8282\u70B9\u7F51\u5173\uFF0C\u62FF\u4E0D\u5230\u8FD9\u53F0\u673A\u5668\u4E0A\u7684\u771F\u5B9E\u6E05\u5355" };
|
|
225189
|
+
return {
|
|
225190
|
+
status: 200,
|
|
225191
|
+
body: {
|
|
225192
|
+
items: await resolveRuntimeModels(deps, runtimeId),
|
|
225193
|
+
refreshed: outcome.ok,
|
|
225194
|
+
...outcome.reason ? { reason: outcome.reason } : {}
|
|
225195
|
+
}
|
|
225196
|
+
};
|
|
225197
|
+
});
|
|
224395
225198
|
router.get("/api/runtimes/:runtimeId/model-overrides", async (req) => {
|
|
224396
225199
|
const runtimeId = req.params["runtimeId"];
|
|
224397
225200
|
if (!runtimeId) return { status: 400, body: { error: "missing runtimeId" } };
|
|
@@ -224806,6 +225609,146 @@ var init_node_store = __esm({
|
|
|
224806
225609
|
}
|
|
224807
225610
|
});
|
|
224808
225611
|
|
|
225612
|
+
// ../server/src/domains/collab/review-activity.ts
|
|
225613
|
+
function reviewCardId(targetWorkId, reviewerActorId) {
|
|
225614
|
+
return `${REVIEW_CARD_PREFIX}${encodeURIComponent(targetWorkId)}:${encodeURIComponent(reviewerActorId)}`;
|
|
225615
|
+
}
|
|
225616
|
+
function activityReviews(snap, events) {
|
|
225617
|
+
const rows = /* @__PURE__ */ new Map();
|
|
225618
|
+
for (const r of snap.reviews) rows.set(r.id, { ...r });
|
|
225619
|
+
const authoritative = new Set(snap.reviews.filter((r) => r.status).map((r) => r.id));
|
|
225620
|
+
for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
|
|
225621
|
+
const e = rec.event;
|
|
225622
|
+
if (e.kind === "review.create" && !rows.has(e.reviewId)) {
|
|
225623
|
+
rows.set(e.reviewId, {
|
|
225624
|
+
id: e.reviewId,
|
|
225625
|
+
workorderId: snap.workorder.id,
|
|
225626
|
+
nodeId: e.nodeId,
|
|
225627
|
+
targetWorkId: e.targetWorkId,
|
|
225628
|
+
reviewerActorId: e.reviewerActorId,
|
|
225629
|
+
reviewGroup: e.reviewGroup ?? e.reviewerActorId,
|
|
225630
|
+
createdAt: rec.createdAt,
|
|
225631
|
+
startedAt: null,
|
|
225632
|
+
endedAt: null,
|
|
225633
|
+
cancelledAt: null,
|
|
225634
|
+
verdict: null,
|
|
225635
|
+
note: null,
|
|
225636
|
+
handActorId: null,
|
|
225637
|
+
decidedAt: null,
|
|
225638
|
+
sessionRef: null
|
|
225639
|
+
});
|
|
225640
|
+
}
|
|
225641
|
+
if (e.kind === "review.create" && !authoritative.has(e.reviewId)) {
|
|
225642
|
+
const r = rows.get(e.reviewId);
|
|
225643
|
+
Object.assign(r, {
|
|
225644
|
+
nodeId: e.nodeId,
|
|
225645
|
+
targetWorkId: e.targetWorkId,
|
|
225646
|
+
reviewerActorId: e.reviewerActorId,
|
|
225647
|
+
reviewGroup: e.reviewGroup ?? r.reviewGroup ?? e.reviewerActorId,
|
|
225648
|
+
createdAt: r.createdAt ?? rec.createdAt
|
|
225649
|
+
});
|
|
225650
|
+
}
|
|
225651
|
+
if (!e.kind.startsWith("review.")) continue;
|
|
225652
|
+
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)) : [];
|
|
225653
|
+
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;
|
|
225654
|
+
for (const r of matches) {
|
|
225655
|
+
if (!r || authoritative.has(r.id)) continue;
|
|
225656
|
+
if (e.kind === "review.started") r.startedAt = rec.createdAt;
|
|
225657
|
+
if (e.kind === "review.timeout") {
|
|
225658
|
+
r.endedAt = rec.createdAt;
|
|
225659
|
+
}
|
|
225660
|
+
if (e.kind === "review.kill") r.cancelledAt = rec.createdAt;
|
|
225661
|
+
if (e.kind === "review.response") {
|
|
225662
|
+
r.verdict = e.verdict;
|
|
225663
|
+
r.note = e.note ?? null;
|
|
225664
|
+
r.decidedAt = rec.createdAt;
|
|
225665
|
+
r.endedAt = rec.createdAt;
|
|
225666
|
+
r.handActorId = e.handActorId ?? null;
|
|
225667
|
+
}
|
|
225668
|
+
}
|
|
225669
|
+
}
|
|
225670
|
+
const createdSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
|
|
225671
|
+
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));
|
|
225672
|
+
}
|
|
225673
|
+
function reviewCardTarget(cardId, reviews) {
|
|
225674
|
+
if (cardId.startsWith(REVIEW_CARD_PREFIX)) {
|
|
225675
|
+
const encoded = cardId.slice(REVIEW_CARD_PREFIX.length);
|
|
225676
|
+
const separator = encoded.indexOf(":");
|
|
225677
|
+
if (separator < 1 || separator === encoded.length - 1) return void 0;
|
|
225678
|
+
try {
|
|
225679
|
+
return {
|
|
225680
|
+
targetWorkId: decodeURIComponent(encoded.slice(0, separator)),
|
|
225681
|
+
reviewerActorId: decodeURIComponent(encoded.slice(separator + 1))
|
|
225682
|
+
};
|
|
225683
|
+
} catch {
|
|
225684
|
+
return void 0;
|
|
225685
|
+
}
|
|
225686
|
+
}
|
|
225687
|
+
if (cardId.startsWith("review:")) {
|
|
225688
|
+
const review = reviews.find((r) => r.id === cardId.slice(7));
|
|
225689
|
+
if (review) return { targetWorkId: review.targetWorkId, reviewerActorId: review.reviewerActorId };
|
|
225690
|
+
}
|
|
225691
|
+
return void 0;
|
|
225692
|
+
}
|
|
225693
|
+
function latestActivityReviews(reviews, events) {
|
|
225694
|
+
const createSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
|
|
225695
|
+
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));
|
|
225696
|
+
const latest = /* @__PURE__ */ new Map();
|
|
225697
|
+
for (const r of rows) latest.set(JSON.stringify([r.reviewGroup, r.reviewerActorId]), r);
|
|
225698
|
+
return [...latest.values()];
|
|
225699
|
+
}
|
|
225700
|
+
function reviewLabel(r) {
|
|
225701
|
+
const state = reviewState(r);
|
|
225702
|
+
if (state === "accept") return "\u5BA1\u6838\u901A\u8FC7";
|
|
225703
|
+
if (state === "reject") return "\u5BA1\u6838\u4E0D\u901A\u8FC7";
|
|
225704
|
+
if (state === "dead") return "\u5BA1\u6838\u5DF2\u505C\u6B62";
|
|
225705
|
+
if (state === "failed") return "\u5BA1\u6838\u8FD0\u884C\u5931\u8D25";
|
|
225706
|
+
if (state === "retry") return "\u6B63\u5728\u91CD\u8BD5\u5BA1\u6838";
|
|
225707
|
+
return r.reviewerActorId.startsWith("actor:human:") ? "\u5F85\u5BA1\u6838" : "\u6B63\u5728\u5BA1\u6838";
|
|
225708
|
+
}
|
|
225709
|
+
function reviewSummary(snap, reviews, events) {
|
|
225710
|
+
const first = reviews[0];
|
|
225711
|
+
const work = snap.works.find((w2) => w2.id === first.targetWorkId);
|
|
225712
|
+
const node2 = snap.nodes.find((n) => n.id === first.nodeId);
|
|
225713
|
+
const name = node2?.title ?? first.nodeId;
|
|
225714
|
+
const latest = latestActivityReviews(reviews, events);
|
|
225715
|
+
const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
|
|
225716
|
+
const judgement = judgeWork({ requirements: requirements.length ? requirements : reviews, reviews });
|
|
225717
|
+
const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
|
|
225718
|
+
if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
|
|
225719
|
+
return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
|
|
225720
|
+
}
|
|
225721
|
+
if (work?.acceptanceState === "rejected" || judgement === "rejected") {
|
|
225722
|
+
return { phase: "done", status: "\u5BA1\u6838\u4E0D\u901A\u8FC7\u3002", latest, pending: [] };
|
|
225723
|
+
}
|
|
225724
|
+
const endedWork = !!(work?.cancelledAt || work?.deadAt);
|
|
225725
|
+
if (endedWork && latest.every((r) => !!r.cancelledAt && reviewState(r) !== "retry")) {
|
|
225726
|
+
return { phase: "done", status: `\u300A${name}\u300B\u7684\u672C\u6B21\u5BA1\u6838\u5DF2\u53D6\u6D88\u3002`, latest, pending: [] };
|
|
225727
|
+
}
|
|
225728
|
+
const interrupted = latest.some((r) => ["failed", "dead"].includes(reviewState(r)));
|
|
225729
|
+
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 };
|
|
225730
|
+
if (latest.some((r) => r.reviewerActorId.startsWith("actor:agent:") && ["running", "retry"].includes(reviewState(r)))) {
|
|
225731
|
+
return { phase: "running", status: `\u6B63\u5728\u5BA1\u6838 \u300A${name}\u300B\u3002`, latest, pending };
|
|
225732
|
+
}
|
|
225733
|
+
return { phase: "not_started", status: `\u8BF7\u786E\u8BA4\u300A${name}\u300B\u662F\u5426\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending };
|
|
225734
|
+
}
|
|
225735
|
+
var REVIEW_CARD_PREFIX, REVIEW_TRACE_EVENT_KINDS;
|
|
225736
|
+
var init_review_activity = __esm({
|
|
225737
|
+
"../server/src/domains/collab/review-activity.ts"() {
|
|
225738
|
+
"use strict";
|
|
225739
|
+
init_src4();
|
|
225740
|
+
REVIEW_CARD_PREFIX = "review-work:";
|
|
225741
|
+
REVIEW_TRACE_EVENT_KINDS = [
|
|
225742
|
+
"review.create",
|
|
225743
|
+
"review.started",
|
|
225744
|
+
"review.response",
|
|
225745
|
+
"review.timeout",
|
|
225746
|
+
"review.kill",
|
|
225747
|
+
"plan.node_retry"
|
|
225748
|
+
];
|
|
225749
|
+
}
|
|
225750
|
+
});
|
|
225751
|
+
|
|
224809
225752
|
// ../server/src/domains/collab/workorder-manager.ts
|
|
224810
225753
|
function resolveWorkorderManager(snap) {
|
|
224811
225754
|
const nodes = snap.nodes;
|
|
@@ -224844,8 +225787,29 @@ function buildWorkorderActivity(input) {
|
|
|
224844
225787
|
if (!snap) return { workorderId, cards: [], truncated: false };
|
|
224845
225788
|
const nodeById = new Map(snap.nodes.map((n) => [n.id, n]));
|
|
224846
225789
|
const workById = new Map(snap.works.map((w2) => [w2.id, w2]));
|
|
224847
|
-
const reviewById = new Map(snap.reviews.map((r) => [r.id, r]));
|
|
224848
225790
|
const issueById = new Map(snap.issues.map((i) => [i.id, i]));
|
|
225791
|
+
const activeWorks = /* @__PURE__ */ new Set();
|
|
225792
|
+
const gapOrigins = /* @__PURE__ */ new Map();
|
|
225793
|
+
const knownStarts = new Set(events.flatMap((r) => r.event.kind === "work.create" || r.event.kind === "work.started" ? [r.event.workId] : []));
|
|
225794
|
+
for (const rec of events) {
|
|
225795
|
+
const event = rec.event;
|
|
225796
|
+
if (event.kind === "work.create" || event.kind === "work.started") activeWorks.add(event.workId);
|
|
225797
|
+
if (event.kind === "work.response" || event.kind === "work.timeout" || event.kind === "work.kill") activeWorks.delete(event.workId);
|
|
225798
|
+
if (event.kind !== "issue.create" || event.issueKind !== "gap") continue;
|
|
225799
|
+
const nodeId = event.raisedByNodeId ?? event.aboutNodeId;
|
|
225800
|
+
const matches = (id) => {
|
|
225801
|
+
const work = workById.get(id);
|
|
225802
|
+
return work?.nodeId === nodeId && work.assigneeActorId === event.authorActorId;
|
|
225803
|
+
};
|
|
225804
|
+
if (event.aboutWorkId && matches(event.aboutWorkId)) {
|
|
225805
|
+
gapOrigins.set(event.issueId, [event.aboutWorkId]);
|
|
225806
|
+
} else if (!event.aboutWorkId) {
|
|
225807
|
+
const candidates = [...activeWorks].filter(matches);
|
|
225808
|
+
const at = Date.parse(rec.createdAt);
|
|
225809
|
+
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));
|
|
225810
|
+
if (candidates.length === 1 && !unobserved) gapOrigins.set(event.issueId, candidates);
|
|
225811
|
+
}
|
|
225812
|
+
}
|
|
224849
225813
|
const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
|
|
224850
225814
|
const rootBriefNodeIds = new Set(
|
|
224851
225815
|
snap.nodes.filter((n) => n.type === "brief" && !nodesWithInEdges.has(n.id)).map((n) => n.id)
|
|
@@ -224896,7 +225860,6 @@ function buildWorkorderActivity(input) {
|
|
|
224896
225860
|
const groupIndexOf = /* @__PURE__ */ new Map();
|
|
224897
225861
|
const cardOfWork = /* @__PURE__ */ new Map();
|
|
224898
225862
|
const groupKey = (nodeId) => `${nodeId}#${groupIndexOf.get(nodeId) ?? 0}`;
|
|
224899
|
-
const reviewCards = /* @__PURE__ */ new Map();
|
|
224900
225863
|
const issueCards = /* @__PURE__ */ new Map();
|
|
224901
225864
|
const workOfIssue = /* @__PURE__ */ new Map();
|
|
224902
225865
|
const plainCommentIssueIds = /* @__PURE__ */ new Set();
|
|
@@ -225118,72 +226081,6 @@ function buildWorkorderActivity(input) {
|
|
|
225118
226081
|
}
|
|
225119
226082
|
break;
|
|
225120
226083
|
}
|
|
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
226084
|
/* ── 沟通与异常(issue)────────────────────────────────────── */
|
|
225188
226085
|
case "issue.create": {
|
|
225189
226086
|
const issueId = String(ev.issueId ?? "");
|
|
@@ -225327,24 +226224,14 @@ function buildWorkorderActivity(input) {
|
|
|
225327
226224
|
if (issue2?.kind === "escalation") {
|
|
225328
226225
|
const linkedGap = str4(issue2.gapId);
|
|
225329
226226
|
const stuckActor = (linkedGap ? issueById.get(linkedGap)?.authorActorId : void 0) ?? issue2.authorActorId;
|
|
226227
|
+
touch(d, rec);
|
|
226228
|
+
d.phase = "done";
|
|
226229
|
+
d.executorId = resolverId;
|
|
226230
|
+
d.handActorId = rec.handActorId ?? void 0;
|
|
226231
|
+
d.status = ACTIVITY_COPY.handledEscalation(nameOf(stuckActor));
|
|
225330
226232
|
d.actions = [];
|
|
225331
226233
|
const raisedCard = issueCards.get(`${issueId}:raised`);
|
|
225332
226234
|
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
226235
|
break;
|
|
225349
226236
|
}
|
|
225350
226237
|
touch(d, rec);
|
|
@@ -225371,9 +226258,7 @@ function buildWorkorderActivity(input) {
|
|
|
225371
226258
|
break;
|
|
225372
226259
|
}
|
|
225373
226260
|
}
|
|
225374
|
-
const reviewedWorkIds = new Set(
|
|
225375
|
-
[...reviewCards.keys()].map((rid) => reviewById.get(rid)?.targetWorkId).filter(Boolean)
|
|
225376
|
-
);
|
|
226261
|
+
const reviewedWorkIds = new Set([...snap.reviews, ...activityReviews(snap, events)].map((r) => r.targetWorkId));
|
|
225377
226262
|
for (const w2 of snap.works) {
|
|
225378
226263
|
if (acceptCards.has(w2.id)) continue;
|
|
225379
226264
|
if (reviewedWorkIds.has(w2.id)) continue;
|
|
@@ -225397,15 +226282,57 @@ function buildWorkorderActivity(input) {
|
|
|
225397
226282
|
status: ACTIVITY_COPY.acceptPending,
|
|
225398
226283
|
artifacts: artifactsByWork.get(w2.id) ?? [],
|
|
225399
226284
|
actions: [
|
|
225400
|
-
{ kind: "accept", label: BUTTON.acceptPass, target: node2.id, verdict: "approve" },
|
|
225401
|
-
{ kind: "accept", label: BUTTON.acceptFail, target: node2.id, verdict: "request_changes" }
|
|
226285
|
+
{ kind: "accept", label: BUTTON.acceptPass, target: node2.id, revisionId: w2.id, verdict: "approve" },
|
|
226286
|
+
{ kind: "accept", label: BUTTON.acceptFail, target: node2.id, revisionId: w2.id, verdict: "request_changes" }
|
|
225402
226287
|
]
|
|
225403
226288
|
});
|
|
225404
226289
|
}
|
|
226290
|
+
const visibleReviews = /* @__PURE__ */ new Map();
|
|
226291
|
+
const allReviews = activityReviews(snap, events);
|
|
226292
|
+
const reviewWorks = /* @__PURE__ */ new Map();
|
|
226293
|
+
const reviewCards = /* @__PURE__ */ new Map();
|
|
226294
|
+
for (const r of allReviews) {
|
|
226295
|
+
const rows = reviewWorks.get(r.targetWorkId) ?? [];
|
|
226296
|
+
rows.push(r);
|
|
226297
|
+
reviewWorks.set(r.targetWorkId, rows);
|
|
226298
|
+
const cardId = reviewCardId(r.targetWorkId, r.reviewerActorId);
|
|
226299
|
+
const group = reviewCards.get(cardId) ?? { workId: r.targetWorkId, reviewerId: r.reviewerActorId, reviews: [] };
|
|
226300
|
+
group.reviews.push(r);
|
|
226301
|
+
reviewCards.set(cardId, group);
|
|
226302
|
+
}
|
|
226303
|
+
for (const [cardId, { workId, reviewerId, reviews }] of reviewCards) {
|
|
226304
|
+
const summary = reviewSummary(snap, reviews, events);
|
|
226305
|
+
const first = reviews[0];
|
|
226306
|
+
const ids2 = new Set(reviews.map((r) => r.id));
|
|
226307
|
+
const created = events.filter((r) => r.event.kind === "review.create" && ids2.has(r.event.reviewId));
|
|
226308
|
+
const dates = summary.latest.flatMap((r) => [r.createdAt, r.startedAt, r.endedAt, r.decidedAt, r.cancelledAt, r.retryAt].filter((v2) => !!v2));
|
|
226309
|
+
const pending = summary.pending.filter((r) => r.reviewerActorId.startsWith("actor:human:") && (!input.viewerActorId || r.reviewerActorId === input.viewerActorId));
|
|
226310
|
+
visibleReviews.set(cardId, {
|
|
226311
|
+
id: cardId,
|
|
226312
|
+
reviewWorkId: workId,
|
|
226313
|
+
reviewerId,
|
|
226314
|
+
reviewerIds: [reviewerId],
|
|
226315
|
+
traceAvailable: true,
|
|
226316
|
+
seq: created.length ? Math.min(...created.map((r) => r.seq)) : 0,
|
|
226317
|
+
at: first.createdAt,
|
|
226318
|
+
updatedAt: dates.sort().at(-1) ?? first.createdAt,
|
|
226319
|
+
nodeId: first.nodeId,
|
|
226320
|
+
executorId: reviewerId,
|
|
226321
|
+
phase: summary.phase,
|
|
226322
|
+
status: summary.status,
|
|
226323
|
+
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 ? `
|
|
226324
|
+
${r.verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${r.note}` : ""}`).join("\n\n"),
|
|
226325
|
+
artifacts: artifactsByWork.get(workId) ?? [],
|
|
226326
|
+
actions: pending.length ? [
|
|
226327
|
+
{ kind: "review", label: BUTTON.approve, target: first.nodeId, revisionId: workId, verdict: "approve" },
|
|
226328
|
+
{ kind: "review", label: BUTTON.reject, target: first.nodeId, revisionId: workId, verdict: "request_changes" }
|
|
226329
|
+
] : []
|
|
226330
|
+
});
|
|
226331
|
+
}
|
|
225405
226332
|
const drafts = [
|
|
225406
226333
|
...standalone,
|
|
225407
226334
|
...groupCards.values(),
|
|
225408
|
-
...
|
|
226335
|
+
...visibleReviews.values(),
|
|
225409
226336
|
...issueCards.values(),
|
|
225410
226337
|
...acceptCards.values()
|
|
225411
226338
|
].sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
|
|
@@ -225429,7 +226356,14 @@ function buildWorkorderActivity(input) {
|
|
|
225429
226356
|
}
|
|
225430
226357
|
}
|
|
225431
226358
|
for (const d of drafts) {
|
|
225432
|
-
if (d.id.startsWith("issue:"))
|
|
226359
|
+
if (!d.id.startsWith("issue:")) continue;
|
|
226360
|
+
const issueId = d.id.slice("issue:".length);
|
|
226361
|
+
if (issueById.get(issueId)?.kind === "gap") {
|
|
226362
|
+
d.traceAvailable = true;
|
|
226363
|
+
d.traceWorkIds = gapOrigins.get(issueId);
|
|
226364
|
+
} else {
|
|
226365
|
+
d.traceWorkIds = issueWorks.get(issueId);
|
|
226366
|
+
}
|
|
225433
226367
|
}
|
|
225434
226368
|
const runIdOfCard = (d) => {
|
|
225435
226369
|
const map = input.runIdByTarget;
|
|
@@ -225439,7 +226373,14 @@ function buildWorkorderActivity(input) {
|
|
|
225439
226373
|
if (run) return run;
|
|
225440
226374
|
}
|
|
225441
226375
|
}
|
|
225442
|
-
|
|
226376
|
+
if (d.reviewWorkId) {
|
|
226377
|
+
for (const r of [...reviewWorks.get(d.reviewWorkId) ?? []].reverse()) {
|
|
226378
|
+
if (d.reviewerId && r.reviewerActorId !== d.reviewerId) continue;
|
|
226379
|
+
const run = map?.get(`review:${r.id}`);
|
|
226380
|
+
if (run) return run;
|
|
226381
|
+
}
|
|
226382
|
+
}
|
|
226383
|
+
return void 0;
|
|
225443
226384
|
};
|
|
225444
226385
|
const cards = drafts.map((d) => ({
|
|
225445
226386
|
id: d.id,
|
|
@@ -225448,12 +226389,14 @@ function buildWorkorderActivity(input) {
|
|
|
225448
226389
|
updatedAt: d.updatedAt,
|
|
225449
226390
|
...d.nodeId ? { nodeId: d.nodeId, nodeTitle: nodeName(d.nodeId) } : {},
|
|
225450
226391
|
executor: ref2(d.executorId),
|
|
226392
|
+
...d.reviewWorkId ? { reviewWorkId: d.reviewWorkId, reviewers: d.reviewerIds?.map(ref2) } : {},
|
|
225451
226393
|
...d.handActorId ? { handActor: ref2(d.handActorId) } : {},
|
|
225452
226394
|
phase: d.phase,
|
|
225453
226395
|
status: d.status,
|
|
225454
226396
|
...d.detail ? { detail: d.detail } : {},
|
|
225455
226397
|
artifacts: d.artifacts,
|
|
225456
226398
|
actions: d.actions,
|
|
226399
|
+
...d.traceAvailable ? { traceAvailable: true } : {},
|
|
225457
226400
|
...d.traceWorkIds?.length ? { traceWorkIds: d.traceWorkIds, traceAvailable: true } : {},
|
|
225458
226401
|
.../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(runIdOfCard(d))
|
|
225459
226402
|
}));
|
|
@@ -225465,6 +226408,7 @@ var init_activity2 = __esm({
|
|
|
225465
226408
|
"use strict";
|
|
225466
226409
|
init_src();
|
|
225467
226410
|
init_src4();
|
|
226411
|
+
init_review_activity();
|
|
225468
226412
|
init_workorder_manager();
|
|
225469
226413
|
ACTIVITY_EVENT_KINDS = [
|
|
225470
226414
|
"plan.changed",
|
|
@@ -225629,12 +226573,21 @@ function buildWorkorderActivityTrace(input) {
|
|
|
225629
226573
|
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
225630
226574
|
const empty2 = { workorderId, cardId, attempts: [] };
|
|
225631
226575
|
if (!snap) return empty2;
|
|
226576
|
+
const reviews = activityReviews(snap, input.events ?? []);
|
|
226577
|
+
const reviewTarget = reviewCardTarget(cardId, reviews);
|
|
226578
|
+
if (reviewTarget) return buildReviewHistory(input, reviewTarget.targetWorkId, reviewTarget.reviewerActorId, reviews);
|
|
225632
226579
|
const card2 = input.events ? buildWorkorderActivity({
|
|
225633
226580
|
workorderId,
|
|
225634
226581
|
snap,
|
|
225635
226582
|
events: input.events,
|
|
225636
226583
|
ref: ref2
|
|
225637
226584
|
}).cards.find((c) => c.id === cardId) : void 0;
|
|
226585
|
+
const gap = cardId.startsWith("issue:") ? snap.issues.find((i) => i.id === cardId.slice(6) && i.kind === "gap") : void 0;
|
|
226586
|
+
if (gap && !card2?.traceWorkIds?.length) return {
|
|
226587
|
+
...empty2,
|
|
226588
|
+
works: [],
|
|
226589
|
+
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"
|
|
226590
|
+
};
|
|
225638
226591
|
const workIds = card2?.traceWorkIds ?? (cardId.startsWith("work:") ? [cardId.slice(5)] : []);
|
|
225639
226592
|
if (workIds.length > 0) {
|
|
225640
226593
|
let firstBriefing;
|
|
@@ -225666,6 +226619,7 @@ function buildWorkorderActivityTrace(input) {
|
|
|
225666
226619
|
cardId,
|
|
225667
226620
|
briefing: firstBriefing,
|
|
225668
226621
|
works,
|
|
226622
|
+
...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
226623
|
attempts: works.flatMap((w2) => w2.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId))
|
|
225670
226624
|
};
|
|
225671
226625
|
}
|
|
@@ -225781,10 +226735,98 @@ function buildSingleTrace(input) {
|
|
|
225781
226735
|
});
|
|
225782
226736
|
return { workorderId, cardId, briefing, attempts };
|
|
225783
226737
|
}
|
|
226738
|
+
function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
|
|
226739
|
+
const { workorderId, cardId } = input;
|
|
226740
|
+
const snap = input.snap;
|
|
226741
|
+
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
226742
|
+
const rows = allReviews.filter((r) => r.targetWorkId === targetWorkId && r.reviewerActorId === reviewerActorId);
|
|
226743
|
+
if (!rows.length) return { workorderId, cardId, attempts: [] };
|
|
226744
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
226745
|
+
const current = new Set(latestActivityReviews(rows, input.events ?? []).map((r) => r.id));
|
|
226746
|
+
const work = snap.works.find((w2) => w2.id === targetWorkId);
|
|
226747
|
+
const nodeId = rows[0].nodeId;
|
|
226748
|
+
const events = [];
|
|
226749
|
+
for (const rec of [...input.events ?? []].sort((a, b2) => a.seq - b2.seq)) {
|
|
226750
|
+
const e = rec.event;
|
|
226751
|
+
const r = "reviewId" in e && e.reviewId ? byId.get(e.reviewId) : void 0;
|
|
226752
|
+
const direct = e.kind === "review.response" && e.targetWorkId === targetWorkId && e.reviewerActorId === reviewerActorId;
|
|
226753
|
+
let retry = false;
|
|
226754
|
+
if (e.kind === "plan.node_retry" && e.nodeId === nodeId) {
|
|
226755
|
+
const preceding = snap.works.filter((w2) => w2.nodeId === nodeId && w2.createdAt <= rec.createdAt).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
226756
|
+
retry = preceding[0]?.id === targetWorkId && rows.some((r2) => r2.createdAt <= rec.createdAt);
|
|
226757
|
+
}
|
|
226758
|
+
if (!r && !direct && !retry) continue;
|
|
226759
|
+
let label;
|
|
226760
|
+
let detail;
|
|
226761
|
+
switch (e.kind) {
|
|
226762
|
+
case "review.create":
|
|
226763
|
+
label = "\u521B\u5EFA\u5BA1\u6838";
|
|
226764
|
+
break;
|
|
226765
|
+
case "review.started":
|
|
226766
|
+
label = "\u5F00\u59CB\u5BA1\u6838";
|
|
226767
|
+
break;
|
|
226768
|
+
case "review.timeout":
|
|
226769
|
+
label = "\u5BA1\u6838\u6267\u884C\u8D85\u65F6 / \u5931\u8D25";
|
|
226770
|
+
break;
|
|
226771
|
+
case "review.kill":
|
|
226772
|
+
label = "\u5BA1\u6838\u5DF2\u505C\u6B62";
|
|
226773
|
+
detail = e.reason;
|
|
226774
|
+
break;
|
|
226775
|
+
case "review.response":
|
|
226776
|
+
label = e.verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : "\u5BA1\u6838\u4E0D\u901A\u8FC7";
|
|
226777
|
+
detail = e.note ?? void 0;
|
|
226778
|
+
break;
|
|
226779
|
+
case "plan.node_retry":
|
|
226780
|
+
label = "\u4ECB\u5165\u91CD\u8BD5";
|
|
226781
|
+
break;
|
|
226782
|
+
default:
|
|
226783
|
+
continue;
|
|
226784
|
+
}
|
|
226785
|
+
const actorId = e.kind === "plan.node_retry" ? e.by : "reviewerActorId" in e ? e.reviewerActorId : rec.actorId;
|
|
226786
|
+
events.push({
|
|
226787
|
+
seq: rec.seq,
|
|
226788
|
+
at: rec.createdAt,
|
|
226789
|
+
kind: e.kind,
|
|
226790
|
+
label,
|
|
226791
|
+
actor: ref2(actorId),
|
|
226792
|
+
...r ? { reviewId: r.id, reviewGroup: r.reviewGroup } : {},
|
|
226793
|
+
...detail ? { detail } : {}
|
|
226794
|
+
});
|
|
226795
|
+
}
|
|
226796
|
+
const reviews = rows.map((r) => {
|
|
226797
|
+
const single = buildSingleTrace({ ...input, snap: { ...snap, reviews: allReviews }, cardId: `review:${r.id}` });
|
|
226798
|
+
return {
|
|
226799
|
+
reviewId: r.id,
|
|
226800
|
+
reviewGroup: r.reviewGroup,
|
|
226801
|
+
actor: ref2(r.reviewerActorId),
|
|
226802
|
+
at: r.createdAt,
|
|
226803
|
+
...r.endedAt ?? r.decidedAt ?? r.cancelledAt ? { endedAt: r.endedAt ?? r.decidedAt ?? r.cancelledAt } : {},
|
|
226804
|
+
status: reviewLabel(r),
|
|
226805
|
+
current: current.has(r.id),
|
|
226806
|
+
...r.note ? { note: r.note } : {},
|
|
226807
|
+
attempts: single.attempts
|
|
226808
|
+
};
|
|
226809
|
+
});
|
|
226810
|
+
return {
|
|
226811
|
+
workorderId,
|
|
226812
|
+
cardId,
|
|
226813
|
+
attempts: reviews.flatMap((r) => r.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId)),
|
|
226814
|
+
reviewHistory: {
|
|
226815
|
+
targetWorkId,
|
|
226816
|
+
nodeTitle: snap.nodes.find((n) => n.id === nodeId)?.title ?? nodeId,
|
|
226817
|
+
...work?.outputVersionNo != null ? { outputVersionNo: work.outputVersionNo } : {},
|
|
226818
|
+
artifacts: work ? artifactsOfWork(work, input) : [],
|
|
226819
|
+
reviews,
|
|
226820
|
+
events,
|
|
226821
|
+
...input.dispatches === void 0 ? { recordsUnavailable: true } : {}
|
|
226822
|
+
}
|
|
226823
|
+
};
|
|
226824
|
+
}
|
|
225784
226825
|
var init_activity_trace = __esm({
|
|
225785
226826
|
"../server/src/domains/collab/activity-trace.ts"() {
|
|
225786
226827
|
"use strict";
|
|
225787
226828
|
init_src4();
|
|
226829
|
+
init_review_activity();
|
|
225788
226830
|
init_activity2();
|
|
225789
226831
|
init_workorder_manager();
|
|
225790
226832
|
}
|
|
@@ -227870,6 +228912,7 @@ function collabDomain(opts) {
|
|
|
227870
228912
|
return {
|
|
227871
228913
|
status: 200,
|
|
227872
228914
|
body: buildWorkorderActivity({
|
|
228915
|
+
viewerActorId: req.auth.actor,
|
|
227873
228916
|
workorderId,
|
|
227874
228917
|
snap,
|
|
227875
228918
|
events,
|
|
@@ -227905,7 +228948,12 @@ function collabDomain(opts) {
|
|
|
227905
228948
|
};
|
|
227906
228949
|
const [snap, events] = engineStore ? await engineStore.transaction(async (tx) => [
|
|
227907
228950
|
await tx.loadWorkorder(workorderId),
|
|
227908
|
-
await tx.listEvents(
|
|
228951
|
+
await tx.listEvents(
|
|
228952
|
+
workorderId,
|
|
228953
|
+
0,
|
|
228954
|
+
cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? void 0 : ACTIVITY_EVENT_LIMIT,
|
|
228955
|
+
cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? REVIEW_TRACE_EVENT_KINDS : ACTIVITY_EVENT_KINDS
|
|
228956
|
+
)
|
|
227909
228957
|
]) : [null, []];
|
|
227910
228958
|
const manifestFiles = snap ? await resolveActivityManifestFiles(snap.artifacts, blobs) : void 0;
|
|
227911
228959
|
const dispatches = await listDispatchRows(workorderId, opts.dispatchesOfWorkorder);
|
|
@@ -228026,6 +229074,9 @@ function collabDomain(opts) {
|
|
|
228026
229074
|
return { status: 201, body: { item } };
|
|
228027
229075
|
});
|
|
228028
229076
|
router.post("/api/workorders/:id/hold", async (req) => {
|
|
229077
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
229078
|
+
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" } } };
|
|
229079
|
+
}
|
|
228029
229080
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
228030
229081
|
const ws = req.params.id;
|
|
228031
229082
|
const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
|
|
@@ -228065,6 +229116,9 @@ function collabDomain(opts) {
|
|
|
228065
229116
|
return { status: 200, body: { cancelled } };
|
|
228066
229117
|
});
|
|
228067
229118
|
router.post("/api/workorders/:id/dispatch", async (req) => {
|
|
229119
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
229120
|
+
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" } } };
|
|
229121
|
+
}
|
|
228068
229122
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
228069
229123
|
const ws = req.params.id;
|
|
228070
229124
|
const already = !kernel.model.pausedWorkorders.has(ws);
|
|
@@ -228390,6 +229444,7 @@ var init_collab = __esm({
|
|
|
228390
229444
|
init_workorder_detail();
|
|
228391
229445
|
init_workorders();
|
|
228392
229446
|
init_activity2();
|
|
229447
|
+
init_review_activity();
|
|
228393
229448
|
init_activity_trace();
|
|
228394
229449
|
init_inbox();
|
|
228395
229450
|
init_escalation_attention();
|
|
@@ -231745,7 +232800,8 @@ function createChatSessionsDomain(opts) {
|
|
|
231745
232800
|
...Object.prototype.hasOwnProperty.call(body2, "projectId") ? { projectId: body2.projectId } : {},
|
|
231746
232801
|
// 会话级模型覆盖:显式传 null / 空串 = 清除覆盖(回到旧解析);不传这一项 = 不动它。
|
|
231747
232802
|
...Object.prototype.hasOwnProperty.call(body2, "model") ? { model: normalizeModel(body2.model) } : {},
|
|
231748
|
-
|
|
232803
|
+
// 只有显式传了才写——改标题 / 换项目 / 换模型都不是「有新动静」,不该顶掉列表顺序与已读水位。
|
|
232804
|
+
...body2.touchedAt !== void 0 ? { touchedAt: body2.touchedAt } : {}
|
|
231749
232805
|
});
|
|
231750
232806
|
return { status: 200, body: await store2.getSession(req.params.id) };
|
|
231751
232807
|
});
|
|
@@ -231964,7 +233020,11 @@ function createChatSessionsDomain(opts) {
|
|
|
231964
233020
|
...record8.label ? { label: record8.label } : {},
|
|
231965
233021
|
...record8.taskDigest ? { taskDigest: record8.taskDigest } : {},
|
|
231966
233022
|
parentMessageId: record8.parentMessageId,
|
|
231967
|
-
|
|
233023
|
+
/* `messageId` 必须过 `touchAnchorMessageId`:流水行的主键是 `(delegation_id, message_id)`,
|
|
233024
|
+
服务端为此在锚点后缀了一段随机 token(`DelegationService.touchAnchor`)。那段后缀是存储
|
|
233025
|
+
的去重装置,直接发给前端等于给了一条 `messages[]` 里不存在的 id ——追加转达卡全部掉进
|
|
233026
|
+
floating 堆在流末尾,同一条消息上的两条委派还会因后缀不同被拆成两张单专家卡。 */
|
|
233027
|
+
touches: touches.map((t) => ({ messageId: touchAnchorMessageId(t.messageId), kind: t.kind, at: t.at })),
|
|
231968
233028
|
artifacts: collectDelegationArtifacts(record8.roundSummaries),
|
|
231969
233029
|
createdAt: record8.createdAt,
|
|
231970
233030
|
...record8.settledAt ? { settledAt: record8.settledAt } : {}
|
|
@@ -244470,6 +245530,64 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244470
245530
|
async appendMessage(m2, opts) {
|
|
244471
245531
|
return (await this.appendMessageOnce(m2, opts)).message;
|
|
244472
245532
|
}
|
|
245533
|
+
/**
|
|
245534
|
+
* 系统状态条的 `chat_items` 补行(ADR-0510 D3 的收口)。
|
|
245535
|
+
*
|
|
245536
|
+
* ## 为什么需要
|
|
245537
|
+
*
|
|
245538
|
+
* `role='system'` 的通告(委派回报「已完成 / 无法完成」、工单终态播报、授权卡状态条)走的是
|
|
245539
|
+
* `appendChatMessageOnce` → 本 store 直写 `chat_messages` 这条路,**不经 `ChatItemLedger`**
|
|
245540
|
+
* (`chat-system-message.ts` 文件头原话:「非 live 直写……不产生 chat_items」)。
|
|
245541
|
+
*
|
|
245542
|
+
* 在 D3 之前这没关系:`/items` 会给没有 item 行的消息合成一条快照条。D3 把历史收成
|
|
245543
|
+
* **只从 `chat_items` 出**、删掉了那条合成路之后,这些通告就从对话页上**凭空消失**了。
|
|
245544
|
+
*
|
|
245545
|
+
* 后果不只是「少看见一条通告」——委派产物的附件卡是**锚在那条回报通告之后的第一条 assistant**
|
|
245546
|
+
* 上的(ws:wo-4a2cd648 §①)。通告不在流里 ⇒ 找不到锚点 ⇒ 卡掉进 floating、渲染到末尾。
|
|
245547
|
+
* 2026-09-09 用户现场:`--continue` 续跑两个子会话,回报通告写进了 `chat_messages`
|
|
245548
|
+
* (07:29:56 / 07:30:12)但 `chat_items` 一行都没有,产物卡因此挂错位置。
|
|
245549
|
+
*
|
|
245550
|
+
* ## 为什么补在这一层
|
|
245551
|
+
*
|
|
245552
|
+
* `appendChatMessageOnce` 是所有系统消息的唯一入口,但那三个调用点(return-flow /
|
|
245553
|
+
* chat-broadcast / server.ts 授权卡)**都拿不到 `ChatItemStore`**,从上面递下来要动三份 deps 契约。
|
|
245554
|
+
* 而这里本来就握着同一个 schema 的连接池——把「用户能看见的消息必有 item 行」这条不变量
|
|
245555
|
+
* 落在存储边界上,比在三个业务处各接一次线更难漏。
|
|
245556
|
+
*
|
|
245557
|
+
* ## 失败了怎么办:吞掉
|
|
245558
|
+
*
|
|
245559
|
+
* item 写失败**不许**把消息本身带走——消息已经 INSERT 成功、幂等键已经占住,抛出去会让调用方
|
|
245560
|
+
* 当成「没写成」而重试,而重试会撞主键。所以这里只记日志。代价是那条通告在页面上仍然看不见,
|
|
245561
|
+
* 与修复前一致,不会更坏。
|
|
245562
|
+
*/
|
|
245563
|
+
async writeSystemMessageItem(m2) {
|
|
245564
|
+
try {
|
|
245565
|
+
await this.pool.query(
|
|
245566
|
+
`INSERT INTO ${this.s}.chat_items
|
|
245567
|
+
(id, session_id, message_id, turn_id, run_id, seq, kind, role, status,
|
|
245568
|
+
version, turn_version, provider_item_key, payload, metadata,
|
|
245569
|
+
created_at, updated_at, completed_at)
|
|
245570
|
+
VALUES ($1, $2, $3, $4, NULL, 1, 'text', $5, 'completed',
|
|
245571
|
+
COALESCE((SELECT MAX(version) FROM ${this.s}.chat_items WHERE session_id = $2), 0) + 1,
|
|
245572
|
+
1, NULL, $6::jsonb, '{"legacyContent": false}'::jsonb,
|
|
245573
|
+
$7::timestamptz, $7::timestamptz, $7::timestamptz)
|
|
245574
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
245575
|
+
[
|
|
245576
|
+
// 与 D3 之前 `/items` 合成条、以及 backfill 补的行**同一套身份**:同一条消息永远只有
|
|
245577
|
+
// 这一个 itemId,重复调用(幂等重写、backfill 再跑)不会造出第二条。
|
|
245578
|
+
`chat-message:${m2.id}`,
|
|
245579
|
+
m2.sessionId,
|
|
245580
|
+
m2.id,
|
|
245581
|
+
`chat-message-turn:${m2.id}`,
|
|
245582
|
+
m2.role,
|
|
245583
|
+
JSON.stringify({ role: m2.role, text: m2.content ?? "" }),
|
|
245584
|
+
m2.createdAt
|
|
245585
|
+
]
|
|
245586
|
+
);
|
|
245587
|
+
} catch (err) {
|
|
245588
|
+
console.warn(`[chat-items] \u7CFB\u7EDF\u72B6\u6001\u6761\u8865\u884C\u5931\u8D25\uFF08message=${m2.id} session=${m2.sessionId}\uFF09: ${String(err)}`);
|
|
245589
|
+
}
|
|
245590
|
+
}
|
|
244473
245591
|
async appendMessageOnce(m2, opts) {
|
|
244474
245592
|
await this.assertSessionInScope(m2.sessionId);
|
|
244475
245593
|
const insert = async () => {
|
|
@@ -244486,7 +245604,9 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244486
245604
|
};
|
|
244487
245605
|
for (let attempt = 0; ; attempt++) {
|
|
244488
245606
|
try {
|
|
244489
|
-
|
|
245607
|
+
const written = { message: { ...m2, seq: await insert() }, inserted: true };
|
|
245608
|
+
if ((opts?.source ?? "live") === "system") await this.writeSystemMessageItem(written.message);
|
|
245609
|
+
return written;
|
|
244490
245610
|
} catch (err) {
|
|
244491
245611
|
const isUniqueViolation5 = err && typeof err === "object" && err.code === "23505";
|
|
244492
245612
|
const constraint = String(err.constraint ?? "");
|
|
@@ -245138,11 +246258,17 @@ var init_postgres_chat_sessions = __esm({
|
|
|
245138
246258
|
model: row.model ?? null
|
|
245139
246259
|
});
|
|
245140
246260
|
rowToSessionWithQuality = (row) => {
|
|
246261
|
+
const status = row.last_assistant_status ?? void 0;
|
|
245141
246262
|
const quality = deriveLastTurnQuality({
|
|
245142
|
-
status
|
|
246263
|
+
status,
|
|
245143
246264
|
content: row.last_assistant_content ?? ""
|
|
245144
246265
|
});
|
|
245145
|
-
|
|
246266
|
+
const running = deriveSessionRunning({ status });
|
|
246267
|
+
return {
|
|
246268
|
+
...rowToSession(row),
|
|
246269
|
+
...quality ? { lastTurnQuality: quality } : {},
|
|
246270
|
+
...running ? { running: true } : {}
|
|
246271
|
+
};
|
|
245146
246272
|
};
|
|
245147
246273
|
rowToMessage = (row) => ({
|
|
245148
246274
|
id: row.id,
|
|
@@ -255770,6 +256896,41 @@ async function startServe(opts) {
|
|
|
255770
256896
|
let nodeTokens = createNodeTokenStore(path29.join(opts.dir, "node-tokens.json"));
|
|
255771
256897
|
const enrollTokens = createEnrollTokenStore(30 * 60 * 1e3, path29.join(opts.dir, "enroll-tokens.json"));
|
|
255772
256898
|
let nodeStore = await FileNodeStore.open(path29.join(opts.dir, "nodes.json"));
|
|
256899
|
+
const nodeReportedModelsStore = /* @__PURE__ */ new Map();
|
|
256900
|
+
const modelRefreshWaiters = /* @__PURE__ */ new Map();
|
|
256901
|
+
const modelRefreshInFlight = /* @__PURE__ */ new Map();
|
|
256902
|
+
const modelRefreshLastAt = /* @__PURE__ */ new Map();
|
|
256903
|
+
const MODELS_STALE_MS = 5 * 6e4;
|
|
256904
|
+
const MODELS_BG_REFRESH_MIN_GAP_MS = 6e4;
|
|
256905
|
+
const MODELS_REFRESH_TIMEOUT_MS = 45e3;
|
|
256906
|
+
const refreshNodeModels = (nodeId) => {
|
|
256907
|
+
const running = modelRefreshInFlight.get(nodeId);
|
|
256908
|
+
if (running) return running;
|
|
256909
|
+
if (!hub) return Promise.resolve({ ok: false, reason: "\u672C\u8FDB\u7A0B\u6CA1\u6709\u8282\u70B9\u7F51\u5173" });
|
|
256910
|
+
const online = hub.connectedDaemons().some((d) => d.daemonId === nodeId);
|
|
256911
|
+
if (!online) return Promise.resolve({ ok: false, reason: "\u8282\u70B9\u5F53\u524D\u4E0D\u5728\u7EBF\uFF0C\u7528\u7684\u662F\u5B83\u4E0A\u6B21\u62A5\u7684\u6E05\u5355" });
|
|
256912
|
+
const requestId = (0, import_node_crypto75.randomUUID)();
|
|
256913
|
+
const task = new Promise((resolve10) => {
|
|
256914
|
+
const timer = setTimeout(() => {
|
|
256915
|
+
modelRefreshWaiters.delete(requestId);
|
|
256916
|
+
resolve10({ ok: false, reason: "\u8282\u70B9\u6CA1\u5728 45 \u79D2\u5185\u56DE\u8BDD\uFF08\u53EF\u80FD\u662F\u65E7\u7248\u8282\u70B9\uFF0C\u6216\u8FD9\u53F0\u673A\u5668\u4E0A\u7684 CLI \u63A2\u6D4B\u8D85\u65F6\uFF09" });
|
|
256917
|
+
}, MODELS_REFRESH_TIMEOUT_MS);
|
|
256918
|
+
modelRefreshWaiters.set(requestId, { resolve: () => resolve10({ ok: true }), timer });
|
|
256919
|
+
hub.dispatch(nodeId, { type: "list_models", requestId });
|
|
256920
|
+
}).finally(() => {
|
|
256921
|
+
modelRefreshInFlight.delete(nodeId);
|
|
256922
|
+
modelRefreshLastAt.set(nodeId, Date.now());
|
|
256923
|
+
});
|
|
256924
|
+
modelRefreshInFlight.set(nodeId, task);
|
|
256925
|
+
return task;
|
|
256926
|
+
};
|
|
256927
|
+
const nodeReportedModels = (nodeId, runtimeKind) => {
|
|
256928
|
+
const entry = nodeReportedModelsStore.get(nodeId);
|
|
256929
|
+
const stale = !entry || Date.now() - entry.fetchedAt > MODELS_STALE_MS;
|
|
256930
|
+
const gapOk = Date.now() - (modelRefreshLastAt.get(nodeId) ?? 0) > MODELS_BG_REFRESH_MIN_GAP_MS;
|
|
256931
|
+
if (stale && gapOk) void refreshNodeModels(nodeId).catch(() => ({ ok: false }));
|
|
256932
|
+
return entry?.byKind.get(runtimeKind);
|
|
256933
|
+
};
|
|
255773
256934
|
const registryAuditFile = path29.join(opts.dir, "registry-audit.ndjson");
|
|
255774
256935
|
const registryListeners = /* @__PURE__ */ new Set();
|
|
255775
256936
|
const registryAudit = (record8) => {
|
|
@@ -255899,7 +257060,7 @@ async function startServe(opts) {
|
|
|
255899
257060
|
listBindingModelIds: async (nodeId, runtimeKind) => {
|
|
255900
257061
|
const rt = (await nodeStore.listRuntimes(nodeId)).find((r) => r.kind === runtimeKind);
|
|
255901
257062
|
if (!rt) return [];
|
|
255902
|
-
return (await resolveRuntimeModels({ nodeStore, registry: registryStore }, rt.id)).map((m2) => m2.id);
|
|
257063
|
+
return (await resolveRuntimeModels({ nodeStore, registry: registryStore, nodeReportedModels }, rt.id)).map((m2) => m2.id);
|
|
255903
257064
|
}
|
|
255904
257065
|
});
|
|
255905
257066
|
assistantsService = new AssistantsService({
|
|
@@ -257437,7 +258598,7 @@ async function startServe(opts) {
|
|
|
257437
258598
|
registry: registryStore,
|
|
257438
258599
|
// 会话级模型覆盖的候选清单:与 `GET /api/runtimes/:id/models` **同一份**实现
|
|
257439
258600
|
// (resolveRuntimeModels = discovery + 管理员 override),避免「下拉选得到、发出去被拒」。
|
|
257440
|
-
listRuntimeModelIds: async (runtimeId) => (await resolveRuntimeModels({ nodeStore, registry: registryStore }, runtimeId)).map((m2) => m2.id),
|
|
258601
|
+
listRuntimeModelIds: async (runtimeId) => (await resolveRuntimeModels({ nodeStore, registry: registryStore, nodeReportedModels }, runtimeId)).map((m2) => m2.id),
|
|
257441
258602
|
// 诊断 ab94f202 Fix A:add-revision --role 写入口校验用「可分配岗位」集合(岗位目录 AND assignable)。
|
|
257442
258603
|
// 与 GET /api/roles 的 assignable 派生同式(domains/roles/index.ts):system=true / 已弃用一律不可分配。
|
|
257443
258604
|
resolveAssignableRoles: async () => new Set(
|
|
@@ -257574,6 +258735,8 @@ async function startServe(opts) {
|
|
|
257574
258735
|
nodeHealth,
|
|
257575
258736
|
activeRunCountOf,
|
|
257576
258737
|
activeRunsOf,
|
|
258738
|
+
nodeReportedModels,
|
|
258739
|
+
refreshNodeModels,
|
|
257577
258740
|
fetchLatestNpmDaemonVersion
|
|
257578
258741
|
}),
|
|
257579
258742
|
collabDomain({
|
|
@@ -258883,6 +260046,24 @@ async function startServe(opts) {
|
|
|
258883
260046
|
});
|
|
258884
260047
|
return chatLiveSessions.has(jobKey) ? "live" : "unknown";
|
|
258885
260048
|
});
|
|
260049
|
+
hub.addMessageListener((daemonId, msg) => {
|
|
260050
|
+
if (msg.type !== "runtime_models") return;
|
|
260051
|
+
const prev = nodeReportedModelsStore.get(daemonId);
|
|
260052
|
+
const byKind = prev?.byKind ?? /* @__PURE__ */ new Map();
|
|
260053
|
+
for (const entry of msg.models) {
|
|
260054
|
+
if (entry.models.length > 0) byKind.set(entry.kind, entry.models);
|
|
260055
|
+
}
|
|
260056
|
+
nodeReportedModelsStore.set(daemonId, { byKind, fetchedAt: Date.now() });
|
|
260057
|
+
if (msg.requestId) {
|
|
260058
|
+
const waiter = modelRefreshWaiters.get(msg.requestId);
|
|
260059
|
+
if (waiter) {
|
|
260060
|
+
clearTimeout(waiter.timer);
|
|
260061
|
+
modelRefreshWaiters.delete(msg.requestId);
|
|
260062
|
+
waiter.resolve();
|
|
260063
|
+
}
|
|
260064
|
+
}
|
|
260065
|
+
console.log(`[models] \u8282\u70B9 ${daemonId} \u4E0A\u62A5\u5019\u9009\u6A21\u578B\uFF1A${msg.models.map((m2) => `${m2.kind}=${m2.models.length}`).join(" ") || "\uFF08\u4E00\u6761\u90FD\u6CA1\u63A2\u5230\uFF09"}`);
|
|
260066
|
+
});
|
|
258886
260067
|
hub.addMessageListener((daemonId, msg) => {
|
|
258887
260068
|
if (msg.type !== "gc_query_artifacts") return;
|
|
258888
260069
|
void (async () => {
|
|
@@ -261986,6 +263167,7 @@ var OUTBOX_MAX = 500;
|
|
|
261986
263167
|
var PENDING_STREAM_MAX = 2e3;
|
|
261987
263168
|
var AUTH_REJECT_BACKOFF_MAX_MS = 15 * 6e4;
|
|
261988
263169
|
var AUTH_REJECT_GIVE_UP_AFTER = 3;
|
|
263170
|
+
var MODEL_REPORT_COOLDOWN_MS = 10 * 6e4;
|
|
261989
263171
|
var AUTH_REJECT_RE = /Unexpected server response:\s*(401|403)/;
|
|
261990
263172
|
var DRAIN_TIMEOUT_MS = 15e3;
|
|
261991
263173
|
var DRAIN_POLL_MS = 50;
|
|
@@ -262079,6 +263261,12 @@ var DaemonWsClient = class {
|
|
|
262079
263261
|
authRejectStreak = 0;
|
|
262080
263262
|
/** 本次连接尝试是否因握手 401/403 失败——error 事件里置、close 事件里消费。 */
|
|
262081
263263
|
lastErrorWasAuthReject = false;
|
|
263264
|
+
/** 模型上报的轮次号:重连会再拉一轮,用它作废上一轮的迟到结果(否则旧结果可能盖住新结果)。 */
|
|
263265
|
+
modelReportGeneration = 0;
|
|
263266
|
+
/** 上一次**探测成功**的时刻;节点频繁掉线重连时靠它避免每分钟把十几个 CLI 重起一遍。 */
|
|
263267
|
+
lastModelReportAt = 0;
|
|
263268
|
+
/** 缓存上一轮结果:冷却期内重连直接重发这份,服务端重启后也能立刻拿到清单。 */
|
|
263269
|
+
lastModelReport = [];
|
|
262082
263270
|
authRejectGiveUpAfter;
|
|
262083
263271
|
/** 放弃时的动作;默认干净退出(exit 0),测试注入以免打死测试进程。 */
|
|
262084
263272
|
onAuthGiveUp;
|
|
@@ -262187,6 +263375,7 @@ var DaemonWsClient = class {
|
|
|
262187
263375
|
this.flushPendingStream();
|
|
262188
263376
|
this.resendOutbox();
|
|
262189
263377
|
this.reportRuntimes = false;
|
|
263378
|
+
void this.reportRuntimeModels();
|
|
262190
263379
|
this.pingTimer = setInterval(() => {
|
|
262191
263380
|
this.send({ type: "pong" });
|
|
262192
263381
|
if (Date.now() - this.lastReceivedAt > this.silenceTimeoutMs) {
|
|
@@ -262353,6 +263542,10 @@ var DaemonWsClient = class {
|
|
|
262353
263542
|
this.send({ type: "workdir_read_result", requestId: msg.requestId, result });
|
|
262354
263543
|
break;
|
|
262355
263544
|
}
|
|
263545
|
+
case "list_models": {
|
|
263546
|
+
void this.reportRuntimeModels({ force: true, requestId: msg.requestId });
|
|
263547
|
+
break;
|
|
263548
|
+
}
|
|
262356
263549
|
case "update": {
|
|
262357
263550
|
this.pendingUpdatePkg = msg.pkg;
|
|
262358
263551
|
if (!this.onUpdate) {
|
|
@@ -262470,6 +263663,42 @@ var DaemonWsClient = class {
|
|
|
262470
263663
|
this.onUpdate?.(this.pendingUpdatePkg);
|
|
262471
263664
|
}
|
|
262472
263665
|
}
|
|
263666
|
+
/**
|
|
263667
|
+
* 拉一遍本机各 runtime 的候选模型清单并上报(`runtime_models` 帧)。
|
|
263668
|
+
*
|
|
263669
|
+
* 串行而不是并发:ACP 家族每探一个都要真起一个 CLI 进程(hermes 40s、oasis-agent 30s 的超时),
|
|
263670
|
+
* 十几个一起起会把节点的内存和文件句柄按住。`listModels` 自带 60 秒缓存,重连时基本是空跑。
|
|
263671
|
+
* 任何一个 runtime 抛错都只丢它自己那一条,不影响其余。全空就不发帧——让服务端走老路,
|
|
263672
|
+
* 而不是用一份空清单把它原本能给出的静态目录盖掉。
|
|
263673
|
+
*/
|
|
263674
|
+
async reportRuntimeModels(opts = {}) {
|
|
263675
|
+
const generation = ++this.modelReportGeneration;
|
|
263676
|
+
const reply = (models) => this.send({ type: "runtime_models", models, ...opts.requestId ? { requestId: opts.requestId } : {} });
|
|
263677
|
+
if (!opts.force && this.lastModelReport.length > 0 && Date.now() - this.lastModelReportAt < MODEL_REPORT_COOLDOWN_MS) {
|
|
263678
|
+
reply(this.lastModelReport);
|
|
263679
|
+
return;
|
|
263680
|
+
}
|
|
263681
|
+
const out = [];
|
|
263682
|
+
for (const rt of this.runtimes) {
|
|
263683
|
+
if (generation !== this.modelReportGeneration) return;
|
|
263684
|
+
try {
|
|
263685
|
+
const kind = rt.kind;
|
|
263686
|
+
if (opts.force) clearModelDiscoveryCache(kind, rt.binary);
|
|
263687
|
+
const models = await listModels(kind, rt.binary);
|
|
263688
|
+
if (models.length > 0) out.push({ kind: rt.kind, models });
|
|
263689
|
+
} catch {
|
|
263690
|
+
}
|
|
263691
|
+
}
|
|
263692
|
+
if (generation !== this.modelReportGeneration) return;
|
|
263693
|
+
if (out.length === 0) {
|
|
263694
|
+
if (opts.requestId) reply([]);
|
|
263695
|
+
return;
|
|
263696
|
+
}
|
|
263697
|
+
this.lastModelReport = out;
|
|
263698
|
+
this.lastModelReportAt = Date.now();
|
|
263699
|
+
reply(out);
|
|
263700
|
+
log2("[node-cli]", `\u5DF2\u4E0A\u62A5\u5019\u9009\u6A21\u578B\uFF1A${out.map((r) => `${r.kind}=${r.models.length}`).join(" ")}`);
|
|
263701
|
+
}
|
|
262473
263702
|
buildMeta() {
|
|
262474
263703
|
const activeSessions = this.sessions.activeSessions();
|
|
262475
263704
|
return {
|
|
@@ -263050,7 +264279,7 @@ var COMMAND_DECLS = {
|
|
|
263050
264279
|
{ 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
264280
|
{ 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
264281
|
{ 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" },
|
|
264282
|
+
{ 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
264283
|
{ name: "status", desc: "\u770B\u4E00\u4E2A\u59D4\u6D3E\u7684\u8BE6\u60C5\uFF1A\u5168\u91CF\u5404\u8F6E\u5C0F\u7ED3 + \u4EA7\u7269\u6E05\u5355" },
|
|
263055
264284
|
{ 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
264285
|
{ name: "all", desc: "\u914D --list\uFF1A\u8FDE\u5386\u53F2\u59D4\u6D3E\u4E00\u8D77\u5217", boolean: true },
|
|
@@ -265536,15 +266765,26 @@ ${round.text}`);
|
|
|
265536
266765
|
const text5 = flags.get("text");
|
|
265537
266766
|
if (!text5) throw new Error('--continue \u8981\u914D --text "<\u8981\u8F6C\u8FBE\u7684\u8BDD>"');
|
|
265538
266767
|
const target = await resolveRef2(continueRef);
|
|
265539
|
-
await api.post(
|
|
266768
|
+
const outcome = await api.post(
|
|
265540
266769
|
`/api/delegations/${encodeURIComponent(target.id)}/messages`,
|
|
265541
266770
|
{ text: text5, ...fileArgs.length ? { files: fileArgs } : {} }
|
|
265542
266771
|
);
|
|
265543
266772
|
if (asJson2) {
|
|
265544
|
-
println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs }, null, 2));
|
|
266773
|
+
println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs, ...outcome }, null, 2));
|
|
265545
266774
|
break;
|
|
265546
266775
|
}
|
|
265547
|
-
|
|
266776
|
+
const who = `${target.expertName ?? target.expertActorId}\uFF08${target.label ?? target.id.slice(0, 8)}\uFF09`;
|
|
266777
|
+
if (outcome?.outcome === "inserted") {
|
|
266778
|
+
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`);
|
|
266779
|
+
} else if (outcome?.outcome === "started-round") {
|
|
266780
|
+
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`);
|
|
266781
|
+
} else if (outcome?.outcome === "queued") {
|
|
266782
|
+
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`);
|
|
266783
|
+
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`);
|
|
266784
|
+
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");
|
|
266785
|
+
} else {
|
|
266786
|
+
println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\u3002`);
|
|
266787
|
+
}
|
|
265548
266788
|
if (fileArgs.length) println(` \u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF1A${fileArgs.join("\u3001")}`);
|
|
265549
266789
|
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
266790
|
break;
|
|
@@ -268340,7 +269580,7 @@ function shimScript() {
|
|
|
268340
269580
|
}
|
|
268341
269581
|
|
|
268342
269582
|
// src/index.ts
|
|
268343
|
-
var PKG_VERSION = true ? "2.2.
|
|
269583
|
+
var PKG_VERSION = true ? "2.2.4" : "dev";
|
|
268344
269584
|
var LOCAL_BIN = localBin();
|
|
268345
269585
|
var NPM_PREFIX = npmPrefix();
|
|
268346
269586
|
var INSTANCE = DEFAULT_INSTANCE;
|