oasis_test_v2 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2043 -513
- 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
|
|
@@ -11107,13 +11181,22 @@ var init_store_postgres = __esm({
|
|
|
11107
11181
|
async close() {
|
|
11108
11182
|
await this.pool.end();
|
|
11109
11183
|
}
|
|
11184
|
+
/**
|
|
11185
|
+
* ★ 与 {@link tx} 同一条纪律:schema 名进的是**标识符位**,必须 {@link quoteIdent}。
|
|
11186
|
+
*
|
|
11187
|
+
* 这两个直连 pool 的方法(本方法与 {@link listNeverStartedWorks})是上一轮修连字符 schema 时
|
|
11188
|
+
* 漏掉的两处——`tx()` 那条路已经引号化了,它们没有。表现不是「一读就 500」而是**两口钟在带
|
|
11189
|
+
* 连字符的公司上永远转不动**:2026-09-09 v2 生产日志里 `[deadline-clock] [company:test-ryzs]`
|
|
11190
|
+
* 与 `[dispatch-drop]` 每 5 秒 / 每分钟各报一次 `syntax error at or near "-"`,
|
|
11191
|
+
* 一分钟约 50 行;也就是说这些公司的 deadline 最迟触发保证与丢单补派**从来没生效过**。
|
|
11192
|
+
*/
|
|
11110
11193
|
async listOpenWorkDeadlines(opts) {
|
|
11111
11194
|
const res = await this.pool.query(
|
|
11112
11195
|
`SELECT workorder_id AS "workorderId",
|
|
11113
11196
|
id AS "workId",
|
|
11114
11197
|
assignee_actor_id AS "assigneeActorId",
|
|
11115
11198
|
COALESCE(last_activity_at, started_at, created_at) AS "deadlineFrom"
|
|
11116
|
-
FROM ${this.schema}.works
|
|
11199
|
+
FROM ${quoteIdent(this.schema)}.works
|
|
11117
11200
|
WHERE ended_at IS NULL AND dead_at IS NULL AND cancelled_at IS NULL
|
|
11118
11201
|
AND ($1::text IS NULL OR id > $1)
|
|
11119
11202
|
ORDER BY id
|
|
@@ -11134,7 +11217,7 @@ var init_store_postgres = __esm({
|
|
|
11134
11217
|
node_id AS "nodeId",
|
|
11135
11218
|
assignee_actor_id AS "assigneeActorId",
|
|
11136
11219
|
created_at AS "createdAt"
|
|
11137
|
-
FROM ${this.schema}.works
|
|
11220
|
+
FROM ${quoteIdent(this.schema)}.works
|
|
11138
11221
|
WHERE ended_at IS NULL AND dead_at IS NULL AND cancelled_at IS NULL
|
|
11139
11222
|
AND started_at IS NULL AND session_ref IS NULL
|
|
11140
11223
|
AND created_at < $1::timestamptz
|
|
@@ -11280,6 +11363,7 @@ var init_store_postgres = __esm({
|
|
|
11280
11363
|
proposalReason: w2.proposal_reason ?? null,
|
|
11281
11364
|
lastActivityAt: iso(w2.last_activity_at),
|
|
11282
11365
|
frozenIssueIds: w2.frozen_issue_ids ?? null,
|
|
11366
|
+
handledIssueIds: w2.handled_issue_ids ?? null,
|
|
11283
11367
|
agentHandoffAttemptId: w2.agent_handoff_attempt_id ?? null,
|
|
11284
11368
|
businessHandoffStatus: w2.business_handoff_status ?? null,
|
|
11285
11369
|
businessHandoffRecordedAt: iso(w2.business_handoff_recorded_at),
|
|
@@ -11485,8 +11569,8 @@ var init_store_postgres = __esm({
|
|
|
11485
11569
|
(id,workorder_id,node_id,assignee_actor_id,created_at,started_at,ended_at,dead_at,cancelled_at,retry_at,status,outcome,
|
|
11486
11570
|
outcome_detail,session_ref,continues_work_id,parent_work_id,reply_to_issue_id,output_version_no,
|
|
11487
11571
|
node_version,conclusion,proposal_reason,last_activity_at,frozen_issue_ids,agent_handoff_attempt_id,business_handoff_status,business_handoff_recorded_at,
|
|
11488
|
-
acceptance_state,accepted_at,accepted_by,rejected_reason,override_by,override_reason,lane)
|
|
11489
|
-
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)
|
|
11490
11574
|
ON CONFLICT (id) DO NOTHING`,
|
|
11491
11575
|
[
|
|
11492
11576
|
w2.id,
|
|
@@ -11521,13 +11605,14 @@ var init_store_postgres = __esm({
|
|
|
11521
11605
|
w2.rejectedReason,
|
|
11522
11606
|
w2.overrideBy,
|
|
11523
11607
|
w2.overrideReason,
|
|
11524
|
-
w2.lane
|
|
11608
|
+
w2.lane,
|
|
11609
|
+
w2.handledIssueIds ? JSON.stringify(w2.handledIssueIds) : null
|
|
11525
11610
|
]
|
|
11526
11611
|
);
|
|
11527
11612
|
break;
|
|
11528
11613
|
}
|
|
11529
11614
|
case "work.update":
|
|
11530
|
-
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 });
|
|
11531
11616
|
break;
|
|
11532
11617
|
case "artifact.replace":
|
|
11533
11618
|
await this.c.query(`DELETE FROM ${this.s}.work_artifacts WHERE work_id = $1`, [m2.workId]);
|
|
@@ -11857,7 +11942,7 @@ function newNode(id, workorderId, at, version2) {
|
|
|
11857
11942
|
updatedAt: at
|
|
11858
11943
|
};
|
|
11859
11944
|
}
|
|
11860
|
-
function markNodeForRetry(state, nodeId, at) {
|
|
11945
|
+
function markNodeForRetry(state, nodeId, at, resetRejectedBudget = false) {
|
|
11861
11946
|
const node2 = state.node(nodeId);
|
|
11862
11947
|
if (!node2) return;
|
|
11863
11948
|
const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
|
|
@@ -11865,6 +11950,7 @@ function markNodeForRetry(state, nodeId, at) {
|
|
|
11865
11950
|
state.updateWork(lw.id, { retryAt: at, status: "retry" });
|
|
11866
11951
|
}
|
|
11867
11952
|
if (lw && !lw.deadAt && workState(lw, hasOutput(lw, state.artifactsOf(lw.id).length)) === "success") {
|
|
11953
|
+
if (resetRejectedBudget && lw.acceptanceState === "rejected") state.updateWork(lw.id, { retryAt: at });
|
|
11868
11954
|
for (const req of state.requirementsOf(node2.id)) {
|
|
11869
11955
|
if (!req.latestReviewId) continue;
|
|
11870
11956
|
const r = state.review(req.latestReviewId);
|
|
@@ -11996,6 +12082,9 @@ var init_plan = __esm({
|
|
|
11996
12082
|
const changed = node2.assigneeActorId !== e.assigneeActorId;
|
|
11997
12083
|
const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
|
|
11998
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
|
+
}
|
|
11999
12088
|
state.updateNode(e.nodeId, {
|
|
12000
12089
|
assigneeActorId: e.assigneeActorId,
|
|
12001
12090
|
assigneeRole: e.assigneeRole,
|
|
@@ -12025,7 +12114,7 @@ var init_plan = __esm({
|
|
|
12025
12114
|
name: "plan/node-retry",
|
|
12026
12115
|
kind: "plan.node_retry",
|
|
12027
12116
|
apply(e, state, ctx) {
|
|
12028
|
-
markNodeForRetry(state, e.nodeId, ctx.at);
|
|
12117
|
+
markNodeForRetry(state, e.nodeId, ctx.at, e.resetRejectedBudget === true);
|
|
12029
12118
|
}
|
|
12030
12119
|
};
|
|
12031
12120
|
planUpdateFields = {
|
|
@@ -12158,7 +12247,13 @@ var init_workorder = __esm({
|
|
|
12158
12247
|
apply(e, state, ctx) {
|
|
12159
12248
|
state.updateWorkorder({ dispatchPaused: false, pausedBy: null, pausedReason: null, updatedAt: ctx.at });
|
|
12160
12249
|
for (const node2 of state.liveNodes()) {
|
|
12161
|
-
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);
|
|
12162
12257
|
}
|
|
12163
12258
|
}
|
|
12164
12259
|
};
|
|
@@ -12172,8 +12267,8 @@ var init_workorder = __esm({
|
|
|
12172
12267
|
});
|
|
12173
12268
|
|
|
12174
12269
|
// ../engine/src/handlers/work.ts
|
|
12175
|
-
function latestProposedOf(state, nodeId) {
|
|
12176
|
-
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));
|
|
12177
12272
|
return successes.length > 0 ? successes[successes.length - 1].id : null;
|
|
12178
12273
|
}
|
|
12179
12274
|
var workCreate, workKill, workResponse, workHandoffRecorded, workStart, workSubmitOutput, workContentEdited, workConclude, nodeLatestProposedRebased, workTimeout;
|
|
@@ -12188,6 +12283,7 @@ var init_work = __esm({
|
|
|
12188
12283
|
name: "work/create",
|
|
12189
12284
|
kind: "work.create",
|
|
12190
12285
|
apply(e, state, ctx) {
|
|
12286
|
+
if (e.pauseGateVersion === 1 && state.workorder.dispatchPaused) return;
|
|
12191
12287
|
if (state.work(e.workId)) return;
|
|
12192
12288
|
const node2 = state.node(e.nodeId);
|
|
12193
12289
|
if (!node2) return;
|
|
@@ -12213,6 +12309,7 @@ var init_work = __esm({
|
|
|
12213
12309
|
replyToIssueId: e.replyToIssueId,
|
|
12214
12310
|
outputVersionNo: null,
|
|
12215
12311
|
conclusion: null,
|
|
12312
|
+
handledIssueIds: null,
|
|
12216
12313
|
agentHandoffAttemptId: null,
|
|
12217
12314
|
businessHandoffStatus: null,
|
|
12218
12315
|
businessHandoffRecordedAt: null,
|
|
@@ -12252,6 +12349,7 @@ var init_work = __esm({
|
|
|
12252
12349
|
outputVersionNo: null,
|
|
12253
12350
|
conclusion: null,
|
|
12254
12351
|
frozenIssueIds: frozen.length > 0 ? frozen : null,
|
|
12352
|
+
handledIssueIds: null,
|
|
12255
12353
|
agentHandoffAttemptId: null,
|
|
12256
12354
|
businessHandoffStatus: null,
|
|
12257
12355
|
businessHandoffRecordedAt: null,
|
|
@@ -12286,6 +12384,7 @@ var init_work = __esm({
|
|
|
12286
12384
|
// 会话侧也要杀(旧 agent 可能还挂着)。cancelPreviousWork 找不到/会话已退则 no-op。
|
|
12287
12385
|
// 回信 work 不杀前会话(apply 不 kill 主链,节点已完结、无在跑会话),只派发。
|
|
12288
12386
|
async effect(e, ctx) {
|
|
12387
|
+
if (e.pauseGateVersion === 1 && !await ctx.canDispatchWork(e.workorderId, e.workId)) return;
|
|
12289
12388
|
if (!e.replyToIssueId) await ctx.cancelPreviousWork(e.nodeId, e.workId);
|
|
12290
12389
|
await ctx.dispatchWork(e.workId, e.resumeEligible !== void 0 ? { resumeEligible: e.resumeEligible } : void 0);
|
|
12291
12390
|
}
|
|
@@ -12316,7 +12415,7 @@ var init_work = __esm({
|
|
|
12316
12415
|
if (node2.latestProposedWorkId) {
|
|
12317
12416
|
const ls = state.work(node2.latestProposedWorkId);
|
|
12318
12417
|
if (!ls || ls.status !== "success") {
|
|
12319
|
-
state.updateNode(e.nodeId, { latestProposedWorkId: latestProposedOf(state, e.nodeId) });
|
|
12418
|
+
state.updateNode(e.nodeId, { latestProposedWorkId: latestProposedOf(state, e.nodeId, e.semanticsVersion === 2) });
|
|
12320
12419
|
}
|
|
12321
12420
|
}
|
|
12322
12421
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
@@ -12346,17 +12445,34 @@ var init_work = __esm({
|
|
|
12346
12445
|
}
|
|
12347
12446
|
const businessFailed = w2.businessHandoffStatus === "not_delivered";
|
|
12348
12447
|
const artifactCount = state.artifactsOf(e.workId).length;
|
|
12349
|
-
const pureReply = e.semanticsVersion
|
|
12350
|
-
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));
|
|
12351
12450
|
const status = succeeded ? "success" : "failed";
|
|
12352
|
-
|
|
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;
|
|
12353
12469
|
state.updateWork(e.workId, {
|
|
12354
12470
|
endedAt: ctx.at,
|
|
12355
12471
|
outcome: businessFailed ? "failed" : e.outcome,
|
|
12356
12472
|
outcomeDetail: businessFailed ? { ...e.detail ?? {}, reason: "agent business handoff not delivered" } : zeroOutputReason ? { ...e.detail ?? {}, reason: e.detail?.reason ?? zeroOutputReason } : e.detail,
|
|
12357
12473
|
status
|
|
12358
12474
|
});
|
|
12359
|
-
if (status === "success" && !(e.semanticsVersion
|
|
12475
|
+
if (status === "success" && !((e.semanticsVersion ?? 1) >= 2 && artifactCount === 0)) {
|
|
12360
12476
|
state.updateNode(w2.nodeId, { latestProposedWorkId: e.workId });
|
|
12361
12477
|
}
|
|
12362
12478
|
}
|
|
@@ -12380,7 +12496,7 @@ var init_work = __esm({
|
|
|
12380
12496
|
} : {}
|
|
12381
12497
|
});
|
|
12382
12498
|
if (flipsToFailed && state.node(w2.nodeId)?.latestProposedWorkId === e.workId) {
|
|
12383
|
-
state.updateNode(w2.nodeId, { latestProposedWorkId: latestProposedOf(state, w2.nodeId) });
|
|
12499
|
+
state.updateNode(w2.nodeId, { latestProposedWorkId: latestProposedOf(state, w2.nodeId, e.semanticsVersion === 2) });
|
|
12384
12500
|
}
|
|
12385
12501
|
}
|
|
12386
12502
|
};
|
|
@@ -12498,6 +12614,7 @@ var init_issue = __esm({
|
|
|
12498
12614
|
"../engine/src/handlers/issue.ts"() {
|
|
12499
12615
|
"use strict";
|
|
12500
12616
|
init_plan();
|
|
12617
|
+
init_letters();
|
|
12501
12618
|
issueCreate = {
|
|
12502
12619
|
name: "issue/create",
|
|
12503
12620
|
kind: "issue.create",
|
|
@@ -12568,6 +12685,11 @@ var init_issue = __esm({
|
|
|
12568
12685
|
// 缺失(人 / chat 会话 / 存量事件)落 null,判据按"不是节点发言"处理。
|
|
12569
12686
|
viaWorkId: e.viaNodeId ? state.node(e.viaNodeId)?.latestWorkId ?? null : null
|
|
12570
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
|
+
}
|
|
12571
12693
|
}
|
|
12572
12694
|
};
|
|
12573
12695
|
issueResolve = {
|
|
@@ -12575,7 +12697,13 @@ var init_issue = __esm({
|
|
|
12575
12697
|
kind: "issue.resolve",
|
|
12576
12698
|
apply(e, state, ctx) {
|
|
12577
12699
|
const issue2 = state.issue(e.issueId);
|
|
12578
|
-
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;
|
|
12579
12707
|
const humanResolution = e.by.startsWith("actor:human:") && e.by === ctx.actorId;
|
|
12580
12708
|
if (e.semanticsVersion === 2 && issue2.kind === "escalation" && !humanResolution) {
|
|
12581
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");
|
|
@@ -12590,7 +12718,7 @@ var init_issue = __esm({
|
|
|
12590
12718
|
resolvedNote: e.note,
|
|
12591
12719
|
resolvedViaWorkId: e.viaWorkId
|
|
12592
12720
|
});
|
|
12593
|
-
if (issue2.kind === "gap" && (e.semanticsVersion !== 2 || humanResolution)) {
|
|
12721
|
+
if (issue2.kind === "gap" && e.closeRelatedEscalations !== false && (e.semanticsVersion !== 2 || humanResolution)) {
|
|
12594
12722
|
for (const other of state.allIssues()) {
|
|
12595
12723
|
if (other.kind === "escalation" && other.resolvedAt === null && other.workorderId === issue2.workorderId && other.gapId === issue2.id) {
|
|
12596
12724
|
state.updateIssue(other.id, {
|
|
@@ -12608,7 +12736,7 @@ var init_issue = __esm({
|
|
|
12608
12736
|
}
|
|
12609
12737
|
if (issue2.kind === "gap" || issue2.kind === "escalation") {
|
|
12610
12738
|
const nodeId = issue2.raisedByNodeId ?? issue2.aboutNodeId;
|
|
12611
|
-
if (nodeId) markNodeForRetry(state, nodeId, ctx.at);
|
|
12739
|
+
if (nodeId) markNodeForRetry(state, nodeId, ctx.at, e.resetRejectedBudget === true);
|
|
12612
12740
|
}
|
|
12613
12741
|
}
|
|
12614
12742
|
};
|
|
@@ -12646,6 +12774,7 @@ var init_review = __esm({
|
|
|
12646
12774
|
name: "review/create",
|
|
12647
12775
|
kind: "review.create",
|
|
12648
12776
|
apply(e, state, ctx) {
|
|
12777
|
+
if (e.pauseGateVersion === 1 && state.workorder.dispatchPaused) return;
|
|
12649
12778
|
const work = state.work(e.targetWorkId);
|
|
12650
12779
|
if (!work) return;
|
|
12651
12780
|
const dup = state.reviewsOf(e.targetWorkId).some(
|
|
@@ -12681,6 +12810,7 @@ var init_review = __esm({
|
|
|
12681
12810
|
}
|
|
12682
12811
|
},
|
|
12683
12812
|
async effect(e, ctx) {
|
|
12813
|
+
if (e.pauseGateVersion === 1 && !await ctx.canDispatchReview(e.workorderId, e.reviewId)) return;
|
|
12684
12814
|
if (e.reviewerIsAgent) await ctx.dispatchReview(e.reviewId, e.nodeId, e.reviewerActorId);
|
|
12685
12815
|
else await ctx.notify([e.reviewerActorId], { kind: "review-requested", reviewId: e.reviewId });
|
|
12686
12816
|
}
|
|
@@ -12987,11 +13117,28 @@ var init_handlers = __esm({
|
|
|
12987
13117
|
});
|
|
12988
13118
|
|
|
12989
13119
|
// ../engine/src/bus.ts
|
|
12990
|
-
function
|
|
12991
|
-
if (event.kind === "work.
|
|
12992
|
-
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;
|
|
12993
13141
|
}
|
|
12994
|
-
return event;
|
|
12995
13142
|
}
|
|
12996
13143
|
function emptySnapshotPlaceholder(record8) {
|
|
12997
13144
|
return {
|
|
@@ -13199,7 +13346,7 @@ var init_bus = __esm({
|
|
|
13199
13346
|
workorderId: input.workorderId,
|
|
13200
13347
|
actorId: input.actorId,
|
|
13201
13348
|
handActorId: input.handActorId ?? null,
|
|
13202
|
-
event:
|
|
13349
|
+
event: versionCommandEvent(input.event),
|
|
13203
13350
|
causedBySeq: null,
|
|
13204
13351
|
origin: "command",
|
|
13205
13352
|
dedupKey: input.dedupKey ?? null,
|
|
@@ -13224,6 +13371,32 @@ var init_bus = __esm({
|
|
|
13224
13371
|
}
|
|
13225
13372
|
}
|
|
13226
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
|
+
}
|
|
13227
13400
|
/* ═══════════════════════ 调度器主循环 ═══════════════════════ */
|
|
13228
13401
|
/**
|
|
13229
13402
|
* 启动调度器——永不返回。
|
|
@@ -13459,7 +13632,7 @@ var init_bus = __esm({
|
|
|
13459
13632
|
workorderId: record8.workorderId,
|
|
13460
13633
|
actorId: SYSTEM_ACTOR,
|
|
13461
13634
|
handActorId: null,
|
|
13462
|
-
event:
|
|
13635
|
+
event: versionCommandEvent(event),
|
|
13463
13636
|
causedBySeq: record8.seq,
|
|
13464
13637
|
origin: "apply",
|
|
13465
13638
|
dedupKey: null,
|
|
@@ -13490,6 +13663,18 @@ var init_bus = __esm({
|
|
|
13490
13663
|
// ★ 注入 workorderId:派发侧读模型没追上时,据它指名重读那张工单(见 EngineIO.dispatchWork 注释)。
|
|
13491
13664
|
dispatchWork: (id, opts) => this.io.dispatchWork(id, { ...opts, workorderId: record8.workorderId }),
|
|
13492
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
|
+
},
|
|
13493
13678
|
cancelSession: (ref2) => this.io.cancelSession(ref2),
|
|
13494
13679
|
// ★ work.create 的「kill 旧会话」:apply 已把该节点上一个 latest_work 置 deadAt(会话可能还挂着),
|
|
13495
13680
|
// 这里按 store 找它 cancel。找不到 / 不是刚被 kill / 会话已退 → no-op。这是设计「节点重新运行 = 自动
|
|
@@ -13556,7 +13741,7 @@ var init_bus = __esm({
|
|
|
13556
13741
|
workorderId: record8.workorderId,
|
|
13557
13742
|
actorId: b2.actorId ?? SYSTEM_ACTOR,
|
|
13558
13743
|
handActorId: null,
|
|
13559
|
-
event:
|
|
13744
|
+
event: versionCommandEvent(b2.event),
|
|
13560
13745
|
causedBySeq: record8.seq,
|
|
13561
13746
|
origin: "effect",
|
|
13562
13747
|
dedupKey: b2.dedupKey ?? null,
|
|
@@ -13594,7 +13779,7 @@ var init_bus = __esm({
|
|
|
13594
13779
|
workorderId,
|
|
13595
13780
|
actorId: SYSTEM_ACTOR,
|
|
13596
13781
|
handActorId: null,
|
|
13597
|
-
event:
|
|
13782
|
+
event: versionCommandEvent(event),
|
|
13598
13783
|
causedBySeq: null,
|
|
13599
13784
|
origin: "apply",
|
|
13600
13785
|
dedupKey: null,
|
|
@@ -13646,7 +13831,7 @@ var init_bus = __esm({
|
|
|
13646
13831
|
workorderId: record8.workorderId,
|
|
13647
13832
|
actorId: SYSTEM_ACTOR,
|
|
13648
13833
|
handActorId: null,
|
|
13649
|
-
event:
|
|
13834
|
+
event: versionCommandEvent(event),
|
|
13650
13835
|
causedBySeq: record8.seq,
|
|
13651
13836
|
origin: "apply",
|
|
13652
13837
|
dedupKey: null,
|
|
@@ -14492,6 +14677,7 @@ async function assembleContext(args) {
|
|
|
14492
14677
|
const siblingParts = args.part !== void 0 ? (artifact.parts ?? []).filter((p2) => p2.name !== args.part) : [];
|
|
14493
14678
|
const consumers = consumersOf(model, artifactId).filter((c) => c.type !== DELIVERY_SUMMARY_ARTIFACT_TYPE);
|
|
14494
14679
|
const staleSet = staleSetOf(model, artifactId);
|
|
14680
|
+
const acceptedHead = concludedHead(model, artifactId);
|
|
14495
14681
|
const head = artifact.currentRev;
|
|
14496
14682
|
const headRev = head !== null ? model.revisions.get(head) : null;
|
|
14497
14683
|
const headSpread = headRev !== null ? await spreadRevisionContent(blobs, headRev, files, "deliverable") : null;
|
|
@@ -14807,10 +14993,19 @@ ${manifestDiff(baseC, newC)}
|
|
|
14807
14993
|
} else if (head === null) {
|
|
14808
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`);
|
|
14809
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");
|
|
14810
14999
|
} else {
|
|
14811
15000
|
whyLines.push(`**\u7EE7\u7EED\u5B8C\u5584**\uFF1A\u4E0A\u6B21\u672A conclude\u3002`);
|
|
14812
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`);
|
|
14813
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
|
+
}
|
|
14814
15009
|
if (pendingConcludeAttempt(model, artifactId) !== null) {
|
|
14815
15010
|
whyLines.push(
|
|
14816
15011
|
``,
|
|
@@ -18040,7 +18235,7 @@ function bridgeLoadReadModel(input) {
|
|
|
18040
18235
|
const art = nodeToArtifact(node2, edgesByTarget.get(node2.id) ?? [], requirementsByNode.get(node2.id) ?? []);
|
|
18041
18236
|
const lw = node2.latestWorkId ? workById.get(node2.latestWorkId) : void 0;
|
|
18042
18237
|
if (lw) {
|
|
18043
|
-
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");
|
|
18044
18239
|
const hasConclusionWithoutResponse = lw.conclusion !== null && lw.conclusion !== void 0 && st === "running" && !lw.endedAt;
|
|
18045
18240
|
let uiSt;
|
|
18046
18241
|
if (st === "retry") uiSt = null;
|
|
@@ -18543,22 +18738,18 @@ ${res.candidates.map((c) => ` - ${c.type}:${c.title ?? "(\u65E0title)"} \u2192
|
|
|
18543
18738
|
return { ...plan, ops };
|
|
18544
18739
|
}
|
|
18545
18740
|
function buildConstituents(model, plan, schema, roleIndex) {
|
|
18546
|
-
const
|
|
18547
|
-
plan.ops.filter((o) => o.action === "unlink").map((o) => `${o.artifactId} ${o.to}`)
|
|
18548
|
-
);
|
|
18741
|
+
const inputs = new Map([...model.artifacts].map(([id, art]) => [id, new Set(art.inputs.map((i) => i.to))]));
|
|
18549
18742
|
const out = [];
|
|
18550
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);
|
|
18551
18747
|
if (c.kind === "seal" && c.payload.reason === "cancelled") {
|
|
18552
|
-
for (const
|
|
18553
|
-
const
|
|
18554
|
-
|
|
18555
|
-
|
|
18556
|
-
|
|
18557
|
-
artifactId: edge.artifactId,
|
|
18558
|
-
kind: "unlink_input",
|
|
18559
|
-
target: edge.artifactId,
|
|
18560
|
-
payload: { to: edge.to }
|
|
18561
|
-
});
|
|
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
|
+
}
|
|
18562
18753
|
}
|
|
18563
18754
|
}
|
|
18564
18755
|
out.push(c);
|
|
@@ -18897,6 +19088,10 @@ function filterReadModelByWorkspace(model, workspace) {
|
|
|
18897
19088
|
pinMeta: new Map([...model.pinMeta].filter(([artId]) => myArtIds.has(artId)))
|
|
18898
19089
|
};
|
|
18899
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
|
+
}
|
|
18900
19095
|
async function mapWithConcurrency(items, limit, fn) {
|
|
18901
19096
|
if (items.length === 0) return [];
|
|
18902
19097
|
const out = new Array(items.length);
|
|
@@ -19532,6 +19727,7 @@ var init_kernel_bridge = __esm({
|
|
|
19532
19727
|
}
|
|
19533
19728
|
if (!workId) {
|
|
19534
19729
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
19730
|
+
assertWorkorderDispatchActive(snap);
|
|
19535
19731
|
const nodeRow = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
19536
19732
|
const headId = nodeRow?.latestWorkId ?? null;
|
|
19537
19733
|
if (headId) {
|
|
@@ -19953,6 +20149,30 @@ var init_kernel_bridge = __esm({
|
|
|
19953
20149
|
});
|
|
19954
20150
|
}
|
|
19955
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
|
+
}
|
|
19956
20176
|
const ann = this.model.annotations.get(args.annotationId);
|
|
19957
20177
|
if (!ann) throw new Error(`annotation not found: ${args.annotationId}`);
|
|
19958
20178
|
if (isHumanChangeRequest(ann) && args.actor.startsWith("actor:agent:") && args.resolution === "resolved" && !args.via) {
|
|
@@ -19971,7 +20191,9 @@ var init_kernel_bridge = __esm({
|
|
|
19971
20191
|
state: args.resolution === "wontfix" ? "closed" : "resolved",
|
|
19972
20192
|
by: args.actor,
|
|
19973
20193
|
note: args.note ?? null,
|
|
19974
|
-
viaWorkId: args.via ?? null
|
|
20194
|
+
viaWorkId: args.via ?? null,
|
|
20195
|
+
...args.viaNode ? { viaNodeId: args.viaNode } : {},
|
|
20196
|
+
...args.resolution === "acknowledged" ? { acknowledged: true } : {}
|
|
19975
20197
|
}
|
|
19976
20198
|
});
|
|
19977
20199
|
}
|
|
@@ -20037,7 +20259,7 @@ var init_kernel_bridge = __esm({
|
|
|
20037
20259
|
for (const [artifactId, gaps] of this.model.gaps) {
|
|
20038
20260
|
if (gaps.some((g2) => g2.gapId === args.gapId && !g2.resolved)) {
|
|
20039
20261
|
const wid = this.wo(artifactId);
|
|
20040
|
-
|
|
20262
|
+
const events = [{
|
|
20041
20263
|
companyId: "",
|
|
20042
20264
|
workorderId: wid,
|
|
20043
20265
|
actorId: args.actor,
|
|
@@ -20050,16 +20272,31 @@ var init_kernel_bridge = __esm({
|
|
|
20050
20272
|
note: args.note ?? null,
|
|
20051
20273
|
viaWorkId: null
|
|
20052
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
|
+
}
|
|
20053
20296
|
});
|
|
20054
|
-
|
|
20055
|
-
|
|
20056
|
-
|
|
20057
|
-
actor: args.actor,
|
|
20058
|
-
kind: "comment",
|
|
20059
|
-
annotationId: `ann:rework:${args.gapId}`,
|
|
20060
|
-
body: args.note ?? "\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C\u8BF7\u6309\u7B54\u590D\u91CD\u505A"
|
|
20061
|
-
});
|
|
20062
|
-
}
|
|
20297
|
+
await this.bus.submitBatchAndProcess(events);
|
|
20298
|
+
this.markWorkorderDirty(wid);
|
|
20299
|
+
await this.refreshTracked();
|
|
20063
20300
|
return;
|
|
20064
20301
|
}
|
|
20065
20302
|
}
|
|
@@ -20108,6 +20345,57 @@ var init_kernel_bridge = __esm({
|
|
|
20108
20345
|
}
|
|
20109
20346
|
});
|
|
20110
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
|
+
}
|
|
20111
20399
|
async resolveEscalation(args) {
|
|
20112
20400
|
if (!args.actor.startsWith("actor:human:")) {
|
|
20113
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");
|
|
@@ -20312,6 +20600,7 @@ var init_kernel_bridge = __esm({
|
|
|
20312
20600
|
async rerunNode(args) {
|
|
20313
20601
|
const wid = this.wo(args.artifactId);
|
|
20314
20602
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
20603
|
+
assertWorkorderDispatchActive(snap);
|
|
20315
20604
|
const node2 = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
20316
20605
|
if (!node2 || node2.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
|
|
20317
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");
|
|
@@ -20361,6 +20650,7 @@ var init_kernel_bridge = __esm({
|
|
|
20361
20650
|
async rereviewNode(args) {
|
|
20362
20651
|
const wid = this.wo(args.artifactId);
|
|
20363
20652
|
const snap = await this.store.transaction((tx) => tx.loadWorkorder(wid));
|
|
20653
|
+
assertWorkorderDispatchActive(snap);
|
|
20364
20654
|
const node2 = snap?.nodes.find((n) => n.id === args.artifactId);
|
|
20365
20655
|
if (!node2 || node2.cancelledAt) throw new KernelError(`\u8282\u70B9\u4E0D\u5B58\u5728\u6216\u5DF2\u4F5C\u5E9F\uFF1A${args.artifactId}`, "not-found");
|
|
20366
20656
|
const lw = node2.latestWorkId ? snap.works.find((w2) => w2.id === node2.latestWorkId) : null;
|
|
@@ -20563,12 +20853,9 @@ var init_kernel_bridge = __esm({
|
|
|
20563
20853
|
throw new Error(`resolveEscalation \u6388\u6743\u5361\u7F3A escalationId\uFF1A${args.draftId}`);
|
|
20564
20854
|
}
|
|
20565
20855
|
const reason = typeof card2.commandArgs?.["reason"] === "string" ? card2.commandArgs["reason"] : void 0;
|
|
20566
|
-
|
|
20567
|
-
|
|
20568
|
-
|
|
20569
|
-
escalationId,
|
|
20570
|
-
...reason !== void 0 ? { reason } : {}
|
|
20571
|
-
});
|
|
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 } : {} });
|
|
20572
20859
|
} else {
|
|
20573
20860
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u4E0D\u652F\u6301\u6267\u884C ${card2.command}`);
|
|
20574
20861
|
}
|
|
@@ -20612,11 +20899,29 @@ var init_kernel_bridge = __esm({
|
|
|
20612
20899
|
if (preview2.changeClass === "C" && !_actor.startsWith("actor:human:")) {
|
|
20613
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");
|
|
20614
20901
|
}
|
|
20615
|
-
const wid =
|
|
20902
|
+
const wid = ws ?? plan.ops.find((o) => o.action === "spawn" && o.workspace)?.workspace ?? "ws:default";
|
|
20616
20903
|
let applied = 0;
|
|
20617
20904
|
const unsupported = [];
|
|
20618
20905
|
const reviewerStaffing = [];
|
|
20619
|
-
|
|
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 });
|
|
20620
20925
|
for (const op of plan.ops) {
|
|
20621
20926
|
switch (op.action) {
|
|
20622
20927
|
case "spawn": {
|
|
@@ -20648,7 +20953,7 @@ var init_kernel_bridge = __esm({
|
|
|
20648
20953
|
// ★ executionEnv 同走 fields jsonb(与路径 A 对称——两条 spawn 路径必须一致,前车之鉴:类型默认评审闸)。
|
|
20649
20954
|
...op.executionEnv !== void 0 ? { executionEnv: op.executionEnv } : {}
|
|
20650
20955
|
} : opFields;
|
|
20651
|
-
await
|
|
20956
|
+
await queue({
|
|
20652
20957
|
companyId: "",
|
|
20653
20958
|
workorderId: wid,
|
|
20654
20959
|
actorId: _actor,
|
|
@@ -20678,27 +20983,64 @@ var init_kernel_bridge = __esm({
|
|
|
20678
20983
|
activateWorkorder: false
|
|
20679
20984
|
}
|
|
20680
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
|
+
});
|
|
20681
20998
|
applied++;
|
|
20682
20999
|
break;
|
|
20683
21000
|
}
|
|
20684
|
-
case "link":
|
|
20685
|
-
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 });
|
|
20686
21005
|
applied++;
|
|
20687
21006
|
break;
|
|
20688
|
-
|
|
20689
|
-
|
|
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);
|
|
20690
21012
|
applied++;
|
|
20691
21013
|
break;
|
|
21014
|
+
}
|
|
20692
21015
|
case "assign":
|
|
20693
|
-
await
|
|
21016
|
+
await enqueue({ kind: "plan.assign_actor", workorderId: woFor(op.artifactId), nodeId: op.artifactId, assigneeActorId: op.owner, assigneeRole: null });
|
|
20694
21017
|
applied++;
|
|
20695
21018
|
break;
|
|
20696
21019
|
case "seal":
|
|
20697
|
-
|
|
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 });
|
|
20698
21027
|
applied++;
|
|
20699
21028
|
break;
|
|
20700
21029
|
case "annotate":
|
|
20701
|
-
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
|
+
});
|
|
20702
21044
|
applied++;
|
|
20703
21045
|
break;
|
|
20704
21046
|
case "edit": {
|
|
@@ -20706,7 +21048,7 @@ var init_kernel_bridge = __esm({
|
|
|
20706
21048
|
const opReviewers = op.reviewers;
|
|
20707
21049
|
const editReviewRoles = op.reviewRoles;
|
|
20708
21050
|
if (artId && (opReviewers || editReviewRoles !== void 0)) {
|
|
20709
|
-
const art =
|
|
21051
|
+
const art = artifacts.get(artId);
|
|
20710
21052
|
const effectiveRoles = editReviewRoles !== void 0 ? editReviewRoles === null ? void 0 : editReviewRoles : art?.reviewRoles;
|
|
20711
21053
|
const effectiveExplicit = opReviewers ?? (art?.reviewers ?? []).map((r) => ({ actor: r.actor, source: r.source }));
|
|
20712
21054
|
const { reviewers: nextReqs, staffingIssues } = resolveNodeReviewers({
|
|
@@ -20720,13 +21062,13 @@ var init_kernel_bridge = __esm({
|
|
|
20720
21062
|
reviewerStaffing.push({ nodeId: artId, type: art?.type ?? "", role: issue2.role, from: issue2.from });
|
|
20721
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`);
|
|
20722
21064
|
}
|
|
20723
|
-
await
|
|
21065
|
+
await queue({
|
|
20724
21066
|
companyId: "",
|
|
20725
|
-
workorderId:
|
|
21067
|
+
workorderId: woFor(artId),
|
|
20726
21068
|
actorId: _actor,
|
|
20727
21069
|
event: {
|
|
20728
21070
|
kind: "plan.update_review_requirements",
|
|
20729
|
-
workorderId:
|
|
21071
|
+
workorderId: woFor(artId),
|
|
20730
21072
|
nodeId: artId,
|
|
20731
21073
|
reviewers: nextReqs
|
|
20732
21074
|
}
|
|
@@ -20735,15 +21077,15 @@ var init_kernel_bridge = __esm({
|
|
|
20735
21077
|
const opTitle = op.title;
|
|
20736
21078
|
const opDescription = op.description;
|
|
20737
21079
|
if (artId && (opTitle !== void 0 || opDescription !== void 0)) {
|
|
20738
|
-
await
|
|
21080
|
+
await queue({
|
|
20739
21081
|
companyId: "",
|
|
20740
|
-
workorderId:
|
|
21082
|
+
workorderId: woFor(artId),
|
|
20741
21083
|
actorId: _actor,
|
|
20742
21084
|
event: {
|
|
20743
21085
|
kind: "plan.update_spec",
|
|
20744
|
-
workorderId:
|
|
21086
|
+
workorderId: woFor(artId),
|
|
20745
21087
|
nodeId: artId,
|
|
20746
|
-
spec: opDescription ??
|
|
21088
|
+
spec: opDescription ?? artifacts.get(artId)?.description ?? null,
|
|
20747
21089
|
...opTitle !== void 0 ? { title: opTitle } : {},
|
|
20748
21090
|
policy: "auto"
|
|
20749
21091
|
}
|
|
@@ -20751,21 +21093,30 @@ var init_kernel_bridge = __esm({
|
|
|
20751
21093
|
}
|
|
20752
21094
|
const fields = op.fields;
|
|
20753
21095
|
if (artId && fields) {
|
|
20754
|
-
const editDef = this.schema.get(
|
|
21096
|
+
const editDef = this.schema.get(artifacts.get(artId)?.type ?? "");
|
|
20755
21097
|
const validated = editDef?.fieldDefs ? validateArtifactFields(fields, editDef.fieldDefs, "edit") : fields;
|
|
20756
|
-
await
|
|
21098
|
+
await queue({
|
|
20757
21099
|
companyId: "",
|
|
20758
|
-
workorderId:
|
|
21100
|
+
workorderId: woFor(artId),
|
|
20759
21101
|
actorId: _actor,
|
|
20760
21102
|
event: {
|
|
20761
21103
|
kind: "plan.update_fields",
|
|
20762
|
-
workorderId:
|
|
21104
|
+
workorderId: woFor(artId),
|
|
20763
21105
|
nodeId: artId,
|
|
20764
21106
|
fields: validated ?? {},
|
|
20765
21107
|
merge: true
|
|
20766
21108
|
}
|
|
20767
21109
|
});
|
|
20768
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
|
+
});
|
|
20769
21120
|
applied++;
|
|
20770
21121
|
break;
|
|
20771
21122
|
}
|
|
@@ -20774,11 +21125,16 @@ var init_kernel_bridge = __esm({
|
|
|
20774
21125
|
if (!escOp.escalationId) {
|
|
20775
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`);
|
|
20776
21127
|
}
|
|
20777
|
-
|
|
20778
|
-
|
|
20779
|
-
|
|
20780
|
-
|
|
20781
|
-
|
|
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
|
|
20782
21138
|
});
|
|
20783
21139
|
applied++;
|
|
20784
21140
|
break;
|
|
@@ -20788,7 +21144,7 @@ var init_kernel_bridge = __esm({
|
|
|
20788
21144
|
break;
|
|
20789
21145
|
}
|
|
20790
21146
|
}
|
|
20791
|
-
await
|
|
21147
|
+
await queue({
|
|
20792
21148
|
companyId: "",
|
|
20793
21149
|
workorderId: wid,
|
|
20794
21150
|
actorId: _actor,
|
|
@@ -20803,17 +21159,22 @@ var init_kernel_bridge = __esm({
|
|
|
20803
21159
|
activateWorkorder: true
|
|
20804
21160
|
}
|
|
20805
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();
|
|
20806
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
|
+
}
|
|
20807
21174
|
if (typeof plan.resolvesProposal === "string") {
|
|
20808
21175
|
this.draftCards.delete(plan.resolvesProposal);
|
|
20809
21176
|
await this.refreshModel();
|
|
20810
21177
|
}
|
|
20811
|
-
if (unsupported.length > 0) {
|
|
20812
|
-
throw new KernelError(
|
|
20813
|
-
`\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`,
|
|
20814
|
-
"unsupported-op"
|
|
20815
|
-
);
|
|
20816
|
-
}
|
|
20817
21178
|
return {
|
|
20818
21179
|
interventionId: `intervention:${(0, import_node_crypto3.randomUUID)()}`,
|
|
20819
21180
|
applied,
|
|
@@ -20865,67 +21226,6 @@ var init_chat_system_message = __esm({
|
|
|
20865
21226
|
}
|
|
20866
21227
|
});
|
|
20867
21228
|
|
|
20868
|
-
// ../server/src/build-info.ts
|
|
20869
|
-
function findRepoRoot() {
|
|
20870
|
-
let dir;
|
|
20871
|
-
try {
|
|
20872
|
-
dir = path.dirname((0, import_node_url2.fileURLToPath)(__esm_import_meta_url));
|
|
20873
|
-
} catch {
|
|
20874
|
-
return null;
|
|
20875
|
-
}
|
|
20876
|
-
for (let i = 0; i < 12; i++) {
|
|
20877
|
-
if (fs.existsSync(path.join(dir, ".git"))) return dir;
|
|
20878
|
-
const parent = path.dirname(dir);
|
|
20879
|
-
if (parent === dir) break;
|
|
20880
|
-
dir = parent;
|
|
20881
|
-
}
|
|
20882
|
-
return null;
|
|
20883
|
-
}
|
|
20884
|
-
function git(root, args) {
|
|
20885
|
-
try {
|
|
20886
|
-
const out = (0, import_node_child_process.execFileSync)("git", args, {
|
|
20887
|
-
cwd: root,
|
|
20888
|
-
encoding: "utf8",
|
|
20889
|
-
timeout: 3e3,
|
|
20890
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
20891
|
-
});
|
|
20892
|
-
const v2 = out.trim();
|
|
20893
|
-
return v2 || null;
|
|
20894
|
-
} catch {
|
|
20895
|
-
return null;
|
|
20896
|
-
}
|
|
20897
|
-
}
|
|
20898
|
-
function getBuildInfo() {
|
|
20899
|
-
if (cached) return cached;
|
|
20900
|
-
const envSha = process.env["OASIS_GIT_SHA"]?.trim();
|
|
20901
|
-
const envBranch = process.env["OASIS_GIT_BRANCH"]?.trim();
|
|
20902
|
-
let gitSha = envSha || null;
|
|
20903
|
-
let gitBranch = envBranch || null;
|
|
20904
|
-
if (!gitSha || !gitBranch) {
|
|
20905
|
-
const root = findRepoRoot();
|
|
20906
|
-
if (root) {
|
|
20907
|
-
if (!gitSha) gitSha = git(root, ["rev-parse", "HEAD"]);
|
|
20908
|
-
if (!gitBranch) {
|
|
20909
|
-
const b2 = git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
20910
|
-
gitBranch = b2 === "HEAD" ? null : b2;
|
|
20911
|
-
}
|
|
20912
|
-
}
|
|
20913
|
-
}
|
|
20914
|
-
cached = { gitSha, gitBranch, startedAt: STARTED_AT };
|
|
20915
|
-
return cached;
|
|
20916
|
-
}
|
|
20917
|
-
var import_node_child_process, fs, path, import_node_url2, STARTED_AT, cached;
|
|
20918
|
-
var init_build_info = __esm({
|
|
20919
|
-
"../server/src/build-info.ts"() {
|
|
20920
|
-
"use strict";
|
|
20921
|
-
import_node_child_process = require("node:child_process");
|
|
20922
|
-
fs = __toESM(require("node:fs"), 1);
|
|
20923
|
-
path = __toESM(require("node:path"), 1);
|
|
20924
|
-
import_node_url2 = require("node:url");
|
|
20925
|
-
STARTED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
20926
|
-
}
|
|
20927
|
-
});
|
|
20928
|
-
|
|
20929
21229
|
// ../server/src/facades/api/router.ts
|
|
20930
21230
|
var ApiError, Router;
|
|
20931
21231
|
var init_router = __esm({
|
|
@@ -21071,6 +21371,32 @@ async function saveArtifactContent(kernel, blobs, actor, input) {
|
|
|
21071
21371
|
}
|
|
21072
21372
|
return { ...view, contentRef, text: input.text };
|
|
21073
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
|
+
}
|
|
21074
21400
|
var init_artifact_content2 = __esm({
|
|
21075
21401
|
"../server/src/artifact-content.ts"() {
|
|
21076
21402
|
"use strict";
|
|
@@ -21079,6 +21405,110 @@ var init_artifact_content2 = __esm({
|
|
|
21079
21405
|
}
|
|
21080
21406
|
});
|
|
21081
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
|
+
|
|
21082
21512
|
// ../server/src/command-policy.ts
|
|
21083
21513
|
function classifyCommand(command) {
|
|
21084
21514
|
return COMMAND_POLICY[command] ?? { risk: "direct" };
|
|
@@ -21105,7 +21535,7 @@ var init_command_policy = __esm({
|
|
|
21105
21535
|
// 重复关同一条是幂等的(kernel.resolveEscalation 内部按 escalationId 去重)
|
|
21106
21536
|
// 「关错=静默停摆」的唯一防线是人在卡上一眼看出「关的是哪一条」——escalationId 与 reason 摘要必须上卡。
|
|
21107
21537
|
// a 里 escalationId/artifactId 由 server.ts 起草时冻进 commandArgs(已四级解析),保证是真值、非 `?`。
|
|
21108
|
-
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`
|
|
21109
21539
|
},
|
|
21110
21540
|
gc: {
|
|
21111
21541
|
risk: "forbidden",
|
|
@@ -28028,6 +28458,15 @@ var init_chat_item_ledger = __esm({
|
|
|
28028
28458
|
this.messageId = deps.messageId ?? null;
|
|
28029
28459
|
this.telemetryTextInContent = deps.telemetryTextInContent === true;
|
|
28030
28460
|
this.persistEnabled = Boolean(deps.items) && chatItemsDoubleWriteEnabled();
|
|
28461
|
+
this.clock = Math.max(0, deps.versionSeed ?? 0);
|
|
28462
|
+
if (deps.versionSeed === void 0 && deps.items) {
|
|
28463
|
+
void deps.items.sessionVersionCursor(deps.sessionId).then(
|
|
28464
|
+
(seed) => {
|
|
28465
|
+
if (seed > this.clock) this.clock = seed;
|
|
28466
|
+
},
|
|
28467
|
+
() => void 0
|
|
28468
|
+
);
|
|
28469
|
+
}
|
|
28031
28470
|
}
|
|
28032
28471
|
/** 账本轮次 id(`chat_session_turns.id`;未接账本时是 {@link fallbackTurnId})。v3 wire 上 item 帧的 turnId 用它(ADR-0510 D1)。 */
|
|
28033
28472
|
get turnId() {
|
|
@@ -28043,6 +28482,11 @@ var init_chat_item_ledger = __esm({
|
|
|
28043
28482
|
/** 内存账本:itemId → 行。插入顺序即 seq 顺序。 */
|
|
28044
28483
|
rows = /* @__PURE__ */ new Map();
|
|
28045
28484
|
seqCounter = 0;
|
|
28485
|
+
/**
|
|
28486
|
+
* 会话逻辑时钟(ADR-0510 附录 A)。**一次写走一格**——不描述某条 item 改了第几次,描述
|
|
28487
|
+
* 「这条会话被写了第几次」。于是「一次建行要发两帧」这个死结不存在:想发几帧就取几个号。
|
|
28488
|
+
*/
|
|
28489
|
+
clock;
|
|
28046
28490
|
segment = 0;
|
|
28047
28491
|
messageId;
|
|
28048
28492
|
/** 已排队但还没执行的那一次 `append_text`——排队期间到的片段合并进它(见 queueAppend)。 */
|
|
@@ -28055,6 +28499,18 @@ var init_chat_item_ledger = __esm({
|
|
|
28055
28499
|
finished = false;
|
|
28056
28500
|
/** 上一次见到的归一层 turnId——变了就清映射(provider 开了新 turn)。 */
|
|
28057
28501
|
lastTurnId = null;
|
|
28502
|
+
/** 会话逻辑时钟走一格。**唯一发号点**(ADR-0510 附录 A.4)——别在别处自己数。 */
|
|
28503
|
+
tick() {
|
|
28504
|
+
return ++this.clock;
|
|
28505
|
+
}
|
|
28506
|
+
/**
|
|
28507
|
+
* 该 item 的服务端展示序;INSERT 还没返回(或没开双写)时是 undefined。
|
|
28508
|
+
* 调用方(`emitV3`)拿不到就不在帧上带号——**不许自己编一个**。
|
|
28509
|
+
*/
|
|
28510
|
+
ordFor(itemId) {
|
|
28511
|
+
const ord = this.rows.get(itemId)?.ord;
|
|
28512
|
+
return ord === null ? void 0 : ord;
|
|
28513
|
+
}
|
|
28058
28514
|
get failures() {
|
|
28059
28515
|
return this.failureCount;
|
|
28060
28516
|
}
|
|
@@ -28091,20 +28547,17 @@ var init_chat_item_ledger = __esm({
|
|
|
28091
28547
|
if (event.type === "turn") {
|
|
28092
28548
|
this.itemKeyMap.clear();
|
|
28093
28549
|
this.lastTurnId = null;
|
|
28094
|
-
return;
|
|
28550
|
+
return null;
|
|
28095
28551
|
}
|
|
28096
28552
|
if (this.lastTurnId !== null && this.lastTurnId !== event.turnId) {
|
|
28097
28553
|
this.itemKeyMap.clear();
|
|
28098
28554
|
}
|
|
28099
28555
|
this.lastTurnId = event.turnId;
|
|
28100
28556
|
const existingId = this.itemKeyMap.get(event.providerItemKey);
|
|
28101
|
-
if (existingId === void 0)
|
|
28102
|
-
this.createFrom(event);
|
|
28103
|
-
return;
|
|
28104
|
-
}
|
|
28557
|
+
if (existingId === void 0) return this.createFrom(event);
|
|
28105
28558
|
const row = this.rows.get(existingId);
|
|
28106
|
-
if (!row) return;
|
|
28107
|
-
this.applyOpTo(row, event);
|
|
28559
|
+
if (!row) return null;
|
|
28560
|
+
return this.applyOpTo(row, event);
|
|
28108
28561
|
}
|
|
28109
28562
|
// ── 非 provider 来源(服务端自己产的行)────────────────────────────────────
|
|
28110
28563
|
/**
|
|
@@ -28299,34 +28752,49 @@ var init_chat_item_ledger = __esm({
|
|
|
28299
28752
|
metadata: { [CHAT_ITEM_META_LEGACY_CONTENT]: inContent }
|
|
28300
28753
|
});
|
|
28301
28754
|
this.itemKeyMap.set(event.providerItemKey, id);
|
|
28302
|
-
|
|
28755
|
+
const row = this.rows.get(id);
|
|
28756
|
+
const outcome = {
|
|
28757
|
+
itemId: id,
|
|
28758
|
+
version: row.wireVersion,
|
|
28759
|
+
startedVersion: row.startedVersion,
|
|
28760
|
+
ord: row.ord
|
|
28761
|
+
};
|
|
28762
|
+
if (event.opShape.op === "set_status") {
|
|
28763
|
+
const followUp = this.applyOpTo(row, event);
|
|
28764
|
+
if (followUp) outcome.version = followUp.version;
|
|
28765
|
+
}
|
|
28766
|
+
return outcome;
|
|
28303
28767
|
}
|
|
28304
28768
|
applyOpTo(row, event) {
|
|
28305
28769
|
const op = event.opShape;
|
|
28306
28770
|
if (event.attrs) row.attrs = { ...row.attrs, ...event.attrs };
|
|
28307
28771
|
switch (op.op) {
|
|
28308
28772
|
case "append_text":
|
|
28309
|
-
if (op.text === "") return;
|
|
28773
|
+
if (op.text === "") return null;
|
|
28310
28774
|
row.text += op.text;
|
|
28311
28775
|
this.queueAppend(row, op.text);
|
|
28312
|
-
return;
|
|
28776
|
+
return this.outcomeOf(row);
|
|
28313
28777
|
case "set_text":
|
|
28314
|
-
if (row.text === op.text) return;
|
|
28778
|
+
if (row.text === op.text) return null;
|
|
28315
28779
|
row.text = op.text;
|
|
28316
28780
|
this.sealPendingAppend();
|
|
28317
28781
|
this.persist(row, row.kind === "text" || row.kind === "thinking" ? { op: "set_text", text: op.text } : { op: "set_payload", payload: this.payloadOf(row) }, "set_text");
|
|
28318
|
-
return;
|
|
28782
|
+
return this.outcomeOf(row);
|
|
28319
28783
|
case "patch_input":
|
|
28320
28784
|
row.attrs = { ...row.attrs, input: op.input };
|
|
28321
28785
|
this.persist(row, { op: "patch_input", input: op.input }, "patch_input");
|
|
28322
|
-
return;
|
|
28786
|
+
return this.outcomeOf(row);
|
|
28323
28787
|
case "set_status":
|
|
28324
|
-
if (row.status === op.status) return;
|
|
28788
|
+
if (row.status === op.status) return null;
|
|
28325
28789
|
row.status = op.status;
|
|
28326
28790
|
this.persist(row, { op: "set_status", status: op.status }, "set_status");
|
|
28327
|
-
return;
|
|
28791
|
+
return this.outcomeOf(row);
|
|
28328
28792
|
}
|
|
28329
28793
|
}
|
|
28794
|
+
/** 这一次写的回执。`persist` 已经在里面走过钟了,这里只是把读数带出去给 wire。 */
|
|
28795
|
+
outcomeOf(row) {
|
|
28796
|
+
return { itemId: row.id, version: row.wireVersion, ord: row.ord };
|
|
28797
|
+
}
|
|
28330
28798
|
/** 关掉所有还 streaming 的行(收尾/切段)。 */
|
|
28331
28799
|
closeStreamingRows(status) {
|
|
28332
28800
|
this.sealPendingAppend();
|
|
@@ -28361,9 +28829,14 @@ var init_chat_item_ledger = __esm({
|
|
|
28361
28829
|
}
|
|
28362
28830
|
insert(input) {
|
|
28363
28831
|
const id = input.deterministicId ?? this.newId();
|
|
28832
|
+
const startedVersion = this.tick();
|
|
28833
|
+
const writeVersion = this.tick();
|
|
28364
28834
|
const row = {
|
|
28365
28835
|
id,
|
|
28366
28836
|
seq: ++this.seqCounter,
|
|
28837
|
+
ord: null,
|
|
28838
|
+
wireVersion: writeVersion,
|
|
28839
|
+
startedVersion,
|
|
28367
28840
|
kind: input.kind,
|
|
28368
28841
|
role: input.role,
|
|
28369
28842
|
status: input.status,
|
|
@@ -28383,7 +28856,7 @@ var init_chat_item_ledger = __esm({
|
|
|
28383
28856
|
const payload = input.payload ?? this.payloadOf(row);
|
|
28384
28857
|
const metadata = input.metadata ?? { [CHAT_ITEM_META_LEGACY_CONTENT]: input.inContent };
|
|
28385
28858
|
this.enqueue(async () => {
|
|
28386
|
-
await this.deps.items.createItem({
|
|
28859
|
+
const created = await this.deps.items.createItem({
|
|
28387
28860
|
id,
|
|
28388
28861
|
sessionId: this.deps.sessionId,
|
|
28389
28862
|
messageId,
|
|
@@ -28395,8 +28868,20 @@ var init_chat_item_ledger = __esm({
|
|
|
28395
28868
|
payload,
|
|
28396
28869
|
metadata,
|
|
28397
28870
|
providerItemKey: input.providerItemKey ?? null,
|
|
28398
|
-
createdAt
|
|
28871
|
+
createdAt,
|
|
28872
|
+
version: writeVersion
|
|
28399
28873
|
});
|
|
28874
|
+
row.ord = created.ord;
|
|
28875
|
+
try {
|
|
28876
|
+
this.deps.onPersisted?.({
|
|
28877
|
+
itemId: id,
|
|
28878
|
+
ord: created.ord,
|
|
28879
|
+
version: writeVersion,
|
|
28880
|
+
role: input.role,
|
|
28881
|
+
text: input.text
|
|
28882
|
+
});
|
|
28883
|
+
} catch {
|
|
28884
|
+
}
|
|
28400
28885
|
}, `create:${input.kind}`);
|
|
28401
28886
|
return id;
|
|
28402
28887
|
}
|
|
@@ -28405,6 +28890,7 @@ var init_chat_item_ledger = __esm({
|
|
|
28405
28890
|
* 语义没变(`version` 数的是落库次数),投影是拼接,合不合并结果逐字相同。
|
|
28406
28891
|
*/
|
|
28407
28892
|
queueAppend(row, text5) {
|
|
28893
|
+
row.wireVersion = this.tick();
|
|
28408
28894
|
if (!row.persist || !this.persistEnabled) return;
|
|
28409
28895
|
const pending = this.pendingAppend;
|
|
28410
28896
|
if (pending && pending.id === row.id) {
|
|
@@ -28418,7 +28904,11 @@ var init_chat_item_ledger = __esm({
|
|
|
28418
28904
|
next.text = "";
|
|
28419
28905
|
if (this.pendingAppend === next) this.pendingAppend = null;
|
|
28420
28906
|
if (!merged) return;
|
|
28421
|
-
const applied = await this.deps.items.applyOp(
|
|
28907
|
+
const applied = await this.deps.items.applyOp(
|
|
28908
|
+
row.id,
|
|
28909
|
+
{ op: "append_text", text: merged },
|
|
28910
|
+
{ at: this.now(), version: row.wireVersion }
|
|
28911
|
+
);
|
|
28422
28912
|
if (!applied) throw new Error(`item ${row.id} \u4E0D\u5B58\u5728\uFF08append_text \u843D\u7A7A\uFF09`);
|
|
28423
28913
|
}, "append_text");
|
|
28424
28914
|
}
|
|
@@ -28427,9 +28917,11 @@ var init_chat_item_ledger = __esm({
|
|
|
28427
28917
|
this.pendingAppend = null;
|
|
28428
28918
|
}
|
|
28429
28919
|
persist(row, op, what) {
|
|
28920
|
+
const version2 = this.tick();
|
|
28921
|
+
row.wireVersion = version2;
|
|
28430
28922
|
if (!row.persist || !this.persistEnabled) return;
|
|
28431
28923
|
this.enqueue(async () => {
|
|
28432
|
-
await this.deps.items.applyOp(row.id, op, { at: this.now() });
|
|
28924
|
+
await this.deps.items.applyOp(row.id, op, { at: this.now(), version: version2 });
|
|
28433
28925
|
}, what);
|
|
28434
28926
|
}
|
|
28435
28927
|
/** 串行链 + 失败吞掉计数。**串行是必须的**:append_text 依赖建行已经落地。 */
|
|
@@ -28518,17 +29010,21 @@ async function openAssistantRunningRow(deps) {
|
|
|
28518
29010
|
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
28519
29011
|
if (typeof deps.chatStore.appendMessage !== "function") return void 0;
|
|
28520
29012
|
const id = (0, import_node_crypto16.randomUUID)();
|
|
29013
|
+
const at = now();
|
|
28521
29014
|
try {
|
|
28522
29015
|
await deps.chatStore.appendMessage({
|
|
28523
29016
|
id,
|
|
28524
29017
|
sessionId: deps.chatSessionId,
|
|
28525
29018
|
role: "assistant",
|
|
28526
29019
|
content: "",
|
|
28527
|
-
createdAt:
|
|
29020
|
+
createdAt: at,
|
|
28528
29021
|
status: "running",
|
|
28529
29022
|
...deps.runId ? { runId: deps.runId } : {},
|
|
28530
29023
|
...deps.turnId ? { turnId: deps.turnId } : {}
|
|
28531
29024
|
});
|
|
29025
|
+
if (typeof deps.chatStore.updateSession === "function") {
|
|
29026
|
+
await deps.chatStore.updateSession(deps.chatSessionId, { touchedAt: at }).catch(() => void 0);
|
|
29027
|
+
}
|
|
28532
29028
|
return id;
|
|
28533
29029
|
} catch {
|
|
28534
29030
|
return void 0;
|
|
@@ -141732,7 +142228,7 @@ function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
|
141732
142228
|
if (w2.nodeId !== artifactId) continue;
|
|
141733
142229
|
if (isReplyWork(w2)) continue;
|
|
141734
142230
|
nodeWorkIds.add(w2.id);
|
|
141735
|
-
const status = w2.status ?? (w2.retryAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
142231
|
+
const status = w2.status ?? (w2.retryAt && w2.deadAt ? "retry" : w2.deadAt ? "dead" : w2.endedAt ? w2.outcome === "failed" ? "failed" : "success" : "running");
|
|
141736
142232
|
items.push({
|
|
141737
142233
|
type: "work",
|
|
141738
142234
|
id: w2.id,
|
|
@@ -159267,13 +159763,15 @@ var init_live_chat = __esm({
|
|
|
159267
159763
|
* 账本坏掉不影响对话;v3 emit 抛异常也不影响 v2 通路与账本落盘——两条独立通路。
|
|
159268
159764
|
*/
|
|
159269
159765
|
applyNormalizedEvent(turn, event) {
|
|
159766
|
+
let outcome = null;
|
|
159270
159767
|
try {
|
|
159271
|
-
turn.items?.apply(event);
|
|
159768
|
+
outcome = turn.items?.apply(event) ?? null;
|
|
159272
159769
|
} catch {
|
|
159273
159770
|
}
|
|
159274
159771
|
if (event.type === "turn") return;
|
|
159772
|
+
if (!outcome) return;
|
|
159275
159773
|
try {
|
|
159276
|
-
this.emitV3(turn, event);
|
|
159774
|
+
this.emitV3(turn, event, outcome);
|
|
159277
159775
|
} catch {
|
|
159278
159776
|
}
|
|
159279
159777
|
}
|
|
@@ -159293,18 +159791,17 @@ var init_live_chat = __esm({
|
|
|
159293
159791
|
wireTurnId(turn) {
|
|
159294
159792
|
return turn.items?.turnId ?? turn.liveTurnId;
|
|
159295
159793
|
}
|
|
159296
|
-
emitV3(turn, event) {
|
|
159794
|
+
emitV3(turn, event, outcome) {
|
|
159297
159795
|
if (!turn.items) return;
|
|
159298
|
-
const itemId =
|
|
159299
|
-
if (!itemId) return;
|
|
159796
|
+
const itemId = outcome.itemId;
|
|
159300
159797
|
const itemType = normalizedKindToItemType(event.kind);
|
|
159301
159798
|
const operation = normalizedOpToV3Op(event.opShape.op, event.kind);
|
|
159302
159799
|
if (!operation) return;
|
|
159303
|
-
const perItem = turn.v3Items.get(itemId) ?? {
|
|
159800
|
+
const perItem = turn.v3Items.get(itemId) ?? { textOffset: 0, started: false };
|
|
159304
159801
|
const isTextual2 = event.kind === "text" || event.kind === "thinking";
|
|
159305
159802
|
const opShape = event.opShape;
|
|
159306
|
-
|
|
159307
|
-
|
|
159803
|
+
const ord = turn.items.ordFor(itemId) ?? outcome.ord ?? void 0;
|
|
159804
|
+
if (!perItem.started && outcome.startedVersion !== void 0) {
|
|
159308
159805
|
const startedFrame = {
|
|
159309
159806
|
protocolVersion: 3,
|
|
159310
159807
|
streamId: turn.liveStreamId,
|
|
@@ -159312,7 +159809,8 @@ var init_live_chat = __esm({
|
|
|
159312
159809
|
itemId,
|
|
159313
159810
|
itemType,
|
|
159314
159811
|
operation: "started",
|
|
159315
|
-
itemVersion:
|
|
159812
|
+
itemVersion: outcome.startedVersion,
|
|
159813
|
+
...ord !== void 0 ? { ord } : {},
|
|
159316
159814
|
offset: 0,
|
|
159317
159815
|
payload: v3StartedPayload(event.role, event.attrs, itemType, event.kind)
|
|
159318
159816
|
};
|
|
@@ -159334,7 +159832,6 @@ var init_live_chat = __esm({
|
|
|
159334
159832
|
} else if (opShape.op === "set_status") {
|
|
159335
159833
|
if (opShape.status === "streaming") return;
|
|
159336
159834
|
}
|
|
159337
|
-
perItem.version += 1;
|
|
159338
159835
|
turn.v3Items.set(itemId, perItem);
|
|
159339
159836
|
const frame = {
|
|
159340
159837
|
protocolVersion: 3,
|
|
@@ -159343,7 +159840,9 @@ var init_live_chat = __esm({
|
|
|
159343
159840
|
itemId,
|
|
159344
159841
|
itemType,
|
|
159345
159842
|
operation,
|
|
159346
|
-
|
|
159843
|
+
// 账本这一次写用掉的那一格钟——**库里那一行的 version 就是它**,快照与增量流同一套版本空间。
|
|
159844
|
+
itemVersion: outcome.version,
|
|
159845
|
+
...ord !== void 0 ? { ord } : {},
|
|
159347
159846
|
...offset !== void 0 ? { offset } : {},
|
|
159348
159847
|
...payload !== void 0 ? { payload } : {}
|
|
159349
159848
|
};
|
|
@@ -159369,6 +159868,7 @@ var init_live_chat = __esm({
|
|
|
159369
159868
|
const payload = pending.frame.payload;
|
|
159370
159869
|
pending.frame.payload = { ...payload, text: String(payload.text ?? "") + text5 };
|
|
159371
159870
|
pending.frame.itemVersion = frame.itemVersion;
|
|
159871
|
+
if (frame.ord !== void 0) pending.frame.ord = frame.ord;
|
|
159372
159872
|
pending.chunks += 1;
|
|
159373
159873
|
pending.chars += text5.length;
|
|
159374
159874
|
return;
|
|
@@ -159409,6 +159909,58 @@ var init_live_chat = __esm({
|
|
|
159409
159909
|
}
|
|
159410
159910
|
}
|
|
159411
159911
|
}
|
|
159912
|
+
/**
|
|
159913
|
+
* 把**用户自己那条消息**推上 v3 流(ADR-0510 D2 的收口)。
|
|
159914
|
+
*
|
|
159915
|
+
* ## 为什么需要
|
|
159916
|
+
*
|
|
159917
|
+
* 用户点发送时前端先画一条「乐观条」,它**没有服务端展示序**(D2:不许前端发明号),
|
|
159918
|
+
* 渲染时钉在列表末尾。这条规矩对「刚发完、还没有任何回复」是对的。
|
|
159919
|
+
*
|
|
159920
|
+
* 但服务端落账本时**立刻**给了这条 user item 一个 ord(它是这一轮第一个建的行,号最小),
|
|
159921
|
+
* 而这个号此前**只走快照**——`emitV3` 只服务 provider 事件(`server.ts` 那句注释:
|
|
159922
|
+
* 「不走 v3 流帧(那条只服务 assistant)」)。于是有个几秒的窗口:
|
|
159923
|
+
* agent 的正文带着更大的号从直播流到了、进入「有号」段,而用户那条还在「无号」段被钉在最后
|
|
159924
|
+
* ⇒ **agent 的回复显示在用户消息上面**,等下一次快照才跳回去。
|
|
159925
|
+
* 2026-09-09 用户现场原话:「过几秒钟页面刷新一下,agent 回复内容会回到用户消息下面」。
|
|
159926
|
+
*
|
|
159927
|
+
* 修法就是把这条也推上去:前端按 itemId(`oasis-user-optim:<csid>`,与乐观桶位同名)覆盖,
|
|
159928
|
+
* 拿到真号立刻归位,窗口从「到下一次快照」压到「一次 INSERT 往返」。
|
|
159929
|
+
*
|
|
159930
|
+
* ## 为什么不在前端算
|
|
159931
|
+
*
|
|
159932
|
+
* 试过的三种都被证伪(见 `chat-items-merge.ts` 里那段注释)。这次也不能用「记下创建时的最大号」:
|
|
159933
|
+
* 空对话首次发送时那个最大号是 0,随后到达的历史(号更大)会全部排到乐观条**后面**——
|
|
159934
|
+
* 正是 ADR D9 点名必须验的「空桶先发送」那一档。号只能由服务端给。
|
|
159935
|
+
*
|
|
159936
|
+
* ## 拿不到轮就静默跳过
|
|
159937
|
+
*
|
|
159938
|
+
* 本方法由账本的落库链回调触发,而那一刻这条会话的 live 轮可能还没 `start`
|
|
159939
|
+
* (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
|
|
159940
|
+
* 前端退回「等下一次快照」,与修复前一致,不会更坏。
|
|
159941
|
+
*/
|
|
159942
|
+
publishUserItem(chatSessionId, item) {
|
|
159943
|
+
const turn = this.turns.get(chatSessionId);
|
|
159944
|
+
if (!turn) return false;
|
|
159945
|
+
const payload = { role: "user", text: item.text };
|
|
159946
|
+
if (item.clientSubmitId) payload.clientSubmitId = item.clientSubmitId;
|
|
159947
|
+
if (item.attachments?.length) payload.attachments = item.attachments;
|
|
159948
|
+
const frame = {
|
|
159949
|
+
protocolVersion: 3,
|
|
159950
|
+
streamId: turn.liveStreamId,
|
|
159951
|
+
turnId: this.wireTurnId(turn),
|
|
159952
|
+
itemId: item.itemId,
|
|
159953
|
+
itemType: "message",
|
|
159954
|
+
operation: "set_text",
|
|
159955
|
+
itemVersion: item.version,
|
|
159956
|
+
...item.ord !== null ? { ord: item.ord } : {},
|
|
159957
|
+
offset: 0,
|
|
159958
|
+
payload
|
|
159959
|
+
};
|
|
159960
|
+
this.flushPendingV3(turn);
|
|
159961
|
+
this.publishV3(turn, this.assignV3Seq(turn, frame));
|
|
159962
|
+
return true;
|
|
159963
|
+
}
|
|
159412
159964
|
emitV3TurnTerminal(turn, status) {
|
|
159413
159965
|
if (status !== "done" && status !== "error") return;
|
|
159414
159966
|
const operation = status === "done" ? "turn_completed" : "turn_failed";
|
|
@@ -160175,7 +160727,7 @@ var init_workorder_drafts = __esm({
|
|
|
160175
160727
|
}
|
|
160176
160728
|
};
|
|
160177
160729
|
ident = (s2) => {
|
|
160178
|
-
if (!/^[
|
|
160730
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
|
|
160179
160731
|
return s2;
|
|
160180
160732
|
};
|
|
160181
160733
|
seqNoOf = (id, fallback) => {
|
|
@@ -160433,7 +160985,7 @@ var init_workorder_draft_planner_issues = __esm({
|
|
|
160433
160985
|
}
|
|
160434
160986
|
};
|
|
160435
160987
|
ident2 = (s2) => {
|
|
160436
|
-
if (!/^[
|
|
160988
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/.test(s2)) throw new Error(`invalid schema name: ${s2}`);
|
|
160437
160989
|
return s2;
|
|
160438
160990
|
};
|
|
160439
160991
|
PostgresWorkorderDraftPlannerIssuesStore = class _PostgresWorkorderDraftPlannerIssuesStore extends MemoryWorkorderDraftPlannerIssuesStore {
|
|
@@ -194283,8 +194835,15 @@ function isTextual(contentType) {
|
|
|
194283
194835
|
const t = contentType.toLowerCase();
|
|
194284
194836
|
return t.startsWith("text/") || t.includes("json") || t.includes("xml") || t.includes("yaml") || t.includes("javascript");
|
|
194285
194837
|
}
|
|
194286
|
-
function toPrepared(rel, source, read) {
|
|
194287
|
-
const base = {
|
|
194838
|
+
function toPrepared(rel, source, read, src) {
|
|
194839
|
+
const base = {
|
|
194840
|
+
rel,
|
|
194841
|
+
source,
|
|
194842
|
+
size: read.size,
|
|
194843
|
+
contentType: read.contentType,
|
|
194844
|
+
...src ? { srcSize: src.size } : {},
|
|
194845
|
+
...src?.mtime ? { srcMtime: src.mtime } : {}
|
|
194846
|
+
};
|
|
194288
194847
|
if (!isTextual(read.contentType)) return { ...base, base64: read.contentBase64 };
|
|
194289
194848
|
return { ...base, text: Buffer.from(read.contentBase64, "base64").toString("utf8") };
|
|
194290
194849
|
}
|
|
@@ -194321,7 +194880,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194321
194880
|
if (!lenient) throw error2;
|
|
194322
194881
|
skipped.push({ source, code: error2.code, message: error2.message });
|
|
194323
194882
|
};
|
|
194324
|
-
if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0 };
|
|
194883
|
+
if (wanted.length === 0) return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
|
|
194325
194884
|
if (!port) {
|
|
194326
194885
|
const error2 = new DelegationFileError(
|
|
194327
194886
|
"FILE_UNREADABLE",
|
|
@@ -194330,7 +194889,7 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194330
194889
|
);
|
|
194331
194890
|
if (!lenient) throw error2;
|
|
194332
194891
|
for (const source of wanted) skipped.push({ source, code: error2.code, message: error2.message });
|
|
194333
|
-
return { prepared: [], skipped, totalBytes: 0 };
|
|
194892
|
+
return { prepared: [], skipped, totalBytes: 0, unchanged: [] };
|
|
194334
194893
|
}
|
|
194335
194894
|
let accepted = wanted;
|
|
194336
194895
|
if (wanted.length > limits.maxFiles) {
|
|
@@ -194408,10 +194967,10 @@ async function collectInboundFiles(port, parent, paths, limits, lenient) {
|
|
|
194408
194967
|
used.add(rel);
|
|
194409
194968
|
prepared.push(toPrepared(rel, source, read));
|
|
194410
194969
|
}
|
|
194411
|
-
return { prepared, skipped, totalBytes };
|
|
194970
|
+
return { prepared, skipped, totalBytes, unchanged: [] };
|
|
194412
194971
|
}
|
|
194413
|
-
async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS) {
|
|
194414
|
-
const out = { prepared: [], skipped: [], totalBytes: 0 };
|
|
194972
|
+
async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FILE_LIMITS, known) {
|
|
194973
|
+
const out = { prepared: [], skipped: [], totalBytes: 0, unchanged: [] };
|
|
194415
194974
|
if (!port) return out;
|
|
194416
194975
|
if (port.locate) {
|
|
194417
194976
|
const located = await port.locate(child).catch((err) => ({
|
|
@@ -194471,7 +195030,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194471
195030
|
for (const entry of level.entries) {
|
|
194472
195031
|
const childPath = `${dir}/${entry.name}`;
|
|
194473
195032
|
if (entry.type === "dir") queue.push(childPath);
|
|
194474
|
-
else files.push({ rel: childPath, size: entry.size });
|
|
195033
|
+
else files.push({ rel: childPath, size: entry.size, ...entry.mtime ? { mtime: entry.mtime } : {} });
|
|
194475
195034
|
}
|
|
194476
195035
|
}
|
|
194477
195036
|
for (const dir of queue) {
|
|
@@ -194487,6 +195046,12 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194487
195046
|
out.skipped.push({ source: file.rel, code: "PATH_REJECTED", message: "\u8DEF\u5F84\u4E0D\u5408\u6CD5\uFF0C\u6CA1\u642C" });
|
|
194488
195047
|
continue;
|
|
194489
195048
|
}
|
|
195049
|
+
const target = `${DELEGATION_OUTBOUND_DIR}/${dirName}/${relative5}`;
|
|
195050
|
+
const seen = known?.get(target);
|
|
195051
|
+
if (seen && file.mtime && seen.size === file.size && seen.mtime === file.mtime) {
|
|
195052
|
+
out.unchanged.push(target);
|
|
195053
|
+
continue;
|
|
195054
|
+
}
|
|
194490
195055
|
if (out.prepared.length >= limits.maxFiles) {
|
|
194491
195056
|
out.skipped.push({ source: file.rel, code: "FILE_QUOTA_EXCEEDED", message: `\u8D85\u8FC7\u4E00\u6B21 ${limits.maxFiles} \u4E2A\u7684\u4E0A\u9650\uFF0C\u6CA1\u642C` });
|
|
194492
195057
|
continue;
|
|
@@ -194505,7 +195070,7 @@ async function prepareOutboundFiles(port, child, dirName, limits = DELEGATION_FI
|
|
|
194505
195070
|
continue;
|
|
194506
195071
|
}
|
|
194507
195072
|
out.totalBytes += read.size;
|
|
194508
|
-
out.prepared.push(toPrepared(
|
|
195073
|
+
out.prepared.push(toPrepared(target, file.rel, read, { size: file.size, ...file.mtime ? { mtime: file.mtime } : {} }));
|
|
194509
195074
|
}
|
|
194510
195075
|
return out;
|
|
194511
195076
|
}
|
|
@@ -194562,6 +195127,31 @@ function digestOf(text5) {
|
|
|
194562
195127
|
const line = text5.trim().replace(/\s+/g, " ");
|
|
194563
195128
|
return line.length > DIGEST_CHARS ? `${line.slice(0, DIGEST_CHARS)}\u2026` : line;
|
|
194564
195129
|
}
|
|
195130
|
+
function childRoundMessageIds(childSessionId, round) {
|
|
195131
|
+
const suffix = round <= 1 ? "" : `:${round}`;
|
|
195132
|
+
return {
|
|
195133
|
+
inputId: deterministicUuid(`delegation-input:${childSessionId}${suffix}`),
|
|
195134
|
+
placeholderId: deterministicUuid(`delegation-placeholder:${childSessionId}${suffix}`)
|
|
195135
|
+
};
|
|
195136
|
+
}
|
|
195137
|
+
async function seedChildRoundRows(chatStore, childSessionId, round, prompt2, now) {
|
|
195138
|
+
const ids2 = childRoundMessageIds(childSessionId, round);
|
|
195139
|
+
await chatStore.appendMessageOnce({
|
|
195140
|
+
id: ids2.inputId,
|
|
195141
|
+
sessionId: childSessionId,
|
|
195142
|
+
role: "user",
|
|
195143
|
+
content: prompt2,
|
|
195144
|
+
createdAt: now
|
|
195145
|
+
});
|
|
195146
|
+
await chatStore.appendMessageOnce({
|
|
195147
|
+
id: ids2.placeholderId,
|
|
195148
|
+
sessionId: childSessionId,
|
|
195149
|
+
role: "assistant",
|
|
195150
|
+
content: "",
|
|
195151
|
+
status: "running",
|
|
195152
|
+
createdAt: now
|
|
195153
|
+
});
|
|
195154
|
+
}
|
|
194565
195155
|
var import_node_crypto32, DelegationError, TITLE_TASK_CHARS, DIGEST_CHARS, PENDING_QUEUE_OWNER, DISPATCH_LEASE_MS, CONSECUTIVE_FAIL_LIMIT, DelegationService;
|
|
194566
195156
|
var init_service3 = __esm({
|
|
194567
195157
|
"../server/src/domains/delegations/service.ts"() {
|
|
@@ -194987,21 +195577,7 @@ var init_service3 = __esm({
|
|
|
194987
195577
|
createdAt: now
|
|
194988
195578
|
});
|
|
194989
195579
|
}
|
|
194990
|
-
await chatStore.
|
|
194991
|
-
id: deterministicUuid(`delegation-input:${record8.childSessionId}`),
|
|
194992
|
-
sessionId: record8.childSessionId,
|
|
194993
|
-
role: "user",
|
|
194994
|
-
content: task,
|
|
194995
|
-
createdAt: now
|
|
194996
|
-
});
|
|
194997
|
-
await chatStore.appendMessageOnce({
|
|
194998
|
-
id: deterministicUuid(`delegation-placeholder:${record8.childSessionId}`),
|
|
194999
|
-
sessionId: record8.childSessionId,
|
|
195000
|
-
role: "assistant",
|
|
195001
|
-
content: "",
|
|
195002
|
-
status: "running",
|
|
195003
|
-
createdAt: now
|
|
195004
|
-
});
|
|
195580
|
+
await seedChildRoundRows(chatStore, record8.childSessionId, 1, task, now);
|
|
195005
195581
|
}
|
|
195006
195582
|
/**
|
|
195007
195583
|
* 去程文件:读父会话工作区、翻成 §8.2 的同步错误码。
|
|
@@ -195161,7 +195737,7 @@ var init_service3 = __esm({
|
|
|
195161
195737
|
settledAt: failedAt
|
|
195162
195738
|
}).catch(() => null);
|
|
195163
195739
|
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
195164
|
-
await chatStore?.updateMessage(
|
|
195740
|
+
await chatStore?.updateMessage(childRoundMessageIds(record8.childSessionId, record8.roundSummaries.length + 1).placeholderId, {
|
|
195165
195741
|
content: `\u6D3E\u53D1\u6CA1\u80FD\u8D77\u6765\uFF1A${message}`,
|
|
195166
195742
|
status: "error",
|
|
195167
195743
|
completedAt: failedAt
|
|
@@ -195210,7 +195786,7 @@ var init_service3 = __esm({
|
|
|
195210
195786
|
const pending = await store.countPendingTouches(fresh.id).catch(() => 0);
|
|
195211
195787
|
const stopLoss = consecutiveFails >= CONSECUTIVE_FAIL_LIMIT;
|
|
195212
195788
|
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
195213
|
-
await chatStore?.updateMessage(
|
|
195789
|
+
await chatStore?.updateMessage(childRoundMessageIds(fresh.childSessionId, round).placeholderId, {
|
|
195214
195790
|
content: text5 || (failureKind ? `\u8FD9\u4E00\u8F6E\u6CA1\u8DD1\u5B8C\uFF1A${error2}` : ""),
|
|
195215
195791
|
status: failureKind ? "error" : "done",
|
|
195216
195792
|
completedAt: at
|
|
@@ -195222,11 +195798,13 @@ var init_service3 = __esm({
|
|
|
195222
195798
|
});
|
|
195223
195799
|
return;
|
|
195224
195800
|
}
|
|
195225
|
-
const
|
|
195801
|
+
const settledSeqAtRound = fresh.settledSeq + 1;
|
|
195802
|
+
const settledSummary = { ...newSummary, settledSeq: settledSeqAtRound };
|
|
195803
|
+
const settled = await store.appendRoundSummary(fresh.id, settledSummary, {
|
|
195226
195804
|
consecutiveFails,
|
|
195227
195805
|
state: failureKind ? "failed" : "done",
|
|
195228
195806
|
failureKind: failureKind ?? null,
|
|
195229
|
-
settledSeq:
|
|
195807
|
+
settledSeq: settledSeqAtRound,
|
|
195230
195808
|
settledAt: at
|
|
195231
195809
|
}).catch(() => null);
|
|
195232
195810
|
if (settled) await this.notifySettled(settled, companyId);
|
|
@@ -195255,17 +195833,19 @@ var init_service3 = __esm({
|
|
|
195255
195833
|
const record8 = await store.getByChildSession(childSessionId).catch(() => null);
|
|
195256
195834
|
if (!record8 || record8.state !== "running") return record8;
|
|
195257
195835
|
const at = this.now();
|
|
195836
|
+
const settledSeqAtRound = record8.settledSeq + 1;
|
|
195258
195837
|
const newSummary = {
|
|
195259
195838
|
round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
|
|
195260
195839
|
text: `\u8FD9\u4E00\u8F6E\u88AB\u65F6\u949F\u95ED\u5408\uFF1A${reason}`,
|
|
195261
|
-
at
|
|
195840
|
+
at,
|
|
195841
|
+
settledSeq: settledSeqAtRound
|
|
195262
195842
|
};
|
|
195263
195843
|
const settled = await store.appendRoundSummary(record8.id, newSummary, {
|
|
195264
195844
|
state: "failed",
|
|
195265
195845
|
// 退出帧丢失后被时钟闭合 = `interrupted`(§9),不是 `not-started`——它确实起来过。
|
|
195266
195846
|
failureKind: "interrupted",
|
|
195267
195847
|
consecutiveFails: record8.consecutiveFails + 1,
|
|
195268
|
-
settledSeq:
|
|
195848
|
+
settledSeq: settledSeqAtRound,
|
|
195269
195849
|
settledAt: at
|
|
195270
195850
|
}).catch(() => null);
|
|
195271
195851
|
if (settled) await this.notifySettled(settled, companyId);
|
|
@@ -195351,6 +195931,15 @@ var init_service3 = __esm({
|
|
|
195351
195931
|
onDispatched: async () => {
|
|
195352
195932
|
await queue.confirm();
|
|
195353
195933
|
await store.markTouchesConsumed(record8.id, touches.map((touch) => touch.messageId), this.now());
|
|
195934
|
+
await seedChildRoundRows(
|
|
195935
|
+
chatStore,
|
|
195936
|
+
record8.childSessionId,
|
|
195937
|
+
record8.roundSummaries.length + 1,
|
|
195938
|
+
body2,
|
|
195939
|
+
this.now()
|
|
195940
|
+
).catch((err) => {
|
|
195941
|
+
console.warn(`[delegation] \u5B50\u4F1A\u8BDD\u8865\u8F6E\u6B21\u884C\u5931\u8D25\uFF08${record8.id} round=${record8.roundSummaries.length + 1}\uFF09: ${String(err)}`);
|
|
195942
|
+
});
|
|
195354
195943
|
},
|
|
195355
195944
|
/**
|
|
195356
195945
|
* **没送到就把正文放回可读态**(见 `dispatchExpert` 的 `onNotDispatched`)。
|
|
@@ -195449,6 +196038,10 @@ var init_service3 = __esm({
|
|
|
195449
196038
|
const inbound = await this.readInboundFiles(parent, files);
|
|
195450
196039
|
const inboundBundle = toBundleFiles(inbound.prepared);
|
|
195451
196040
|
let delivered = false;
|
|
196041
|
+
let deliveryReasonCode;
|
|
196042
|
+
if (!this.options.appendToChild) {
|
|
196043
|
+
deliveryReasonCode = "no-append-port";
|
|
196044
|
+
}
|
|
195452
196045
|
if (this.options.appendToChild) {
|
|
195453
196046
|
const result = await this.options.appendToChild(record8.childSessionId, {
|
|
195454
196047
|
// 文件是静默落盘的,append 这条路没有 TASK.md 可改——**必须自己在正文里说一声**,
|
|
@@ -195464,6 +196057,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195464
196057
|
...Object.keys(inboundBundle.binaryFiles).length ? { binaryFiles: inboundBundle.binaryFiles } : {}
|
|
195465
196058
|
}).catch(() => ({ accepted: false, reason: "append-threw" }));
|
|
195466
196059
|
delivered = result.accepted === true;
|
|
196060
|
+
if (!delivered) deliveryReasonCode = result.reason ?? "rejected";
|
|
195467
196061
|
}
|
|
195468
196062
|
if (!delivered) {
|
|
195469
196063
|
if (chatStore.enqueuePendingMessage) {
|
|
@@ -195522,6 +196116,7 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195522
196116
|
}
|
|
195523
196117
|
return {
|
|
195524
196118
|
delivered,
|
|
196119
|
+
...delivered ? {} : { deliveryReasonCode: deliveryReasonCode ?? "rejected" },
|
|
195525
196120
|
startedRound,
|
|
195526
196121
|
pendingInputs: await this.pendingInputs(store, record8.id),
|
|
195527
196122
|
// 起了新的一轮 = 快照与文件都随那一轮重新送过去了,没有「等下一轮再刷新」这回事。
|
|
@@ -195644,6 +196239,34 @@ ${inboundBundle.paths.map((path41) => `- ${path41}`).join("\n")}` : text5,
|
|
|
195644
196239
|
}
|
|
195645
196240
|
});
|
|
195646
196241
|
|
|
196242
|
+
// ../server/src/domains/delegations/continue-outcome.ts
|
|
196243
|
+
function describeContinueOutcome(input) {
|
|
196244
|
+
if (input.delivered) return { outcome: "inserted" };
|
|
196245
|
+
if (input.startedRound) return { outcome: "started-round" };
|
|
196246
|
+
const code2 = input.deliveryReasonCode ?? "rejected";
|
|
196247
|
+
return {
|
|
196248
|
+
outcome: "queued",
|
|
196249
|
+
// 认不出的码**原样带出来**,不折成「未知原因」——排障时那串字符本身就是线索。
|
|
196250
|
+
reason: REASON_TEXT[code2] ?? `\u672A\u80FD\u63D2\u5165\uFF08${code2}\uFF09`,
|
|
196251
|
+
reasonCode: code2
|
|
196252
|
+
};
|
|
196253
|
+
}
|
|
196254
|
+
var REASON_TEXT;
|
|
196255
|
+
var init_continue_outcome = __esm({
|
|
196256
|
+
"../server/src/domains/delegations/continue-outcome.ts"() {
|
|
196257
|
+
"use strict";
|
|
196258
|
+
REASON_TEXT = {
|
|
196259
|
+
"no-live-turn": "\u4E13\u5BB6\u6B64\u523B\u6CA1\u6709\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\uFF08\u591A\u534A\u662F\u521A\u6536\u5C3E\u3001\u4E0B\u4E00\u8F6E\u8FD8\u6CA1\u8D77\u6765\uFF09",
|
|
196260
|
+
unsupported: "\u8FD9\u4E2A\u4E13\u5BB6\u7528\u7684\u6A21\u578B\u4E0D\u652F\u6301\u4E2D\u9014\u63D2\u8BDD\uFF08\u8981\u7B49\u5B83\u8FD9\u4E00\u8F6E\u8DD1\u5B8C\uFF09",
|
|
196261
|
+
"session-closing": "\u4E13\u5BB6\u90A3\u4E00\u8F6E\u6B63\u5728\u6536\u5C3E\uFF0C\u8FD9\u53E5\u8BDD\u6765\u665A\u4E86\u4E00\u6B65",
|
|
196262
|
+
"turn-finished": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u4E13\u5BB6\u90A3\u4E00\u8F6E\u5DF2\u7ECF\u6536\u5C3E\u4E86",
|
|
196263
|
+
rejected: "\u4E13\u5BB6\u90A3\u4E00\u4FA7\u62D2\u6536\u4E86\u8FD9\u6B21\u63D2\u5165",
|
|
196264
|
+
"append-threw": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u51FA\u9519\uFF08\u7F51\u7EDC\u6216\u8282\u70B9\u5F02\u5E38\uFF09",
|
|
196265
|
+
"no-append-port": "\u8FD9\u5957\u90E8\u7F72\u6CA1\u6709\u63A5\u4E2D\u9014\u63D2\u5165\u901A\u9053"
|
|
196266
|
+
};
|
|
196267
|
+
}
|
|
196268
|
+
});
|
|
196269
|
+
|
|
195647
196270
|
// ../server/src/domains/delegations/routes.ts
|
|
195648
196271
|
function trustedDelegationInvocationId(body2, turn) {
|
|
195649
196272
|
const scope = turn?.artifactId ?? turn?.dispatchId;
|
|
@@ -195712,8 +196335,16 @@ function delegationRoutes(service) {
|
|
|
195712
196335
|
const text5 = requireString(raw["text"], "--text");
|
|
195713
196336
|
const files = requireStringArray(raw["files"], "--file");
|
|
195714
196337
|
try {
|
|
195715
|
-
await service.continueDelegation(req.params.id, text5, callerOf(req), files);
|
|
195716
|
-
|
|
196338
|
+
const res = await service.continueDelegation(req.params.id, text5, callerOf(req), files);
|
|
196339
|
+
const view = describeContinueOutcome(res);
|
|
196340
|
+
const body2 = {
|
|
196341
|
+
outcome: view.outcome,
|
|
196342
|
+
...view.reason ? { reason: view.reason } : {},
|
|
196343
|
+
...view.reasonCode ? { reasonCode: view.reasonCode } : {},
|
|
196344
|
+
pendingInputs: res.pendingInputs,
|
|
196345
|
+
...res.attachedFiles.length ? { attachedFiles: res.attachedFiles } : {}
|
|
196346
|
+
};
|
|
196347
|
+
return { status: 200, body: body2 };
|
|
195717
196348
|
} catch (error2) {
|
|
195718
196349
|
rethrow(error2);
|
|
195719
196350
|
}
|
|
@@ -195741,6 +196372,7 @@ var init_routes2 = __esm({
|
|
|
195741
196372
|
"use strict";
|
|
195742
196373
|
import_node_crypto33 = require("node:crypto");
|
|
195743
196374
|
init_router();
|
|
196375
|
+
init_continue_outcome();
|
|
195744
196376
|
init_service3();
|
|
195745
196377
|
}
|
|
195746
196378
|
});
|
|
@@ -196112,6 +196744,9 @@ function delegationReturnText(terminal, opts) {
|
|
|
196112
196744
|
if (opts.movedPaths.length > 0) {
|
|
196113
196745
|
lines.push("", "\u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6\uFF08\u5DF2\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u53EF\u76F4\u63A5\u8BFB\u53D6\uFF09\uFF1A", ...opts.movedPaths.map((p2) => `- ${p2}`));
|
|
196114
196746
|
}
|
|
196747
|
+
if ((opts.unchangedPaths?.length ?? 0) > 0) {
|
|
196748
|
+
lines.push("", `\u53E6\u6709 ${opts.unchangedPaths.length} \u4E2A\u6587\u4EF6\u4E0E\u4E0A\u6B21\u4EA4\u56DE\u65F6\u9010\u5B57\u4E00\u81F4\uFF0C\u672C\u8F6E\u6CA1\u6709\u91CD\u4F20\uFF08\u4ECD\u5728\u4F60\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0B\uFF0C\u8DEF\u5F84\u4E0D\u53D8\uFF09\u3002`);
|
|
196749
|
+
}
|
|
196115
196750
|
const locationFailures = opts.skipped.filter((s2) => s2.code !== void 0 && isLocationFailureCode(s2.code));
|
|
196116
196751
|
const contentSkipped = opts.skipped.filter((s2) => s2.code === void 0 || !isLocationFailureCode(s2.code));
|
|
196117
196752
|
if (locationFailures.length > 0) {
|
|
@@ -196151,17 +196786,21 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196151
196786
|
};
|
|
196152
196787
|
}
|
|
196153
196788
|
const dirName = delegationDirName(record8.label, record8.expertActorId, record8.id);
|
|
196789
|
+
const known = delegationOutboxFingerprints(record8.roundSummaries);
|
|
196154
196790
|
const outbound = await prepareOutboundFiles(
|
|
196155
196791
|
deps.workdir,
|
|
196156
196792
|
// 子会话与父会话同公司(§6.1.2 隔离二),所以这里的 companyId 取父会话那个。
|
|
196157
196793
|
{ chatSessionId: record8.childSessionId, ...parentSession.companyId ? { companyId: parentSession.companyId } : {} },
|
|
196158
|
-
dirName
|
|
196794
|
+
dirName,
|
|
196795
|
+
void 0,
|
|
196796
|
+
known
|
|
196159
196797
|
).catch((err) => {
|
|
196160
196798
|
log3(`[delegation-return] \u4EA7\u7269\u642C\u8FD0\u5931\u8D25\uFF08delegation=${record8.id}\uFF09: ${String(err)}`);
|
|
196161
196799
|
return {
|
|
196162
196800
|
prepared: [],
|
|
196163
196801
|
skipped: [{ source: "outputs/", code: "INTERNAL", message: "\u642C\u8FD0\u5931\u8D25" }],
|
|
196164
196802
|
totalBytes: 0,
|
|
196803
|
+
unchanged: [],
|
|
196165
196804
|
listDirCode: "INTERNAL"
|
|
196166
196805
|
};
|
|
196167
196806
|
});
|
|
@@ -196181,8 +196820,18 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196181
196820
|
const text5 = delegationReturnText(terminal, {
|
|
196182
196821
|
movedPaths: bundleFiles.paths,
|
|
196183
196822
|
skipped: outbound.skipped,
|
|
196184
|
-
recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS
|
|
196823
|
+
recentRounds: deps.recentRounds ?? DELEGATION_RETURN_RECENT_ROUNDS,
|
|
196824
|
+
unchangedPaths: outbound.unchanged
|
|
196185
196825
|
});
|
|
196826
|
+
const movedFileRefs = outbound.prepared.map((f2) => ({
|
|
196827
|
+
name: f2.rel,
|
|
196828
|
+
...typeof f2.srcSize === "number" ? { size: f2.srcSize } : {},
|
|
196829
|
+
...f2.srcMtime ? { mtime: f2.srcMtime } : {}
|
|
196830
|
+
}));
|
|
196831
|
+
const fingerprints = {};
|
|
196832
|
+
for (const f2 of movedFileRefs) {
|
|
196833
|
+
if (typeof f2.size === "number" || f2.mtime) fingerprints[f2.name] = { ...typeof f2.size === "number" ? { size: f2.size } : {}, ...f2.mtime ? { mtime: f2.mtime } : {} };
|
|
196834
|
+
}
|
|
196186
196835
|
const source = delegationReturnSource(record8);
|
|
196187
196836
|
const messageId = deriveSystemMessageId(source);
|
|
196188
196837
|
let written;
|
|
@@ -196213,6 +196862,8 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196213
196862
|
const bundle = bundleFiles.paths.length ? {
|
|
196214
196863
|
files: bundleFiles.files,
|
|
196215
196864
|
binaryFiles: bundleFiles.binaryFiles,
|
|
196865
|
+
// queued 那一档要靠它把指纹带到 flush 落账那一刻(`PendingChatAttachment.srcSize`)。
|
|
196866
|
+
fingerprints,
|
|
196216
196867
|
taskLines: [
|
|
196217
196868
|
"",
|
|
196218
196869
|
"## \u4E13\u5BB6\u4EA4\u56DE\u7684\u6587\u4EF6",
|
|
@@ -196247,6 +196898,8 @@ async function deliverDelegationReturn(deps, terminal) {
|
|
|
196247
196898
|
via: delivery.via,
|
|
196248
196899
|
movedFiles: bundleFiles.paths.length,
|
|
196249
196900
|
movedPaths: bundleFiles.paths,
|
|
196901
|
+
movedFileRefs,
|
|
196902
|
+
unchangedPaths: outbound.unchanged,
|
|
196250
196903
|
skippedFiles: outbound.skipped.length
|
|
196251
196904
|
};
|
|
196252
196905
|
}
|
|
@@ -196258,6 +196911,7 @@ var init_return_flow = __esm({
|
|
|
196258
196911
|
init_chat_system_message();
|
|
196259
196912
|
init_chat_broadcast();
|
|
196260
196913
|
init_files();
|
|
196914
|
+
init_src();
|
|
196261
196915
|
}
|
|
196262
196916
|
});
|
|
196263
196917
|
|
|
@@ -196401,8 +197055,10 @@ function assembleDelegationsDomain(deps) {
|
|
|
196401
197055
|
);
|
|
196402
197056
|
const targetRound = record8.roundSummaries[record8.roundSummaries.length - 1]?.round;
|
|
196403
197057
|
let movedPaths = null;
|
|
197058
|
+
let movedRefs = [];
|
|
196404
197059
|
if (outcome.status === "delivered" && (outcome.via === "turn" || outcome.via === "append") && outcome.movedPaths.length > 0) {
|
|
196405
197060
|
movedPaths = outcome.movedPaths;
|
|
197061
|
+
movedRefs = outcome.movedFileRefs;
|
|
196406
197062
|
}
|
|
196407
197063
|
if (movedPaths === null || targetRound === void 0) return;
|
|
196408
197064
|
const store = await deps.resolveStore(companyId).catch(() => null);
|
|
@@ -196415,7 +197071,7 @@ function assembleDelegationsDomain(deps) {
|
|
|
196415
197071
|
`[delegation-return] \u4EA7\u7269\u6E05\u5355\u843D\u8D26\u672A\u6210 (immediate) delegation=${record8.id} round=${targetRound} status=resolve-failed moved=${movedPaths.length} paths=${pathsField}`
|
|
196416
197072
|
);
|
|
196417
197073
|
} else {
|
|
196418
|
-
outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedPaths);
|
|
197074
|
+
outcomeCode = await registerRoundFiles(store, record8.id, targetRound, movedRefs.length ? movedRefs : movedPaths);
|
|
196419
197075
|
if (outcomeCode === "ok") return;
|
|
196420
197076
|
deps.metrics?.recordFilesRegisterFailure?.(outcomeCode);
|
|
196421
197077
|
warn(
|
|
@@ -196429,6 +197085,11 @@ function assembleDelegationsDomain(deps) {
|
|
|
196429
197085
|
chatSessionId: record8.parentSessionId,
|
|
196430
197086
|
delegationRef: { delegationId: record8.id, round: targetRound },
|
|
196431
197087
|
paths: movedPaths,
|
|
197088
|
+
// 补账那一趟同样要带指纹,否则「首次登记失败 → 补账成功」的路径会留下无指纹条目。
|
|
197089
|
+
fingerprints: Object.fromEntries(movedRefs.map((f2) => [f2.name, {
|
|
197090
|
+
...typeof f2.size === "number" ? { size: f2.size } : {},
|
|
197091
|
+
...f2.mtime ? { mtime: f2.mtime } : {}
|
|
197092
|
+
}])),
|
|
196432
197093
|
reason: `immediate-register-failed:${outcomeCode}`
|
|
196433
197094
|
}).catch((err) => {
|
|
196434
197095
|
warn(
|
|
@@ -196451,16 +197112,20 @@ async function registerDelegationFilesOnFlush(deps, row, companyId) {
|
|
|
196451
197112
|
const warn = deps.log ?? ((m2) => console.warn(m2));
|
|
196452
197113
|
const ref2 = row.delegationRef;
|
|
196453
197114
|
if (!ref2) return "no-ref";
|
|
196454
|
-
const
|
|
196455
|
-
if (
|
|
197115
|
+
const rows = (row.attachments ?? []).filter((a) => !!a.path);
|
|
197116
|
+
if (rows.length === 0) return "no-files";
|
|
196456
197117
|
const store = await deps.resolveStore(companyId).catch(() => null);
|
|
196457
197118
|
if (!store) return "failed";
|
|
196458
|
-
const files =
|
|
197119
|
+
const files = rows.map((a) => ({
|
|
197120
|
+
name: a.path,
|
|
197121
|
+
...typeof a.srcSize === "number" ? { size: a.srcSize } : {},
|
|
197122
|
+
...a.srcMtime ? { mtime: a.srcMtime } : {}
|
|
197123
|
+
}));
|
|
196459
197124
|
const outcome = await registerRoundFiles(store, ref2.delegationId, ref2.round, files);
|
|
196460
197125
|
if (outcome !== "ok") {
|
|
196461
197126
|
deps.metrics?.recordFilesRegisterFailure?.(outcome);
|
|
196462
197127
|
warn(
|
|
196463
|
-
`[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${
|
|
197128
|
+
`[delegation-return] flush \u843D\u8D26\u672A\u6210 delegation=${ref2.delegationId} round=${ref2.round} status=${outcome} files=${files.length}`
|
|
196464
197129
|
);
|
|
196465
197130
|
}
|
|
196466
197131
|
return outcome;
|
|
@@ -196483,6 +197148,31 @@ var init_assembly = __esm({
|
|
|
196483
197148
|
}
|
|
196484
197149
|
});
|
|
196485
197150
|
|
|
197151
|
+
// ../server/src/domains/delegations/child-live-turn.ts
|
|
197152
|
+
function registerDelegatedChildTurn(liveChat, childSessionId, session) {
|
|
197153
|
+
const appendInput = session.appendInput;
|
|
197154
|
+
if (typeof appendInput !== "function" || session.canAppendInput === false) return;
|
|
197155
|
+
const ctrl = liveChat.start(childSessionId, {
|
|
197156
|
+
runtimeSessionId: session.id,
|
|
197157
|
+
...session.runId ? { runId: session.runId } : {},
|
|
197158
|
+
kill: () => {
|
|
197159
|
+
void session.kill?.();
|
|
197160
|
+
},
|
|
197161
|
+
appendInput: (input) => appendInput.call(session, input),
|
|
197162
|
+
// **每次现读**,不快照:一轮跑到收尾时 stdin 会先关,那之后 runtime 自己会回
|
|
197163
|
+
// `session-closing`,判断权本就该留在它那儿(同 `/api/chat` 那条路的写法)。
|
|
197164
|
+
get canAppendInput() {
|
|
197165
|
+
return session.canAppendInput !== false;
|
|
197166
|
+
}
|
|
197167
|
+
});
|
|
197168
|
+
void session.done.then(() => ctrl.finish("done"), () => ctrl.finish("error"));
|
|
197169
|
+
}
|
|
197170
|
+
var init_child_live_turn = __esm({
|
|
197171
|
+
"../server/src/domains/delegations/child-live-turn.ts"() {
|
|
197172
|
+
"use strict";
|
|
197173
|
+
}
|
|
197174
|
+
});
|
|
197175
|
+
|
|
196486
197176
|
// ../connectors/src/_base/wrapper-assets.ts
|
|
196487
197177
|
function isUsableWrapper(scriptPath) {
|
|
196488
197178
|
if (!(0, import_node_fs7.existsSync)(scriptPath)) return false;
|
|
@@ -201232,7 +201922,7 @@ function authorizedCommandReceipt(command, args, actor) {
|
|
|
201232
201922
|
case "promote":
|
|
201233
201923
|
return { message: "promoted to persistent" };
|
|
201234
201924
|
case "resolveEscalation":
|
|
201235
|
-
return { message: `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
|
|
201925
|
+
return { message: optStr(args, "gapId") ? `\u5DF2\u786E\u8BA4\u6062\u590D\u65B9\u6848\uFF0C\u89E3\u51B3\u7F3A\u53E3 ${str(args, "gapId")}\u3001\u5173\u95ED\u5173\u8054\u4E0A\u62A5 ${str(args, "escalationId")}\uFF1B\u8C03\u5EA6\u5668\u5C06\u5728\u5176\u4ED6\u963B\u585E\u89E3\u9664\u540E\u7EE7\u7EED\u6267\u884C\u3002` : `\u5DF2\u5173\u95ED\u8BE5\u6761\u4E0A\u62A5\uFF08${str(args, "escalationId")}\uFF09\u3002` };
|
|
201236
201926
|
default:
|
|
201237
201927
|
throw new Error(`\u547D\u4EE4\u6388\u6743\uFF1A\u6682\u65E0\u56DE\u6267\u6784\u9020 ${command}`);
|
|
201238
201928
|
}
|
|
@@ -201271,7 +201961,11 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
201271
201961
|
);
|
|
201272
201962
|
}
|
|
201273
201963
|
}
|
|
201964
|
+
const linkedGapId = escalationById(kernel.model, escalationId)?.gapId;
|
|
201965
|
+
const linkedGap = linkedGapId ? (kernel.model.gaps.get(artifactId) ?? []).find((g2) => g2.gapId === linkedGapId) : void 0;
|
|
201966
|
+
if (linkedGap && !why?.trim()) throw new Error("\u8BF7\u8BF4\u660E\u91C7\u7528\u4EC0\u4E48\u6062\u590D\u65B9\u6848\uFF0C\u518D\u63D0\u4EA4\u7F3A\u53E3\u4E0E\u4E0A\u62A5\u7684\u8054\u5408\u786E\u8BA4");
|
|
201274
201967
|
const normalizedArgs = {
|
|
201968
|
+
...linkedGap ? { gapId: linkedGap.gapId, continueExecution: true } : {},
|
|
201275
201969
|
escalationId,
|
|
201276
201970
|
// ← 已四级解析出的具体一条(绝非 undefined、绝非「全清」)
|
|
201277
201971
|
artifactId,
|
|
@@ -201362,14 +202056,15 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
201362
202056
|
}
|
|
201363
202057
|
const projectId2 = optStr(args, "project_id") ?? optStr(args, "projectId");
|
|
201364
202058
|
const schemaForSeed = (createdViaArg ?? "manual-spawn") === "seed" ? ctx.schema : void 0;
|
|
202059
|
+
const bindingState = ctx.workspaceBindingState ?? ctx.artifactState;
|
|
201365
202060
|
if (projectId2 !== void 0) {
|
|
201366
|
-
if (!ctx.projectState || !
|
|
202061
|
+
if (!ctx.projectState || !bindingState) {
|
|
201367
202062
|
throw new Error("spawn \u5E26 project_id \u9700\u8981\u670D\u52A1\u7AEF\u6CE8\u5165\u9879\u76EE\u5B58\u50A8\u4E0E\u5DE5\u5355\u7ED1\u5B9A\u5B58\u50A8");
|
|
201368
202063
|
}
|
|
201369
202064
|
const project = await ctx.projectState.getProject(projectId2);
|
|
201370
202065
|
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
201371
|
-
await
|
|
201372
|
-
} else if (createdViaArg === "seed" && ctx.projectState &&
|
|
202066
|
+
await bindingState.upsertWorkspaceBinding({ workOrderId: workspace, projectId: projectId2 });
|
|
202067
|
+
} else if (createdViaArg === "seed" && ctx.projectState && bindingState) {
|
|
201373
202068
|
await ensureWorkorderProject({
|
|
201374
202069
|
workOrderId: workspace,
|
|
201375
202070
|
...optStr(args, "title") ? { title: optStr(args, "title") } : {},
|
|
@@ -201380,7 +202075,7 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
201380
202075
|
// 由读侧从工单 brief.owner 派生(service.resolveProjectCreator)。
|
|
201381
202076
|
...ctx.workorderInitiator?.humanActorId ? { createdBy: ctx.workorderInitiator.humanActorId } : !isAgent(actor) ? { createdBy: actor } : {},
|
|
201382
202077
|
createProject: makeEnsureProjectFromStore(ctx.projectState),
|
|
201383
|
-
upsertBinding: (b2) =>
|
|
202078
|
+
upsertBinding: (b2) => bindingState.upsertWorkspaceBinding(b2),
|
|
201384
202079
|
onWarn: (m2) => console.warn(m2)
|
|
201385
202080
|
});
|
|
201386
202081
|
}
|
|
@@ -201868,10 +202563,10 @@ ${acceptanceCriteria.map((c) => `- ${c}`).join("\n")}`] : []
|
|
|
201868
202563
|
const note = optStr(args, "note");
|
|
201869
202564
|
const via = optStr(args, "via");
|
|
201870
202565
|
const target = kernel.model.annotations.get(annotationId);
|
|
201871
|
-
if (target && target.state !== "open") {
|
|
202566
|
+
if (target && target.state !== "open" && resolution !== "acknowledged") {
|
|
201872
202567
|
return { message: `annotation \u5DF2\u5173\u95ED\uFF08${target.state}\uFF09\u2014\u2014\u65E0\u9700\u518D\u5904\u7406${isReactivatedLetter(target, kernel.model.convergenceUnified) ? "\uFF1B\u8FD9\u5C01\u4FE1\u5728\u590D\u6D3B\u4E2D\uFF0C\u56DE\u5E16\u7B54\u590D\u7528 `oasis reply`\uFF0C\u6216\u6539\u5185\u5BB9 propose\uFF08\u7533\u62A5\u4F1A\u5E26\u4E0A\u5B83\uFF09" : ""}` };
|
|
201873
202568
|
}
|
|
201874
|
-
await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {} });
|
|
202569
|
+
await kernel.resolveAnnotation({ annotationId, actor, resolution, note, ...via !== void 0 ? { via } : {}, ...hand !== void 0 ? { hand } : {}, ...ctx.sessionArtifactId ? { viaNode: ctx.sessionArtifactId } : {} });
|
|
201875
202570
|
return { message: `annotation \u2192 ${resolution}${via ? `\uFF08via ${via}\uFF09` : ""}${isAgent(actor) && target?.author.startsWith("actor:human:") ? "\uFF1B\u4F5C\u8005\u4F1A\u6536\u5230\u901A\u77E5\uFF0C\u82E5\u4E0D\u8BA4\u53EF\u4F1A\u5728\u4FE1\u91CC\u56DE\u5E16\u5524\u4F60" : ""}` };
|
|
201876
202571
|
}
|
|
201877
202572
|
case "gap": {
|
|
@@ -202422,7 +203117,15 @@ async function startOasisServer(opts) {
|
|
|
202422
203117
|
resolveStore: opts.delegationStoreFor,
|
|
202423
203118
|
resolveChatSessions: resolveChatSessionsForDelegation,
|
|
202424
203119
|
resolveActors: async (companyId) => (await actorsDomain2.resolveCtx(companyId)).service,
|
|
202425
|
-
|
|
203120
|
+
// **派发完顺手把子会话这一轮登记进 live 注册表**(v2 bug 0088)。缺这一步时下面那行
|
|
203121
|
+
// `appendToChild` 恒回 `no-live-turn`——注册表里从来没有以 `childSessionId` 为键的轮,
|
|
203122
|
+
// 于是每一条 `--continue` 转达都落队列,专家要等本轮跑完才看见「手上这段作废」。
|
|
203123
|
+
// 理由与两条纪律见 `child-live-turn.ts` 的文件头。
|
|
203124
|
+
dispatchChat: async (request2) => {
|
|
203125
|
+
const session = await dispatchChat(request2);
|
|
203126
|
+
registerDelegatedChildTurn(liveChat, request2.chatSessionId, session);
|
|
203127
|
+
return session;
|
|
203128
|
+
},
|
|
202426
203129
|
appendToChild: (childSessionId, text5, extra) => liveChat.append(childSessionId, text5, extra),
|
|
202427
203130
|
// 用 lambda 转一层:`broadcastDepsFor` 在本文件里声明得更靠后(回流是异步发生的,
|
|
202428
203131
|
// 那时它早就赋过值了)。直接把值递进去会撞 TDZ。
|
|
@@ -202663,16 +203366,25 @@ async function startOasisServer(opts) {
|
|
|
202663
203366
|
...store?.enqueuePendingMessage ? {
|
|
202664
203367
|
enqueuePending: async (input) => {
|
|
202665
203368
|
const bundle = input.bundle;
|
|
203369
|
+
const fp = (p2) => {
|
|
203370
|
+
const f2 = bundle?.fingerprints?.[p2];
|
|
203371
|
+
return {
|
|
203372
|
+
...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
|
|
203373
|
+
...f2?.mtime ? { srcMtime: f2.mtime } : {}
|
|
203374
|
+
};
|
|
203375
|
+
};
|
|
202666
203376
|
const attachments = bundle ? [
|
|
202667
203377
|
...Object.entries(bundle.files ?? {}).map(([p2, text5]) => ({
|
|
202668
203378
|
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
202669
203379
|
text: text5,
|
|
202670
|
-
path: p2
|
|
203380
|
+
path: p2,
|
|
203381
|
+
...fp(p2)
|
|
202671
203382
|
})),
|
|
202672
203383
|
...Object.entries(bundle.binaryFiles ?? {}).map(([p2, b64]) => ({
|
|
202673
203384
|
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
202674
203385
|
bytesBase64: b64,
|
|
202675
|
-
path: p2
|
|
203386
|
+
path: p2,
|
|
203387
|
+
...fp(p2)
|
|
202676
203388
|
}))
|
|
202677
203389
|
] : [];
|
|
202678
203390
|
const { randomUUID: randomUUID40 } = await import("node:crypto");
|
|
@@ -202702,7 +203414,15 @@ async function startOasisServer(opts) {
|
|
|
202702
203414
|
// ——生产 `deliverToAgent:512` 会开轮/append,用户听到空消息且下一场 UPDATE 再把已完成
|
|
202703
203415
|
// 的行改回 delivering,违背「只补账、不投递」语义。
|
|
202704
203416
|
enqueueRegisterPending: async (input) => {
|
|
202705
|
-
const attachments = input.paths.map((p2) =>
|
|
203417
|
+
const attachments = input.paths.map((p2) => {
|
|
203418
|
+
const f2 = input.fingerprints?.[p2];
|
|
203419
|
+
return {
|
|
203420
|
+
name: p2.slice(p2.lastIndexOf("/") + 1) || p2,
|
|
203421
|
+
path: p2,
|
|
203422
|
+
...typeof f2?.size === "number" ? { srcSize: f2.size } : {},
|
|
203423
|
+
...f2?.mtime ? { srcMtime: f2.mtime } : {}
|
|
203424
|
+
};
|
|
203425
|
+
});
|
|
202706
203426
|
const { randomUUID: randomUUID40 } = await import("node:crypto");
|
|
202707
203427
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
202708
203428
|
try {
|
|
@@ -203136,6 +203856,22 @@ async function startOasisServer(opts) {
|
|
|
203136
203856
|
}));
|
|
203137
203857
|
return;
|
|
203138
203858
|
}
|
|
203859
|
+
if (url.pathname.startsWith("/api/files/")) {
|
|
203860
|
+
const company = opts.resolveCompanyContext ? await opts.resolveCompanyContext(actor, req.headers, url.pathname) : void 0;
|
|
203861
|
+
if (company && company.kind !== "ok") {
|
|
203862
|
+
res.writeHead(403, { "content-type": "application/json" }).end(JSON.stringify({
|
|
203863
|
+
error: { code: "FORBIDDEN", message: "\u65E0\u6743\u8BFB\u53D6\u8BE5\u516C\u53F8\u7684\u6587\u4EF6" }
|
|
203864
|
+
}));
|
|
203865
|
+
return;
|
|
203866
|
+
}
|
|
203867
|
+
await serveFileRequest(
|
|
203868
|
+
req,
|
|
203869
|
+
res,
|
|
203870
|
+
url,
|
|
203871
|
+
await resolveEngine(company?.kind === "ok" ? company.companyId : void 0)
|
|
203872
|
+
);
|
|
203873
|
+
return;
|
|
203874
|
+
}
|
|
203139
203875
|
if (url.pathname === "/api/whoami" && req.method === "GET") {
|
|
203140
203876
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ actor }));
|
|
203141
203877
|
return;
|
|
@@ -203175,6 +203911,7 @@ async function startOasisServer(opts) {
|
|
|
203175
203911
|
}
|
|
203176
203912
|
};
|
|
203177
203913
|
const artifactStateNow = async () => opts.artifactStateFor ? await opts.artifactStateFor(currentCompanyId) : opts.artifactState;
|
|
203914
|
+
const projectStateNow = async () => opts.projectStateFor ? await opts.projectStateFor(currentCompanyId) : opts.projectState;
|
|
203178
203915
|
if (url.pathname.startsWith("/api/channels")) {
|
|
203179
203916
|
const json = (status, body2) => {
|
|
203180
203917
|
res.writeHead(status, { "content-type": "application/json" }).end(JSON.stringify(body2));
|
|
@@ -203273,6 +204010,10 @@ async function startOasisServer(opts) {
|
|
|
203273
204010
|
}
|
|
203274
204011
|
}
|
|
203275
204012
|
const chatAnchorMessageId = await anchorMessageIdFor(chatOrigin, chatStoreNowOrNull);
|
|
204013
|
+
const spawnArgs = body2.args;
|
|
204014
|
+
const needsProject = body2.command === "spawn" && (spawnArgs?.["createdVia"] === "seed" || spawnArgs?.["project_id"] !== void 0 || spawnArgs?.["projectId"] !== void 0);
|
|
204015
|
+
const commandProjects = needsProject ? await projectStateNow() : opts.projectState;
|
|
204016
|
+
const commandBindings = needsProject && commandProjects ? await artifactStateNow() : void 0;
|
|
203276
204017
|
const result = await runCommand(
|
|
203277
204018
|
engine.kernel,
|
|
203278
204019
|
engine.blobs,
|
|
@@ -203291,13 +204032,14 @@ async function startOasisServer(opts) {
|
|
|
203291
204032
|
...chatOrigin !== void 0 ? { chatOrigin } : {},
|
|
203292
204033
|
...chatAnchorMessageId !== void 0 ? { chatAnchorMessageId } : {},
|
|
203293
204034
|
...sessionEscalationId !== void 0 ? { sessionEscalationId } : {},
|
|
203294
|
-
...
|
|
204035
|
+
...commandProjects ? { projectState: commandProjects } : {},
|
|
204036
|
+
...commandBindings ? { workspaceBindingState: commandBindings } : {},
|
|
203295
204037
|
...opts.artifactState ? { artifactState: opts.artifactState } : {},
|
|
203296
204038
|
...opts.schema ? { schema: opts.schema } : {},
|
|
203297
204039
|
...engine.registry ? { registry: engine.registry } : {},
|
|
203298
204040
|
...opts.resolveAssignableRoles ? { resolveAssignableRoles: opts.resolveAssignableRoles } : {},
|
|
203299
204041
|
...opts.planWorkorderWithAgent ? { planWorkorderWithAgent: opts.planWorkorderWithAgent } : {},
|
|
203300
|
-
...opts.resolveCodeRepo ? { resolveCodeRepo: opts.resolveCodeRepo } : {},
|
|
204042
|
+
...opts.resolveCodeRepo ? { resolveCodeRepo: (ws) => opts.resolveCodeRepo(ws, currentCompanyId) } : {},
|
|
203301
204043
|
...opts.dispatchProduce ? { dispatchProduce: opts.dispatchProduce } : {},
|
|
203302
204044
|
...wodrafts ? { workorderDrafts: wodrafts } : {},
|
|
203303
204045
|
workorderDraftPlannerIssues: wodraftPlannerIssues,
|
|
@@ -203585,6 +204327,7 @@ async function startOasisServer(opts) {
|
|
|
203585
204327
|
}
|
|
203586
204328
|
const resuming = resumeState.hasNodes;
|
|
203587
204329
|
let sessionProjectId;
|
|
204330
|
+
let sessionBindingState;
|
|
203588
204331
|
if (body2.chatSessionId && opts.chatSession) {
|
|
203589
204332
|
const applyChatStore = await chatStoreNow();
|
|
203590
204333
|
const session = await applyChatStore.getSession(body2.chatSessionId);
|
|
@@ -203593,10 +204336,12 @@ async function startOasisServer(opts) {
|
|
|
203593
204336
|
}
|
|
203594
204337
|
sessionProjectId = session.projectId ?? void 0;
|
|
203595
204338
|
if (sessionProjectId) {
|
|
203596
|
-
|
|
204339
|
+
const projects = await projectStateNow();
|
|
204340
|
+
sessionBindingState = await artifactStateNow();
|
|
204341
|
+
if (!projects || !sessionBindingState) {
|
|
203597
204342
|
throw new Error("chat session \u5E26 projectId \u65F6\u9700\u8981\u670D\u52A1\u7AEF\u6CE8\u5165\u9879\u76EE\u5B58\u50A8\u4E0E\u5DE5\u5355\u7ED1\u5B9A\u5B58\u50A8");
|
|
203598
204343
|
}
|
|
203599
|
-
const project = await
|
|
204344
|
+
const project = await projects.getProject(sessionProjectId);
|
|
203600
204345
|
if (!project) throw new Error(`project not found: ${sessionProjectId}`);
|
|
203601
204346
|
}
|
|
203602
204347
|
}
|
|
@@ -203646,7 +204391,7 @@ async function startOasisServer(opts) {
|
|
|
203646
204391
|
return;
|
|
203647
204392
|
}
|
|
203648
204393
|
if (targetWs && sessionProjectId) {
|
|
203649
|
-
await
|
|
204394
|
+
await sessionBindingState.upsertWorkspaceBinding({ workOrderId: targetWs, projectId: sessionProjectId });
|
|
203650
204395
|
}
|
|
203651
204396
|
const result = resuming ? { interventionId: "", applied: 0, resumed: true } : await engine.kernel.applyIntervention(body2.plan, body2.baseSeqs, actor);
|
|
203652
204397
|
if (resuming) {
|
|
@@ -204829,8 +205574,26 @@ ${composed}`;
|
|
|
204829
205574
|
});
|
|
204830
205575
|
}
|
|
204831
205576
|
const itemStore = persistTarget ? opts.resolveChatItems ? await opts.resolveChatItems(currentCompanyId).catch(() => void 0) : opts.chatItems : void 0;
|
|
205577
|
+
const versionSeed = itemStore ? await itemStore.sessionVersionCursor(persistTarget?.id ?? session.id).catch(() => 0) : 0;
|
|
204832
205578
|
const itemLedger = new ChatItemLedger({
|
|
204833
205579
|
...itemStore ? { items: itemStore } : {},
|
|
205580
|
+
versionSeed,
|
|
205581
|
+
/* 用户那条消息一落库就推上 v3 流(ADR-0510 D2 的收口)。
|
|
205582
|
+
不推的话:乐观条没有服务端号、被钉在列表末尾,而 agent 的正文带着更大的号从
|
|
205583
|
+
直播流到达、进「有号」段 ⇒ **回复显示在用户消息上面**,直到下一次快照才跳回去
|
|
205584
|
+
(2026-09-09 用户现场)。这里把窗口从「等下一次快照」压到「一次 INSERT 往返」。
|
|
205585
|
+
只推 user:assistant 的每一条本来就走 `emitV3`。 */
|
|
205586
|
+
onPersisted: (info) => {
|
|
205587
|
+
if (info.role !== "user") return;
|
|
205588
|
+
opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
|
|
205589
|
+
itemId: info.itemId,
|
|
205590
|
+
ord: info.ord,
|
|
205591
|
+
version: info.version,
|
|
205592
|
+
text: info.text,
|
|
205593
|
+
...clientSubmitId ? { clientSubmitId } : {},
|
|
205594
|
+
...effectiveAttachments.length ? { attachments: effectiveAttachments } : {}
|
|
205595
|
+
});
|
|
205596
|
+
},
|
|
204834
205597
|
sessionId: persistTarget?.id ?? session.id,
|
|
204835
205598
|
// 优先账本 id(`chat_session_turns.id`,跨进程唯一、重启不失忆);账本没接入才兜底。
|
|
204836
205599
|
turnId: heldTurn?.id ?? fallbackTurnId(session.runId),
|
|
@@ -206141,6 +206904,7 @@ var init_server3 = __esm({
|
|
|
206141
206904
|
init_src();
|
|
206142
206905
|
init_src();
|
|
206143
206906
|
init_src();
|
|
206907
|
+
init_file_http();
|
|
206144
206908
|
init_build_info();
|
|
206145
206909
|
init_artifact_content2();
|
|
206146
206910
|
init_command_policy();
|
|
@@ -206182,6 +206946,7 @@ var init_server3 = __esm({
|
|
|
206182
206946
|
init_workorder_terminal();
|
|
206183
206947
|
init_knowledge2();
|
|
206184
206948
|
init_assembly();
|
|
206949
|
+
init_child_live_turn();
|
|
206185
206950
|
init_src8();
|
|
206186
206951
|
init_continuation();
|
|
206187
206952
|
githubAppPending = new PendingAppCreations();
|
|
@@ -206762,7 +207527,12 @@ var init_memory_registry_store = __esm({
|
|
|
206762
207527
|
async putVariable(v2) {
|
|
206763
207528
|
const mk = this._varKey(v2.key, v2.actorId, v2.projectId);
|
|
206764
207529
|
const ex = this.variables.get(mk);
|
|
206765
|
-
|
|
207530
|
+
const keptName = v2.name ?? ex?.name;
|
|
207531
|
+
this.variables.set(mk, {
|
|
207532
|
+
...v2,
|
|
207533
|
+
...ex?.lastUsedAt !== void 0 ? { lastUsedAt: ex.lastUsedAt } : {},
|
|
207534
|
+
...keptName ? { name: keptName } : {}
|
|
207535
|
+
});
|
|
206766
207536
|
}
|
|
206767
207537
|
async touchVariableUsage(key, actorId, projectId2, when) {
|
|
206768
207538
|
const mk = this._varKey(key, actorId, projectId2);
|
|
@@ -209263,6 +210033,30 @@ function foldSingleNodeTasks(items) {
|
|
|
209263
210033
|
}
|
|
209264
210034
|
return folded;
|
|
209265
210035
|
}
|
|
210036
|
+
function fillFolderSizes(items) {
|
|
210037
|
+
const folders = items.filter((i) => i.kind === "folder" && i.path);
|
|
210038
|
+
if (folders.length === 0) return;
|
|
210039
|
+
const files = items.filter((i) => i.kind !== "folder" && i.path);
|
|
210040
|
+
for (const folder of folders) {
|
|
210041
|
+
const prefix = `${folder.path}/`;
|
|
210042
|
+
let sum = 0;
|
|
210043
|
+
let seen = 0;
|
|
210044
|
+
let complete = true;
|
|
210045
|
+
for (const file of files) {
|
|
210046
|
+
if (!file.path.startsWith(prefix)) continue;
|
|
210047
|
+
seen += 1;
|
|
210048
|
+
if (file.size == null) {
|
|
210049
|
+
complete = false;
|
|
210050
|
+
break;
|
|
210051
|
+
}
|
|
210052
|
+
sum += file.size;
|
|
210053
|
+
}
|
|
210054
|
+
folder.size = seen > 0 && complete ? sum : null;
|
|
210055
|
+
}
|
|
210056
|
+
}
|
|
210057
|
+
function engineContentKind(k2) {
|
|
210058
|
+
return k2 === "manifest" ? "manifest" : k2 === "external" ? "external-pin" : "inline-blob";
|
|
210059
|
+
}
|
|
209266
210060
|
function namedError(name, message) {
|
|
209267
210061
|
const err = new Error(message);
|
|
209268
210062
|
err.name = name;
|
|
@@ -209484,6 +210278,13 @@ var init_service4 = __esm({
|
|
|
209484
210278
|
async listProjects() {
|
|
209485
210279
|
return [...this.projects.values()].map((project) => ({ ...project }));
|
|
209486
210280
|
}
|
|
210281
|
+
/** 与 Postgres 同语义:主记录 + 成员 + 上传文件 + 内容位置一并清掉;不存在的 id 幂等返回。 */
|
|
210282
|
+
async deleteProject(id) {
|
|
210283
|
+
this.projects.delete(id);
|
|
210284
|
+
this.members.delete(id);
|
|
210285
|
+
for (const [fileId, f2] of this.files) if (f2.projectId === id) this.files.delete(fileId);
|
|
210286
|
+
for (const [locId, l] of this.contentLocations) if (l.projectId === id) this.contentLocations.delete(locId);
|
|
210287
|
+
}
|
|
209487
210288
|
async replaceProjectMembers(projectId2, members) {
|
|
209488
210289
|
this.members.set(projectId2, new Map(members.map((member) => [member.actorId, cloneProjectMember(member)])));
|
|
209489
210290
|
}
|
|
@@ -209629,6 +210430,7 @@ var init_service4 = __esm({
|
|
|
209629
210430
|
}
|
|
209630
210431
|
projectCreatedHandler;
|
|
209631
210432
|
onProjectNameChanged;
|
|
210433
|
+
onProjectDeleted;
|
|
209632
210434
|
/**
|
|
209633
210435
|
* Cross-domain follow-up for project-scoped resources. Project persistence stays authoritative:
|
|
209634
210436
|
* a failed follow-up is reported but never turns an already-created project into a false 500.
|
|
@@ -209643,6 +210445,10 @@ var init_service4 = __esm({
|
|
|
209643
210445
|
setOnProjectNameChanged(handler) {
|
|
209644
210446
|
this.onProjectNameChanged = handler;
|
|
209645
210447
|
}
|
|
210448
|
+
/** 删项目后的跨域清理钩子(会话解绑 + 项目变量清理)。装配层注入;缺省 = 无副作用。 */
|
|
210449
|
+
setOnProjectDeleted(handler) {
|
|
210450
|
+
this.onProjectDeleted = handler;
|
|
210451
|
+
}
|
|
209646
210452
|
/** 工单→项目绑定查询(content-location add 的项目匹配守卫用)。 */
|
|
209647
210453
|
async getWorkspaceBinding(workOrderId) {
|
|
209648
210454
|
return this.artifacts.getWorkspaceBinding(workOrderId);
|
|
@@ -210031,6 +210837,60 @@ var init_service4 = __esm({
|
|
|
210031
210837
|
await this.artifacts.upsertWorkspaceBinding(binding);
|
|
210032
210838
|
return binding;
|
|
210033
210839
|
}
|
|
210840
|
+
/**
|
|
210841
|
+
* 删除项目(帧 `444:1792` 三点菜单第二项,口径由发起人 2026-09-09 拍板)。
|
|
210842
|
+
*
|
|
210843
|
+
* **只删项目这个壳**——项目主记录 + 成员名单 + 上传文件记录 + 内容位置。任务和会话一条都不删:
|
|
210844
|
+
*
|
|
210845
|
+
* 1. 绑到本项目的工单**不管在跑还是没在跑**,一律 `unlinkWorkorder` 回落到自己的
|
|
210846
|
+
* `proj_tmp_<slug>`,也就是读侧的「无分类项目」。发起人明确不要「有任务在跑就不让删」
|
|
210847
|
+
* 这道闸,所以这里没有任何在跑判断。
|
|
210848
|
+
* 2. 搬工单的同时**把本项目的 git 类内容位置复制一份到每个工单的临时项目上**。
|
|
210849
|
+
* 这条不是可选的:`git/resolve.ts` 的 `resolveCodeRepo` 是**派发时**沿
|
|
210850
|
+
* `binding → project → listContentLocations` 实时解的,不带过去的话,正在跑的任务
|
|
210851
|
+
* 下一个节点就取不到代码仓——正是 `ephemeral-project.ts` 顶部那条五环事故链。
|
|
210852
|
+
* 3. 会话的 `project_id` 置空、项目作用域的变量/密钥清理,走 {@link OnProjectDeleted} 钩子
|
|
210853
|
+
* (本 service 对会话域与凭据域零耦合,同 `onProjectNameChanged` 的注入模式)。
|
|
210854
|
+
*
|
|
210855
|
+
* 拒绝两类 id:`proj:uncategorized`(读侧虚拟卡,存储里没有真行)与 `proj_tmp_*`
|
|
210856
|
+
* (工单的兜底归属,删掉等于把工单变成无绑定态——契约里 `Workspace.projectId` 必填)。
|
|
210857
|
+
*/
|
|
210858
|
+
async deleteProject(id) {
|
|
210859
|
+
const projectId2 = id?.trim();
|
|
210860
|
+
if (!projectId2) throw new Error("project id is required");
|
|
210861
|
+
if (isUncategorizedProjectId(projectId2)) throw new Error(`project not deletable: ${projectId2}`);
|
|
210862
|
+
if (isEphemeralProject(projectId2)) throw new Error(`project not deletable: ${projectId2}`);
|
|
210863
|
+
const project = await this.projects.getProject(projectId2);
|
|
210864
|
+
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
210865
|
+
const gitLocations = (await this.projects.listContentLocations(projectId2)).filter((loc) => loc.kind === "external-home" && loc.externalHome?.system === "git");
|
|
210866
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
210867
|
+
const bindings = await this.artifacts.listWorkspaceBindings();
|
|
210868
|
+
const movedWorkOrderIds = [];
|
|
210869
|
+
for (const binding of bindings) {
|
|
210870
|
+
if (binding.projectId !== projectId2) continue;
|
|
210871
|
+
const moved = await this.unlinkWorkorder(binding.workOrderId);
|
|
210872
|
+
movedWorkOrderIds.push(binding.workOrderId);
|
|
210873
|
+
for (const loc of gitLocations) {
|
|
210874
|
+
await this.projects.addContentLocation({
|
|
210875
|
+
...loc,
|
|
210876
|
+
projectId: moved.projectId,
|
|
210877
|
+
// 派生 id:同一条位置搬到同一个临时项目永远算出同一个 id(addContentLocation 是
|
|
210878
|
+
// upsert),重跑不会堆出重复的仓库行。
|
|
210879
|
+
locationId: `pcl-${moved.projectId}-carried-${loc.locationId}`,
|
|
210880
|
+
updatedAt: now
|
|
210881
|
+
});
|
|
210882
|
+
}
|
|
210883
|
+
}
|
|
210884
|
+
await this.projects.deleteProject(projectId2);
|
|
210885
|
+
if (this.onProjectDeleted) {
|
|
210886
|
+
try {
|
|
210887
|
+
await this.onProjectDeleted({ projectId: projectId2, projectName: project.name });
|
|
210888
|
+
} catch (error2) {
|
|
210889
|
+
console.warn(`[projects] project-deleted follow-up failed for ${projectId2}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
210890
|
+
}
|
|
210891
|
+
}
|
|
210892
|
+
return { projectId: projectId2, movedWorkOrderIds, carriedGitLocationCount: gitLocations.length };
|
|
210893
|
+
}
|
|
210034
210894
|
/**
|
|
210035
210895
|
* 项目关联工单(帧 `1355:4918`):把内部私有的 `listProjectWorkorders` 暴露给路由层。
|
|
210036
210896
|
* `projectId="proj:uncategorized"` 时聚合所有 `proj_tmp_*` 绑定的工单。
|
|
@@ -210091,6 +210951,7 @@ var init_service4 = __esm({
|
|
|
210091
210951
|
const projectByWorkOrder = new Map(bindings.map((b2) => [b2.workOrderId, b2.projectId]));
|
|
210092
210952
|
const workorderSummaries = await this.listProjectWorkorders(projectId2);
|
|
210093
210953
|
const workorderTitleById = new Map(workorderSummaries.map((w2) => [w2.id, w2.title]));
|
|
210954
|
+
const workorderOwnerById = new Map(workorderSummaries.map((w2) => [w2.id, w2.owner?.id ?? null]));
|
|
210094
210955
|
const engineRows = await this.collectEngineFilesForProject(
|
|
210095
210956
|
projectId2,
|
|
210096
210957
|
workorderSummaries.map((w2) => w2.id),
|
|
@@ -210109,6 +210970,8 @@ var init_service4 = __esm({
|
|
|
210109
210970
|
const taskId = record8.createdFromWorkOrderId ?? artifact?.workspace ?? null;
|
|
210110
210971
|
const taskTitle = taskId ? workorderTitleById.get(taskId) ?? null : null;
|
|
210111
210972
|
const creatorId = revision.author || null;
|
|
210973
|
+
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
210974
|
+
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210112
210975
|
const folderPath = taskId ? `wo:${taskId}/node:${record8.artifactId}` : null;
|
|
210113
210976
|
if (folderPath) items.push({
|
|
210114
210977
|
kind: "folder",
|
|
@@ -210121,13 +210984,14 @@ var init_service4 = __esm({
|
|
|
210121
210984
|
size: null,
|
|
210122
210985
|
taskId,
|
|
210123
210986
|
taskTitle,
|
|
210124
|
-
creatorKind
|
|
210125
|
-
creatorId
|
|
210126
|
-
creatorName
|
|
210987
|
+
creatorKind,
|
|
210988
|
+
creatorId,
|
|
210989
|
+
creatorName,
|
|
210127
210990
|
updatedAt: record8.updatedAt
|
|
210128
210991
|
});
|
|
210129
210992
|
const content3 = revision.contentKind === "manifest" ? await this.readContentRefText(revision.contentRef) : null;
|
|
210130
210993
|
const files2 = content3 !== null ? tryParseManifestFiles(content3) : void 0;
|
|
210994
|
+
const wholeSize = files2?.length ? null : content3 !== null ? Buffer.byteLength(content3, "utf8") : await this.contentByteSize(revision.contentRef, revision.contentKind);
|
|
210131
210995
|
for (const file of files2?.length ? files2 : [null]) {
|
|
210132
210996
|
items.push({
|
|
210133
210997
|
kind: "artifact",
|
|
@@ -210137,12 +211001,15 @@ var init_service4 = __esm({
|
|
|
210137
211001
|
filePath: file?.path ?? null,
|
|
210138
211002
|
depth: folderPath ? 2 : 0,
|
|
210139
211003
|
type: record8.type,
|
|
210140
|
-
size: file
|
|
211004
|
+
size: file ? file.size ?? null : wholeSize,
|
|
210141
211005
|
taskId,
|
|
210142
211006
|
taskTitle,
|
|
210143
|
-
|
|
211007
|
+
// 内容形态/引用只挂在**整份产物**那一行;装箱单摊出来的成员行有自己的后缀,
|
|
211008
|
+
// 按后缀走图标即可,挂上 manifest 反而会把 `Dockerfile` 这种无后缀成员画成压缩包。
|
|
211009
|
+
...file ? {} : { contentKind: revision.contentKind, contentRef: revision.contentRef },
|
|
211010
|
+
creatorKind,
|
|
210144
211011
|
creatorId,
|
|
210145
|
-
creatorName
|
|
211012
|
+
creatorName,
|
|
210146
211013
|
updatedAt: record8.updatedAt
|
|
210147
211014
|
});
|
|
210148
211015
|
}
|
|
@@ -210154,6 +211021,7 @@ var init_service4 = __esm({
|
|
|
210154
211021
|
if (foldedTasks.has(item.taskId)) continue;
|
|
210155
211022
|
taskFolders.add(item.taskId);
|
|
210156
211023
|
const path41 = `wo:${item.taskId}`;
|
|
211024
|
+
const ownerId = workorderOwnerById.get(item.taskId) ?? null;
|
|
210157
211025
|
items.push({
|
|
210158
211026
|
kind: "folder",
|
|
210159
211027
|
id: `folder:${path41}`,
|
|
@@ -210165,9 +211033,9 @@ var init_service4 = __esm({
|
|
|
210165
211033
|
size: null,
|
|
210166
211034
|
taskId: item.taskId,
|
|
210167
211035
|
taskTitle: item.taskTitle,
|
|
210168
|
-
creatorKind: null,
|
|
210169
|
-
creatorId:
|
|
210170
|
-
creatorName: null,
|
|
211036
|
+
creatorKind: ownerId ? ownerId.startsWith("actor:human:") ? "human" : "agent" : null,
|
|
211037
|
+
creatorId: ownerId,
|
|
211038
|
+
creatorName: ownerId ? this.resolveActorName(ownerId) : null,
|
|
210171
211039
|
updatedAt: item.updatedAt
|
|
210172
211040
|
});
|
|
210173
211041
|
}
|
|
@@ -210193,6 +211061,7 @@ var init_service4 = __esm({
|
|
|
210193
211061
|
updatedAt: file.uploadedAt
|
|
210194
211062
|
});
|
|
210195
211063
|
}
|
|
211064
|
+
fillFolderSizes(items);
|
|
210196
211065
|
items.sort((a, b2) => {
|
|
210197
211066
|
const pathA = a.path ?? "";
|
|
210198
211067
|
const pathB = b2.path ?? "";
|
|
@@ -210236,6 +211105,9 @@ var init_service4 = __esm({
|
|
|
210236
211105
|
const work = worksById.get(acceptedId);
|
|
210237
211106
|
const folderPath = `wo:${workOrderId}/node:${node2.id}`;
|
|
210238
211107
|
const folderUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
|
|
211108
|
+
const creatorId = work?.assigneeActorId ?? null;
|
|
211109
|
+
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
211110
|
+
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210239
211111
|
out.push({
|
|
210240
211112
|
kind: "folder",
|
|
210241
211113
|
id: `folder:${folderPath}`,
|
|
@@ -210247,14 +211119,11 @@ var init_service4 = __esm({
|
|
|
210247
211119
|
size: null,
|
|
210248
211120
|
taskId: workOrderId,
|
|
210249
211121
|
taskTitle,
|
|
210250
|
-
creatorKind
|
|
210251
|
-
creatorId
|
|
210252
|
-
creatorName
|
|
211122
|
+
creatorKind,
|
|
211123
|
+
creatorId,
|
|
211124
|
+
creatorName,
|
|
210253
211125
|
updatedAt: folderUpdatedAt
|
|
210254
211126
|
});
|
|
210255
|
-
const creatorId = work?.assigneeActorId ?? null;
|
|
210256
|
-
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
210257
|
-
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
210258
211127
|
const sortedArts = arts.slice().sort((a, b2) => a.ordinal - b2.ordinal);
|
|
210259
211128
|
for (const a of sortedArts) {
|
|
210260
211129
|
const artifactId = node2.id;
|
|
@@ -210293,7 +211162,9 @@ var init_service4 = __esm({
|
|
|
210293
211162
|
filePath: null,
|
|
210294
211163
|
depth: 2,
|
|
210295
211164
|
type: artifactType || "",
|
|
210296
|
-
size:
|
|
211165
|
+
size: await this.contentByteSize(a.contentRef, engineContentKind(a.contentKind)),
|
|
211166
|
+
contentKind: engineContentKind(a.contentKind),
|
|
211167
|
+
...a.contentRef ? { contentRef: a.contentRef } : {},
|
|
210297
211168
|
taskId: workOrderId,
|
|
210298
211169
|
taskTitle,
|
|
210299
211170
|
creatorKind,
|
|
@@ -210645,7 +211516,6 @@ var init_service4 = __esm({
|
|
|
210645
211516
|
const worksById = new Map(snap.works.map((w2) => [w2.id, w2]));
|
|
210646
211517
|
const binding = this.artifacts.getWorkspaceBinding ? await this.artifacts.getWorkspaceBinding(workOrderId).catch(() => null) : null;
|
|
210647
211518
|
const projectId2 = binding?.projectId ?? ephemeralProjectIdFor(workOrderId);
|
|
210648
|
-
const mapContentKind = (k2) => k2 === "manifest" ? "manifest" : k2 === "external" ? "external-pin" : "inline-blob";
|
|
210649
211519
|
const out = [];
|
|
210650
211520
|
for (const node2 of snap.nodes) {
|
|
210651
211521
|
if (!node2.latestAcceptId) continue;
|
|
@@ -210665,7 +211535,7 @@ var init_service4 = __esm({
|
|
|
210665
211535
|
contentRef: a.contentRef,
|
|
210666
211536
|
title: node2.title || a.name,
|
|
210667
211537
|
// 节点标题优先;否则用 engine artifact 的 "output" 兜底
|
|
210668
|
-
contentKind:
|
|
211538
|
+
contentKind: engineContentKind(a.contentKind)
|
|
210669
211539
|
}));
|
|
210670
211540
|
out.push({
|
|
210671
211541
|
outputId: `engine:${acceptedId}`,
|
|
@@ -210711,6 +211581,19 @@ var init_service4 = __esm({
|
|
|
210711
211581
|
}
|
|
210712
211582
|
return { ...withKind, size: Buffer.byteLength(content3, "utf8") };
|
|
210713
211583
|
}
|
|
211584
|
+
/**
|
|
211585
|
+
* 一份产物正文的字节数——**与任务页「相关产物」共用同一条判据**(见 {@link enrichNodeOutputDocument}):
|
|
211586
|
+
* 不看 `contentKind` 名义,只看正文能不能从 BlobStore 读到;读不到就是 null。
|
|
211587
|
+
*
|
|
211588
|
+
* 于是 git 提交号 / Figma version 这类真·外部钉、未纳管、`empty` 都诚实无大小(前端显「—」),
|
|
211589
|
+
* 而 `inline-blob` 的任务书、PRD、ADR 会报出真实字节数——此前 files-view 这条路径**恒发 null**,
|
|
211590
|
+
* 同一份任务书在任务页有 8.0 KB、在项目页却是「—」。
|
|
211591
|
+
*/
|
|
211592
|
+
async contentByteSize(contentRef, contentKind) {
|
|
211593
|
+
if (contentKind === "empty" || !contentRef?.trim()) return null;
|
|
211594
|
+
const content3 = await this.readContentRefText(contentRef);
|
|
211595
|
+
return content3 === null ? null : Buffer.byteLength(content3, "utf8");
|
|
211596
|
+
}
|
|
210714
211597
|
/** 正文读取的唯一入口:只认 BlobStore;没配 / 没纳管 / 读失败一律 null(调用方据此诚实缺 size)。 */
|
|
210715
211598
|
async readContentRefText(contentRef) {
|
|
210716
211599
|
if (!this.blobs) return null;
|
|
@@ -217718,6 +218601,8 @@ ${input.description}
|
|
|
217718
218601
|
}
|
|
217719
218602
|
await this.opts.store.putVariable({
|
|
217720
218603
|
key: v2.key,
|
|
218604
|
+
// 展示名(子 PRD《密钥管理》§2)。没传就不传下去——存储层按「不动」处理,不抹既有名字。
|
|
218605
|
+
...v2.name !== void 0 && v2.name !== "" ? { name: v2.name } : {},
|
|
217721
218606
|
scope: v2.scope,
|
|
217722
218607
|
...v2.actorId !== void 0 ? { actorId: v2.actorId } : {},
|
|
217723
218608
|
...v2.projectId !== void 0 ? { projectId: v2.projectId } : {},
|
|
@@ -217786,6 +218671,7 @@ ${input.description}
|
|
|
217786
218671
|
const isPlain = r.valueEncrypted.startsWith("plain:");
|
|
217787
218672
|
return {
|
|
217788
218673
|
key: r.key,
|
|
218674
|
+
...r.name !== void 0 ? { name: r.name } : {},
|
|
217789
218675
|
scope: r.scope,
|
|
217790
218676
|
...r.actorId !== void 0 ? { actorId: r.actorId } : {},
|
|
217791
218677
|
...r.projectId !== void 0 ? { projectId: r.projectId } : {},
|
|
@@ -219613,6 +220499,8 @@ function actorsDomain(opts) {
|
|
|
219613
220499
|
key: b2.key,
|
|
219614
220500
|
value: b2.value,
|
|
219615
220501
|
scope,
|
|
220502
|
+
// 展示名(子 PRD《密钥管理》§2「名称」);不传 = 不改既有名字。
|
|
220503
|
+
...typeof b2.name === "string" && b2.name.trim() ? { name: b2.name.trim() } : {},
|
|
219616
220504
|
...scope === "project" ? { projectId: b2.projectId } : {},
|
|
219617
220505
|
...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
|
|
219618
220506
|
...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
|
|
@@ -219684,6 +220572,8 @@ function actorsDomain(opts) {
|
|
|
219684
220572
|
value: b2.value,
|
|
219685
220573
|
actorId,
|
|
219686
220574
|
scope: "personal",
|
|
220575
|
+
// 展示名(同上)。组织页「添加密钥 → 新建」走的就是这条端点。
|
|
220576
|
+
...typeof b2.name === "string" && b2.name.trim() ? { name: b2.name.trim() } : {},
|
|
219687
220577
|
...b2.deliveryMode !== void 0 ? { deliveryMode: b2.deliveryMode } : {},
|
|
219688
220578
|
...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
|
|
219689
220579
|
...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
|
|
@@ -220740,6 +221630,22 @@ function projectsDomain(opts) {
|
|
|
220740
221630
|
throw mapProjectError(err);
|
|
220741
221631
|
}
|
|
220742
221632
|
});
|
|
221633
|
+
router.delete("/api/projects/:id", async (req) => {
|
|
221634
|
+
try {
|
|
221635
|
+
const result = await (await svc(req)).deleteProject(req.params.id);
|
|
221636
|
+
return {
|
|
221637
|
+
status: 200,
|
|
221638
|
+
body: {
|
|
221639
|
+
ok: true,
|
|
221640
|
+
project_id: result.projectId,
|
|
221641
|
+
moved_work_order_ids: result.movedWorkOrderIds,
|
|
221642
|
+
carried_git_location_count: result.carriedGitLocationCount
|
|
221643
|
+
}
|
|
221644
|
+
};
|
|
221645
|
+
} catch (err) {
|
|
221646
|
+
throw mapProjectError(err);
|
|
221647
|
+
}
|
|
221648
|
+
});
|
|
220743
221649
|
router.get("/api/projects/:id/members", async (req) => {
|
|
220744
221650
|
try {
|
|
220745
221651
|
const members = await (await svc(req)).listProjectMembers(req.params.id);
|
|
@@ -221162,6 +222068,9 @@ function mapProjectError(err) {
|
|
|
221162
222068
|
if (err instanceof Error && err.message.startsWith("project not found:")) {
|
|
221163
222069
|
return new ApiError(404, "NOT_FOUND", err.message);
|
|
221164
222070
|
}
|
|
222071
|
+
if (err instanceof Error && err.message.startsWith("project not deletable:")) {
|
|
222072
|
+
return new ApiError(400, "PROJECT_NOT_DELETABLE", err.message);
|
|
222073
|
+
}
|
|
221165
222074
|
if (err instanceof Error && err.message.startsWith("missing artifact revision registration:")) {
|
|
221166
222075
|
return new ApiError(400, "MISSING_ARTIFACT_REVISION", err.message);
|
|
221167
222076
|
}
|
|
@@ -221469,6 +222378,10 @@ function toApiProjectFilesView(view) {
|
|
|
221469
222378
|
file_path: item.filePath ?? null,
|
|
221470
222379
|
depth: item.depth,
|
|
221471
222380
|
type: item.type,
|
|
222381
|
+
// 内容形态 + 内容引用:前端名称列给**没有后缀**的行选图标用(正文→文本、git 提交→代码、
|
|
222382
|
+
// 其它外部引用→链接)。交付物整份产物行才有,上传文件与文件夹为 null。
|
|
222383
|
+
content_kind: item.contentKind ?? null,
|
|
222384
|
+
content_ref: item.contentRef ?? null,
|
|
221472
222385
|
size: item.size,
|
|
221473
222386
|
task_id: item.taskId,
|
|
221474
222387
|
task_title: item.taskTitle,
|
|
@@ -221524,6 +222437,10 @@ function createProjectsDomain(opts) {
|
|
|
221524
222437
|
const handler = opts.onProjectNameChangedFor(companyId);
|
|
221525
222438
|
if (handler) svc.setOnProjectNameChanged(handler);
|
|
221526
222439
|
}
|
|
222440
|
+
if (opts.onProjectDeletedFor) {
|
|
222441
|
+
const handler = opts.onProjectDeletedFor(companyId);
|
|
222442
|
+
if (handler) svc.setOnProjectDeleted(handler);
|
|
222443
|
+
}
|
|
221527
222444
|
return svc;
|
|
221528
222445
|
};
|
|
221529
222446
|
const service = build(void 0, void 0);
|
|
@@ -222349,8 +223266,8 @@ var init_offboarding = __esm({
|
|
|
222349
223266
|
return { ownedAgents, inFlight };
|
|
222350
223267
|
}
|
|
222351
223268
|
/**
|
|
222352
|
-
* 现在**能不能直接删**这个成员。DELETE
|
|
222353
|
-
*
|
|
223269
|
+
* 现在**能不能直接删**这个成员。DELETE 与 commit 共用它,不会两处各判一套。
|
|
223270
|
+
* 交接删掉之后这里只剩两条:不是成员(NOT_FOUND)、公司最后一位 owner(LAST_OWNER)。
|
|
222354
223271
|
*/
|
|
222355
223272
|
async blockersForRemoval(companyId, accountId) {
|
|
222356
223273
|
const blockers = [];
|
|
@@ -222533,6 +223450,15 @@ function companiesDomain(opts) {
|
|
|
222533
223450
|
if (!agentCredentials) throw new ApiError(501, "NOT_IMPLEMENTED", "\u672C\u90E8\u7F72\u672A\u63A5\u5165 agent \u51ED\u8BC1");
|
|
222534
223451
|
return agentCredentials;
|
|
222535
223452
|
};
|
|
223453
|
+
const syncActorNameToDisplayName = async (member, by) => {
|
|
223454
|
+
if (!resolveActorsCtx) return;
|
|
223455
|
+
const name = member.displayName?.trim();
|
|
223456
|
+
if (!name) return;
|
|
223457
|
+
const { service: actorsSvc } = await resolveActorsCtx(member.companyId);
|
|
223458
|
+
const existing = await actorsSvc.getActor(member.actorId);
|
|
223459
|
+
if (!existing || existing.name === name) return;
|
|
223460
|
+
await actorsSvc.upsertActor({ ...existing, name }, by);
|
|
223461
|
+
};
|
|
222536
223462
|
const requireIfMatch = (req) => {
|
|
222537
223463
|
const raw = req.raw.headers["if-match"];
|
|
222538
223464
|
const text5 = (typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : void 0)?.replace(/^"|"$/g, "");
|
|
@@ -222648,6 +223574,7 @@ function companiesDomain(opts) {
|
|
|
222648
223574
|
throw new ApiError(400, "BAD_REQUEST", "displayName \u987B\u4E3A\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
222649
223575
|
}
|
|
222650
223576
|
member = await service.setMemberDisplayName(cid, aid, b2.displayName).catch(mapErr);
|
|
223577
|
+
await syncActorNameToDisplayName(member, req.auth.actor);
|
|
222651
223578
|
}
|
|
222652
223579
|
return { status: 200, body: member };
|
|
222653
223580
|
});
|
|
@@ -224606,6 +225533,146 @@ var init_node_store = __esm({
|
|
|
224606
225533
|
}
|
|
224607
225534
|
});
|
|
224608
225535
|
|
|
225536
|
+
// ../server/src/domains/collab/review-activity.ts
|
|
225537
|
+
function reviewCardId(targetWorkId, reviewerActorId) {
|
|
225538
|
+
return `${REVIEW_CARD_PREFIX}${encodeURIComponent(targetWorkId)}:${encodeURIComponent(reviewerActorId)}`;
|
|
225539
|
+
}
|
|
225540
|
+
function activityReviews(snap, events) {
|
|
225541
|
+
const rows = /* @__PURE__ */ new Map();
|
|
225542
|
+
for (const r of snap.reviews) rows.set(r.id, { ...r });
|
|
225543
|
+
const authoritative = new Set(snap.reviews.filter((r) => r.status).map((r) => r.id));
|
|
225544
|
+
for (const rec of [...events].sort((a, b2) => a.seq - b2.seq)) {
|
|
225545
|
+
const e = rec.event;
|
|
225546
|
+
if (e.kind === "review.create" && !rows.has(e.reviewId)) {
|
|
225547
|
+
rows.set(e.reviewId, {
|
|
225548
|
+
id: e.reviewId,
|
|
225549
|
+
workorderId: snap.workorder.id,
|
|
225550
|
+
nodeId: e.nodeId,
|
|
225551
|
+
targetWorkId: e.targetWorkId,
|
|
225552
|
+
reviewerActorId: e.reviewerActorId,
|
|
225553
|
+
reviewGroup: e.reviewGroup ?? e.reviewerActorId,
|
|
225554
|
+
createdAt: rec.createdAt,
|
|
225555
|
+
startedAt: null,
|
|
225556
|
+
endedAt: null,
|
|
225557
|
+
cancelledAt: null,
|
|
225558
|
+
verdict: null,
|
|
225559
|
+
note: null,
|
|
225560
|
+
handActorId: null,
|
|
225561
|
+
decidedAt: null,
|
|
225562
|
+
sessionRef: null
|
|
225563
|
+
});
|
|
225564
|
+
}
|
|
225565
|
+
if (e.kind === "review.create" && !authoritative.has(e.reviewId)) {
|
|
225566
|
+
const r = rows.get(e.reviewId);
|
|
225567
|
+
Object.assign(r, {
|
|
225568
|
+
nodeId: e.nodeId,
|
|
225569
|
+
targetWorkId: e.targetWorkId,
|
|
225570
|
+
reviewerActorId: e.reviewerActorId,
|
|
225571
|
+
reviewGroup: e.reviewGroup ?? r.reviewGroup ?? e.reviewerActorId,
|
|
225572
|
+
createdAt: r.createdAt ?? rec.createdAt
|
|
225573
|
+
});
|
|
225574
|
+
}
|
|
225575
|
+
if (!e.kind.startsWith("review.")) continue;
|
|
225576
|
+
const candidates = "reviewId" in e && e.reviewId ? [rows.get(e.reviewId)] : e.kind === "review.response" ? [...rows.values()].filter((r) => r.targetWorkId === e.targetWorkId && r.reviewerActorId === e.reviewerActorId && (!e.reviewGroup || r.reviewGroup === e.reviewGroup)) : [];
|
|
225577
|
+
const matches = e.kind === "review.response" && !e.reviewId ? latestActivityReviews(candidates.filter((r) => !!r && r.createdAt <= rec.createdAt), events.filter((r) => r.seq <= rec.seq)) : candidates;
|
|
225578
|
+
for (const r of matches) {
|
|
225579
|
+
if (!r || authoritative.has(r.id)) continue;
|
|
225580
|
+
if (e.kind === "review.started") r.startedAt = rec.createdAt;
|
|
225581
|
+
if (e.kind === "review.timeout") {
|
|
225582
|
+
r.endedAt = rec.createdAt;
|
|
225583
|
+
}
|
|
225584
|
+
if (e.kind === "review.kill") r.cancelledAt = rec.createdAt;
|
|
225585
|
+
if (e.kind === "review.response") {
|
|
225586
|
+
r.verdict = e.verdict;
|
|
225587
|
+
r.note = e.note ?? null;
|
|
225588
|
+
r.decidedAt = rec.createdAt;
|
|
225589
|
+
r.endedAt = rec.createdAt;
|
|
225590
|
+
r.handActorId = e.handActorId ?? null;
|
|
225591
|
+
}
|
|
225592
|
+
}
|
|
225593
|
+
}
|
|
225594
|
+
const createdSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
|
|
225595
|
+
return [...rows.values()].filter((r) => r.nodeId && r.targetWorkId && r.reviewerActorId && r.createdAt).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (createdSeq.get(a.id) ?? 0) - (createdSeq.get(b2.id) ?? 0) || a.id.localeCompare(b2.id));
|
|
225596
|
+
}
|
|
225597
|
+
function reviewCardTarget(cardId, reviews) {
|
|
225598
|
+
if (cardId.startsWith(REVIEW_CARD_PREFIX)) {
|
|
225599
|
+
const encoded = cardId.slice(REVIEW_CARD_PREFIX.length);
|
|
225600
|
+
const separator = encoded.indexOf(":");
|
|
225601
|
+
if (separator < 1 || separator === encoded.length - 1) return void 0;
|
|
225602
|
+
try {
|
|
225603
|
+
return {
|
|
225604
|
+
targetWorkId: decodeURIComponent(encoded.slice(0, separator)),
|
|
225605
|
+
reviewerActorId: decodeURIComponent(encoded.slice(separator + 1))
|
|
225606
|
+
};
|
|
225607
|
+
} catch {
|
|
225608
|
+
return void 0;
|
|
225609
|
+
}
|
|
225610
|
+
}
|
|
225611
|
+
if (cardId.startsWith("review:")) {
|
|
225612
|
+
const review = reviews.find((r) => r.id === cardId.slice(7));
|
|
225613
|
+
if (review) return { targetWorkId: review.targetWorkId, reviewerActorId: review.reviewerActorId };
|
|
225614
|
+
}
|
|
225615
|
+
return void 0;
|
|
225616
|
+
}
|
|
225617
|
+
function latestActivityReviews(reviews, events) {
|
|
225618
|
+
const createSeq = new Map(events.flatMap((r) => r.event.kind === "review.create" ? [[r.event.reviewId, r.seq]] : []));
|
|
225619
|
+
const rows = [...reviews].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (createSeq.get(a.id) ?? 0) - (createSeq.get(b2.id) ?? 0) || a.id.localeCompare(b2.id));
|
|
225620
|
+
const latest = /* @__PURE__ */ new Map();
|
|
225621
|
+
for (const r of rows) latest.set(JSON.stringify([r.reviewGroup, r.reviewerActorId]), r);
|
|
225622
|
+
return [...latest.values()];
|
|
225623
|
+
}
|
|
225624
|
+
function reviewLabel(r) {
|
|
225625
|
+
const state = reviewState(r);
|
|
225626
|
+
if (state === "accept") return "\u5BA1\u6838\u901A\u8FC7";
|
|
225627
|
+
if (state === "reject") return "\u5BA1\u6838\u4E0D\u901A\u8FC7";
|
|
225628
|
+
if (state === "dead") return "\u5BA1\u6838\u5DF2\u505C\u6B62";
|
|
225629
|
+
if (state === "failed") return "\u5BA1\u6838\u8FD0\u884C\u5931\u8D25";
|
|
225630
|
+
if (state === "retry") return "\u6B63\u5728\u91CD\u8BD5\u5BA1\u6838";
|
|
225631
|
+
return r.reviewerActorId.startsWith("actor:human:") ? "\u5F85\u5BA1\u6838" : "\u6B63\u5728\u5BA1\u6838";
|
|
225632
|
+
}
|
|
225633
|
+
function reviewSummary(snap, reviews, events) {
|
|
225634
|
+
const first = reviews[0];
|
|
225635
|
+
const work = snap.works.find((w2) => w2.id === first.targetWorkId);
|
|
225636
|
+
const node2 = snap.nodes.find((n) => n.id === first.nodeId);
|
|
225637
|
+
const name = node2?.title ?? first.nodeId;
|
|
225638
|
+
const latest = latestActivityReviews(reviews, events);
|
|
225639
|
+
const requirements = snap.requirements.filter((r) => r.nodeId === first.nodeId && r.reviewerActorId === first.reviewerActorId);
|
|
225640
|
+
const judgement = judgeWork({ requirements: requirements.length ? requirements : reviews, reviews });
|
|
225641
|
+
const pending = latest.filter((r) => !r.cancelledAt && !r.verdict && reviewState(r) === "running");
|
|
225642
|
+
if (work?.acceptanceState === "accepted" || work?.acceptedAt || work?.acceptanceState !== "rejected" && judgement === "passed") {
|
|
225643
|
+
return { phase: "done", status: `\u300A${name}\u300B\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending: [] };
|
|
225644
|
+
}
|
|
225645
|
+
if (work?.acceptanceState === "rejected" || judgement === "rejected") {
|
|
225646
|
+
return { phase: "done", status: "\u5BA1\u6838\u4E0D\u901A\u8FC7\u3002", latest, pending: [] };
|
|
225647
|
+
}
|
|
225648
|
+
const endedWork = !!(work?.cancelledAt || work?.deadAt);
|
|
225649
|
+
if (endedWork && latest.every((r) => !!r.cancelledAt && reviewState(r) !== "retry")) {
|
|
225650
|
+
return { phase: "done", status: `\u300A${name}\u300B\u7684\u672C\u6B21\u5BA1\u6838\u5DF2\u53D6\u6D88\u3002`, latest, pending: [] };
|
|
225651
|
+
}
|
|
225652
|
+
const interrupted = latest.some((r) => ["failed", "dead"].includes(reviewState(r)));
|
|
225653
|
+
if (interrupted) return { phase: "stuck", status: latest.length === 1 ? reviewState(latest[0]) === "dead" ? events.some((r) => r.event.kind === "review.kill" && r.event.reviewId === latest[0].id) ? `\u300A${name}\u300B\u7684\u8BC4\u5BA1\u8FDE\u7EED\u5931\u8D25\uFF0C\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u5DF2\u505C\u6B62\u3002` : `\u300A${name}\u300B\u7684\u5BA1\u6838\u672A\u5728\u65F6\u9650\u5185\u5B8C\u6210\u3002` : `\u300A${name}\u300B\u7684\u90E8\u5206\u5BA1\u6838\u6267\u884C\u4E2D\u65AD\uFF0C\u5C1A\u672A\u5B8C\u6210\u5168\u90E8\u5BA1\u6838\u3002`, latest, pending };
|
|
225654
|
+
if (latest.some((r) => r.reviewerActorId.startsWith("actor:agent:") && ["running", "retry"].includes(reviewState(r)))) {
|
|
225655
|
+
return { phase: "running", status: `\u6B63\u5728\u5BA1\u6838 \u300A${name}\u300B\u3002`, latest, pending };
|
|
225656
|
+
}
|
|
225657
|
+
return { phase: "not_started", status: `\u8BF7\u786E\u8BA4\u300A${name}\u300B\u662F\u5426\u5BA1\u6838\u901A\u8FC7\u3002`, latest, pending };
|
|
225658
|
+
}
|
|
225659
|
+
var REVIEW_CARD_PREFIX, REVIEW_TRACE_EVENT_KINDS;
|
|
225660
|
+
var init_review_activity = __esm({
|
|
225661
|
+
"../server/src/domains/collab/review-activity.ts"() {
|
|
225662
|
+
"use strict";
|
|
225663
|
+
init_src4();
|
|
225664
|
+
REVIEW_CARD_PREFIX = "review-work:";
|
|
225665
|
+
REVIEW_TRACE_EVENT_KINDS = [
|
|
225666
|
+
"review.create",
|
|
225667
|
+
"review.started",
|
|
225668
|
+
"review.response",
|
|
225669
|
+
"review.timeout",
|
|
225670
|
+
"review.kill",
|
|
225671
|
+
"plan.node_retry"
|
|
225672
|
+
];
|
|
225673
|
+
}
|
|
225674
|
+
});
|
|
225675
|
+
|
|
224609
225676
|
// ../server/src/domains/collab/workorder-manager.ts
|
|
224610
225677
|
function resolveWorkorderManager(snap) {
|
|
224611
225678
|
const nodes = snap.nodes;
|
|
@@ -224644,8 +225711,29 @@ function buildWorkorderActivity(input) {
|
|
|
224644
225711
|
if (!snap) return { workorderId, cards: [], truncated: false };
|
|
224645
225712
|
const nodeById = new Map(snap.nodes.map((n) => [n.id, n]));
|
|
224646
225713
|
const workById = new Map(snap.works.map((w2) => [w2.id, w2]));
|
|
224647
|
-
const reviewById = new Map(snap.reviews.map((r) => [r.id, r]));
|
|
224648
225714
|
const issueById = new Map(snap.issues.map((i) => [i.id, i]));
|
|
225715
|
+
const activeWorks = /* @__PURE__ */ new Set();
|
|
225716
|
+
const gapOrigins = /* @__PURE__ */ new Map();
|
|
225717
|
+
const knownStarts = new Set(events.flatMap((r) => r.event.kind === "work.create" || r.event.kind === "work.started" ? [r.event.workId] : []));
|
|
225718
|
+
for (const rec of events) {
|
|
225719
|
+
const event = rec.event;
|
|
225720
|
+
if (event.kind === "work.create" || event.kind === "work.started") activeWorks.add(event.workId);
|
|
225721
|
+
if (event.kind === "work.response" || event.kind === "work.timeout" || event.kind === "work.kill") activeWorks.delete(event.workId);
|
|
225722
|
+
if (event.kind !== "issue.create" || event.issueKind !== "gap") continue;
|
|
225723
|
+
const nodeId = event.raisedByNodeId ?? event.aboutNodeId;
|
|
225724
|
+
const matches = (id) => {
|
|
225725
|
+
const work = workById.get(id);
|
|
225726
|
+
return work?.nodeId === nodeId && work.assigneeActorId === event.authorActorId;
|
|
225727
|
+
};
|
|
225728
|
+
if (event.aboutWorkId && matches(event.aboutWorkId)) {
|
|
225729
|
+
gapOrigins.set(event.issueId, [event.aboutWorkId]);
|
|
225730
|
+
} else if (!event.aboutWorkId) {
|
|
225731
|
+
const candidates = [...activeWorks].filter(matches);
|
|
225732
|
+
const at = Date.parse(rec.createdAt);
|
|
225733
|
+
const unobserved = snap.works.some((work) => !knownStarts.has(work.id) && matches(work.id) && Date.parse(work.createdAt) <= at && (!work.endedAt || Date.parse(work.endedAt) >= at) && (!work.deadAt || Date.parse(work.deadAt) >= at) && (!work.cancelledAt || Date.parse(work.cancelledAt) >= at));
|
|
225734
|
+
if (candidates.length === 1 && !unobserved) gapOrigins.set(event.issueId, candidates);
|
|
225735
|
+
}
|
|
225736
|
+
}
|
|
224649
225737
|
const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
|
|
224650
225738
|
const rootBriefNodeIds = new Set(
|
|
224651
225739
|
snap.nodes.filter((n) => n.type === "brief" && !nodesWithInEdges.has(n.id)).map((n) => n.id)
|
|
@@ -224696,8 +225784,9 @@ function buildWorkorderActivity(input) {
|
|
|
224696
225784
|
const groupIndexOf = /* @__PURE__ */ new Map();
|
|
224697
225785
|
const cardOfWork = /* @__PURE__ */ new Map();
|
|
224698
225786
|
const groupKey = (nodeId) => `${nodeId}#${groupIndexOf.get(nodeId) ?? 0}`;
|
|
224699
|
-
const reviewCards = /* @__PURE__ */ new Map();
|
|
224700
225787
|
const issueCards = /* @__PURE__ */ new Map();
|
|
225788
|
+
const workOfIssue = /* @__PURE__ */ new Map();
|
|
225789
|
+
const plainCommentIssueIds = /* @__PURE__ */ new Set();
|
|
224701
225790
|
const acceptCards = /* @__PURE__ */ new Map();
|
|
224702
225791
|
const standalone = [];
|
|
224703
225792
|
const touch = (d, rec) => {
|
|
@@ -224755,10 +225844,22 @@ function buildWorkorderActivity(input) {
|
|
|
224755
225844
|
const executorId = w2?.assigneeActorId ?? rec.actorId;
|
|
224756
225845
|
if (isPendingActor(executorId)) break;
|
|
224757
225846
|
const status = reasonKinds.has("rework") ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
|
|
225847
|
+
const drivingIssueId = str4(reasons.find((r) => r.kind === "issue")?.issueId);
|
|
225848
|
+
if (drivingIssueId) {
|
|
225849
|
+
workOfIssue.set(drivingIssueId, workId);
|
|
225850
|
+
const ic = plainCommentIssueIds.has(drivingIssueId) ? issueCards.get(drivingIssueId) : void 0;
|
|
225851
|
+
const commentAuthor = issueById.get(drivingIssueId)?.authorActorId;
|
|
225852
|
+
if (ic && commentAuthor) {
|
|
225853
|
+
touch(ic, rec);
|
|
225854
|
+
ic.executorId = executorId;
|
|
225855
|
+
ic.status = ACTIVITY_COPY.commentHandling(nameOf(executorId), nameOf(commentAuthor));
|
|
225856
|
+
}
|
|
225857
|
+
}
|
|
224758
225858
|
const reworkReason = str4(reasons.find((r) => r.kind === "rework")?.reason);
|
|
224759
225859
|
const key = groupKey(nodeId);
|
|
224760
225860
|
const existingGroup = groupCards.get(key);
|
|
224761
225861
|
if (existingGroup) {
|
|
225862
|
+
if (!existingGroup.traceWorkIds.includes(workId)) existingGroup.traceWorkIds.push(workId);
|
|
224762
225863
|
touch(existingGroup, rec);
|
|
224763
225864
|
existingGroup.executorId = executorId;
|
|
224764
225865
|
existingGroup.phase = "running";
|
|
@@ -224771,6 +225872,7 @@ function buildWorkorderActivity(input) {
|
|
|
224771
225872
|
}
|
|
224772
225873
|
const fresh = {
|
|
224773
225874
|
id: `work:${workId}`,
|
|
225875
|
+
traceWorkIds: [workId],
|
|
224774
225876
|
seq: rec.seq,
|
|
224775
225877
|
at: rec.createdAt,
|
|
224776
225878
|
updatedAt: rec.createdAt,
|
|
@@ -224813,8 +225915,9 @@ function buildWorkorderActivity(input) {
|
|
|
224813
225915
|
}
|
|
224814
225916
|
break;
|
|
224815
225917
|
}
|
|
225918
|
+
const engineVerdict = workById.get(workId)?.status;
|
|
224816
225919
|
touch(d, rec);
|
|
224817
|
-
if (ev.outcome === "failed") {
|
|
225920
|
+
if (ev.outcome === "failed" && engineVerdict !== "success") {
|
|
224818
225921
|
d.phase = "stuck";
|
|
224819
225922
|
d.stuckReason = "failed";
|
|
224820
225923
|
d.status = ACTIVITY_COPY.failed(nameOf(d.executorId));
|
|
@@ -224902,72 +226005,6 @@ function buildWorkorderActivity(input) {
|
|
|
224902
226005
|
}
|
|
224903
226006
|
break;
|
|
224904
226007
|
}
|
|
224905
|
-
/* ── 审核(review)──────────────────────────────────────────── */
|
|
224906
|
-
case "review.create": {
|
|
224907
|
-
const reviewId = String(ev.reviewId ?? "");
|
|
224908
|
-
const nodeId = String(ev.nodeId ?? "");
|
|
224909
|
-
const targetWorkId = String(ev.targetWorkId ?? "");
|
|
224910
|
-
const reviewerId = str4(ev.reviewerActorId) ?? rec.actorId;
|
|
224911
|
-
const isAgent2 = ev.reviewerIsAgent === true;
|
|
224912
|
-
const name = nodeName(nodeId);
|
|
224913
|
-
reviewCards.set(reviewId, {
|
|
224914
|
-
id: `review:${reviewId}`,
|
|
224915
|
-
seq: rec.seq,
|
|
224916
|
-
at: rec.createdAt,
|
|
224917
|
-
updatedAt: rec.createdAt,
|
|
224918
|
-
nodeId,
|
|
224919
|
-
executorId: reviewerId,
|
|
224920
|
-
...rec.handActorId ? { handActorId: rec.handActorId } : {},
|
|
224921
|
-
// agent 评审是「机器在跑」= 进行中;人审是待办 = 待开始(人没有「开始看了」这个事件)。
|
|
224922
|
-
phase: isAgent2 ? "running" : "not_started",
|
|
224923
|
-
status: isAgent2 ? ACTIVITY_COPY.reviewing(name) : ACTIVITY_COPY.reviewPending(name),
|
|
224924
|
-
/* 挂上被审那一次交付的**全部产物**(发起人 2026-09-08 定):要人点「通过/不通过」,
|
|
224925
|
-
就得让他在同一张卡上看到到底在审什么。此前这里恒空,产物只挂在验收卡上——
|
|
224926
|
-
而验收卡与审核卡是同一件事的两张(见下方 `acceptCards` 那段的跳过判据)。 */
|
|
224927
|
-
artifacts: artifactsByWork.get(targetWorkId) ?? [],
|
|
224928
|
-
// 人审才出底栏按钮;agent 审无按钮(原型同)。
|
|
224929
|
-
actions: isAgent2 ? [] : [
|
|
224930
|
-
{ kind: "review", label: BUTTON.approve, target: nodeId, verdict: "approve" },
|
|
224931
|
-
{ kind: "review", label: BUTTON.reject, target: nodeId, verdict: "request_changes" }
|
|
224932
|
-
]
|
|
224933
|
-
});
|
|
224934
|
-
break;
|
|
224935
|
-
}
|
|
224936
|
-
case "review.started": {
|
|
224937
|
-
const d = reviewCards.get(String(ev.reviewId ?? ""));
|
|
224938
|
-
if (d) touch(d, rec);
|
|
224939
|
-
break;
|
|
224940
|
-
}
|
|
224941
|
-
case "review.response": {
|
|
224942
|
-
const reviewId = str4(ev.reviewId);
|
|
224943
|
-
const targetWorkId = String(ev.targetWorkId ?? "");
|
|
224944
|
-
const d = reviewId ? reviewCards.get(reviewId) : [...reviewCards.values()].find((c) => reviewById.get(c.id.slice("review:".length))?.targetWorkId === targetWorkId);
|
|
224945
|
-
if (!d) break;
|
|
224946
|
-
touch(d, rec);
|
|
224947
|
-
d.phase = "done";
|
|
224948
|
-
d.executorId = str4(ev.reviewerActorId) ?? d.executorId;
|
|
224949
|
-
d.actions = [];
|
|
224950
|
-
const nodeId = d.nodeId;
|
|
224951
|
-
const name = nodeName(nodeId);
|
|
224952
|
-
if (ev.verdict === "approve") {
|
|
224953
|
-
d.status = ACTIVITY_COPY.reviewApproved(name);
|
|
224954
|
-
} else {
|
|
224955
|
-
d.status = ACTIVITY_COPY.reviewRejected;
|
|
224956
|
-
const note = str4(ev.note);
|
|
224957
|
-
if (note) d.detail = ACTIVITY_COPY.reviewRejectedDetail(note);
|
|
224958
|
-
}
|
|
224959
|
-
break;
|
|
224960
|
-
}
|
|
224961
|
-
case "review.timeout":
|
|
224962
|
-
case "review.kill": {
|
|
224963
|
-
const d = reviewCards.get(String(ev.reviewId ?? ""));
|
|
224964
|
-
if (!d) break;
|
|
224965
|
-
touch(d, rec);
|
|
224966
|
-
d.phase = "stuck";
|
|
224967
|
-
d.status = ev.kind === "review.kill" ? ACTIVITY_COPY.reviewKilled(nodeName(d.nodeId)) : ACTIVITY_COPY.reviewTimedOut(nodeName(d.nodeId));
|
|
224968
|
-
d.actions = [];
|
|
224969
|
-
break;
|
|
224970
|
-
}
|
|
224971
226008
|
/* ── 沟通与异常(issue)────────────────────────────────────── */
|
|
224972
226009
|
case "issue.create": {
|
|
224973
226010
|
const issueId = String(ev.issueId ?? "");
|
|
@@ -225046,6 +226083,7 @@ function buildWorkorderActivity(input) {
|
|
|
225046
226083
|
});
|
|
225047
226084
|
break;
|
|
225048
226085
|
}
|
|
226086
|
+
if (!isGap && !isEscalation && !isBackpressure) plainCommentIssueIds.add(issueId);
|
|
225049
226087
|
const nodeAssignee = nodeId ? nodeById.get(nodeId)?.assigneeActorId : void 0;
|
|
225050
226088
|
const commentCounterpart = nodeAssignee ?? ownerId;
|
|
225051
226089
|
const commentTargetId = commentCounterpart && commentCounterpart !== authorId ? commentCounterpart : void 0;
|
|
@@ -225110,31 +226148,24 @@ function buildWorkorderActivity(input) {
|
|
|
225110
226148
|
if (issue2?.kind === "escalation") {
|
|
225111
226149
|
const linkedGap = str4(issue2.gapId);
|
|
225112
226150
|
const stuckActor = (linkedGap ? issueById.get(linkedGap)?.authorActorId : void 0) ?? issue2.authorActorId;
|
|
226151
|
+
touch(d, rec);
|
|
226152
|
+
d.phase = "done";
|
|
226153
|
+
d.executorId = resolverId;
|
|
226154
|
+
d.handActorId = rec.handActorId ?? void 0;
|
|
226155
|
+
d.status = ACTIVITY_COPY.handledEscalation(nameOf(stuckActor));
|
|
225113
226156
|
d.actions = [];
|
|
225114
226157
|
const raisedCard = issueCards.get(`${issueId}:raised`);
|
|
225115
226158
|
if (raisedCard) raisedCard.actions = [];
|
|
225116
|
-
standalone.push({
|
|
225117
|
-
id: `issue:${issueId}:resolved`,
|
|
225118
|
-
seq: rec.seq,
|
|
225119
|
-
at: rec.createdAt,
|
|
225120
|
-
updatedAt: rec.createdAt,
|
|
225121
|
-
...d.nodeId ? { nodeId: d.nodeId } : {},
|
|
225122
|
-
executorId: resolverId,
|
|
225123
|
-
...rec.handActorId ? { handActorId: rec.handActorId } : {},
|
|
225124
|
-
phase: "done",
|
|
225125
|
-
/* 收口正文不上卡(与开口那一支同一条口径):`ws:wo-635d7add` 的收口 note 是一整段
|
|
225126
|
-
处置说明,铺回展开区又是一屏字。 */
|
|
225127
|
-
status: ACTIVITY_COPY.handledEscalation(nameOf(stuckActor)),
|
|
225128
|
-
artifacts: [],
|
|
225129
|
-
actions: []
|
|
225130
|
-
});
|
|
225131
226159
|
break;
|
|
225132
226160
|
}
|
|
225133
226161
|
touch(d, rec);
|
|
225134
226162
|
d.phase = "done";
|
|
225135
226163
|
d.actions = [];
|
|
225136
226164
|
d.executorId = resolverId;
|
|
225137
|
-
|
|
226165
|
+
const drivenWorkId = workOfIssue.get(issueId);
|
|
226166
|
+
const producedNewVersion = !!(drivenWorkId && workById.get(drivenWorkId)?.conclusion);
|
|
226167
|
+
const commentAuthorId = issue2?.authorActorId;
|
|
226168
|
+
d.status = producedNewVersion && commentAuthorId ? ACTIVITY_COPY.commentAddressed(nameOf(resolverId), nameOf(commentAuthorId), nodeName(d.nodeId)) : ACTIVITY_COPY.commentResolved(nameOf(resolverId), nodeName(d.nodeId));
|
|
225138
226169
|
break;
|
|
225139
226170
|
}
|
|
225140
226171
|
case "plan.node_retry": {
|
|
@@ -225151,9 +226182,7 @@ function buildWorkorderActivity(input) {
|
|
|
225151
226182
|
break;
|
|
225152
226183
|
}
|
|
225153
226184
|
}
|
|
225154
|
-
const reviewedWorkIds = new Set(
|
|
225155
|
-
[...reviewCards.keys()].map((rid) => reviewById.get(rid)?.targetWorkId).filter(Boolean)
|
|
225156
|
-
);
|
|
226185
|
+
const reviewedWorkIds = new Set([...snap.reviews, ...activityReviews(snap, events)].map((r) => r.targetWorkId));
|
|
225157
226186
|
for (const w2 of snap.works) {
|
|
225158
226187
|
if (acceptCards.has(w2.id)) continue;
|
|
225159
226188
|
if (reviewedWorkIds.has(w2.id)) continue;
|
|
@@ -225177,23 +226206,104 @@ function buildWorkorderActivity(input) {
|
|
|
225177
226206
|
status: ACTIVITY_COPY.acceptPending,
|
|
225178
226207
|
artifacts: artifactsByWork.get(w2.id) ?? [],
|
|
225179
226208
|
actions: [
|
|
225180
|
-
{ kind: "accept", label: BUTTON.acceptPass, target: node2.id, verdict: "approve" },
|
|
225181
|
-
{ kind: "accept", label: BUTTON.acceptFail, target: node2.id, verdict: "request_changes" }
|
|
226209
|
+
{ kind: "accept", label: BUTTON.acceptPass, target: node2.id, revisionId: w2.id, verdict: "approve" },
|
|
226210
|
+
{ kind: "accept", label: BUTTON.acceptFail, target: node2.id, revisionId: w2.id, verdict: "request_changes" }
|
|
225182
226211
|
]
|
|
225183
226212
|
});
|
|
225184
226213
|
}
|
|
226214
|
+
const visibleReviews = /* @__PURE__ */ new Map();
|
|
226215
|
+
const allReviews = activityReviews(snap, events);
|
|
226216
|
+
const reviewWorks = /* @__PURE__ */ new Map();
|
|
226217
|
+
const reviewCards = /* @__PURE__ */ new Map();
|
|
226218
|
+
for (const r of allReviews) {
|
|
226219
|
+
const rows = reviewWorks.get(r.targetWorkId) ?? [];
|
|
226220
|
+
rows.push(r);
|
|
226221
|
+
reviewWorks.set(r.targetWorkId, rows);
|
|
226222
|
+
const cardId = reviewCardId(r.targetWorkId, r.reviewerActorId);
|
|
226223
|
+
const group = reviewCards.get(cardId) ?? { workId: r.targetWorkId, reviewerId: r.reviewerActorId, reviews: [] };
|
|
226224
|
+
group.reviews.push(r);
|
|
226225
|
+
reviewCards.set(cardId, group);
|
|
226226
|
+
}
|
|
226227
|
+
for (const [cardId, { workId, reviewerId, reviews }] of reviewCards) {
|
|
226228
|
+
const summary = reviewSummary(snap, reviews, events);
|
|
226229
|
+
const first = reviews[0];
|
|
226230
|
+
const ids2 = new Set(reviews.map((r) => r.id));
|
|
226231
|
+
const created = events.filter((r) => r.event.kind === "review.create" && ids2.has(r.event.reviewId));
|
|
226232
|
+
const dates = summary.latest.flatMap((r) => [r.createdAt, r.startedAt, r.endedAt, r.decidedAt, r.cancelledAt, r.retryAt].filter((v2) => !!v2));
|
|
226233
|
+
const pending = summary.pending.filter((r) => r.reviewerActorId.startsWith("actor:human:") && (!input.viewerActorId || r.reviewerActorId === input.viewerActorId));
|
|
226234
|
+
visibleReviews.set(cardId, {
|
|
226235
|
+
id: cardId,
|
|
226236
|
+
reviewWorkId: workId,
|
|
226237
|
+
reviewerId,
|
|
226238
|
+
reviewerIds: [reviewerId],
|
|
226239
|
+
traceAvailable: true,
|
|
226240
|
+
seq: created.length ? Math.min(...created.map((r) => r.seq)) : 0,
|
|
226241
|
+
at: first.createdAt,
|
|
226242
|
+
updatedAt: dates.sort().at(-1) ?? first.createdAt,
|
|
226243
|
+
nodeId: first.nodeId,
|
|
226244
|
+
executorId: reviewerId,
|
|
226245
|
+
phase: summary.phase,
|
|
226246
|
+
status: summary.status,
|
|
226247
|
+
detail: summary.latest.length === 1 ? summary.latest[0].note ? `${summary.latest[0].verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${summary.latest[0].note}` : void 0 : summary.latest.map((r) => `${nameOf(r.reviewerActorId)}\uFF08\u8BC4\u5BA1\u7EC4 ${[...new Set(summary.latest.map((r2) => r2.reviewGroup))].indexOf(r.reviewGroup) + 1}\uFF09\uFF1A${reviewLabel(r)}${r.note ? `
|
|
226248
|
+
${r.verdict === "request_changes" ? "\u5BA1\u6838\u4E0D\u901A\u8FC7" : "\u5BA1\u6838\u610F\u89C1"}\uFF1A${r.note}` : ""}`).join("\n\n"),
|
|
226249
|
+
artifacts: artifactsByWork.get(workId) ?? [],
|
|
226250
|
+
actions: pending.length ? [
|
|
226251
|
+
{ kind: "review", label: BUTTON.approve, target: first.nodeId, revisionId: workId, verdict: "approve" },
|
|
226252
|
+
{ kind: "review", label: BUTTON.reject, target: first.nodeId, revisionId: workId, verdict: "request_changes" }
|
|
226253
|
+
] : []
|
|
226254
|
+
});
|
|
226255
|
+
}
|
|
225185
226256
|
const drafts = [
|
|
225186
226257
|
...standalone,
|
|
225187
226258
|
...groupCards.values(),
|
|
225188
|
-
...
|
|
226259
|
+
...visibleReviews.values(),
|
|
225189
226260
|
...issueCards.values(),
|
|
225190
226261
|
...acceptCards.values()
|
|
225191
226262
|
].sort((a, b2) => a.updatedAt.localeCompare(b2.updatedAt) || a.seq - b2.seq || a.id.localeCompare(b2.id));
|
|
225192
|
-
const
|
|
225193
|
-
const
|
|
225194
|
-
|
|
225195
|
-
|
|
225196
|
-
if (
|
|
226263
|
+
const issueWorks = /* @__PURE__ */ new Map();
|
|
226264
|
+
const reasonIds = /* @__PURE__ */ new Map();
|
|
226265
|
+
for (const rec of input.events) {
|
|
226266
|
+
const ev = rec.event;
|
|
226267
|
+
if (ev.kind === "work.create") reasonIds.set(
|
|
226268
|
+
ev.workId,
|
|
226269
|
+
ev.reasons.filter((r) => r.kind === "issue").map((r) => r.issueId)
|
|
226270
|
+
);
|
|
226271
|
+
}
|
|
226272
|
+
for (const w2 of [...snap?.works ?? []].sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id))) {
|
|
226273
|
+
const replyIssueId = replyIssueIdOf(w2);
|
|
226274
|
+
for (const id of new Set(w2.frozenIssueIds ?? reasonIds.get(w2.id) ?? (replyIssueId ? [replyIssueId] : []))) {
|
|
226275
|
+
const issue2 = issueById.get(id);
|
|
226276
|
+
if (issue2?.kind !== "comment" && issue2?.kind !== "change_request") continue;
|
|
226277
|
+
const ids2 = issueWorks.get(id) ?? [];
|
|
226278
|
+
ids2.push(w2.id);
|
|
226279
|
+
issueWorks.set(id, ids2);
|
|
226280
|
+
}
|
|
226281
|
+
}
|
|
226282
|
+
for (const d of drafts) {
|
|
226283
|
+
if (!d.id.startsWith("issue:")) continue;
|
|
226284
|
+
const issueId = d.id.slice("issue:".length);
|
|
226285
|
+
if (issueById.get(issueId)?.kind === "gap") {
|
|
226286
|
+
d.traceAvailable = true;
|
|
226287
|
+
d.traceWorkIds = gapOrigins.get(issueId);
|
|
226288
|
+
} else {
|
|
226289
|
+
d.traceWorkIds = issueWorks.get(issueId);
|
|
226290
|
+
}
|
|
226291
|
+
}
|
|
226292
|
+
const runIdOfCard = (d) => {
|
|
226293
|
+
const map = input.runIdByTarget;
|
|
226294
|
+
if (d.traceWorkIds?.length) {
|
|
226295
|
+
for (const id of [...d.traceWorkIds].reverse()) {
|
|
226296
|
+
const run = map?.get(`work:${id}`);
|
|
226297
|
+
if (run) return run;
|
|
226298
|
+
}
|
|
226299
|
+
}
|
|
226300
|
+
if (d.reviewWorkId) {
|
|
226301
|
+
for (const r of [...reviewWorks.get(d.reviewWorkId) ?? []].reverse()) {
|
|
226302
|
+
if (d.reviewerId && r.reviewerActorId !== d.reviewerId) continue;
|
|
226303
|
+
const run = map?.get(`review:${r.id}`);
|
|
226304
|
+
if (run) return run;
|
|
226305
|
+
}
|
|
226306
|
+
}
|
|
225197
226307
|
return void 0;
|
|
225198
226308
|
};
|
|
225199
226309
|
const cards = drafts.map((d) => ({
|
|
@@ -225203,13 +226313,16 @@ function buildWorkorderActivity(input) {
|
|
|
225203
226313
|
updatedAt: d.updatedAt,
|
|
225204
226314
|
...d.nodeId ? { nodeId: d.nodeId, nodeTitle: nodeName(d.nodeId) } : {},
|
|
225205
226315
|
executor: ref2(d.executorId),
|
|
226316
|
+
...d.reviewWorkId ? { reviewWorkId: d.reviewWorkId, reviewers: d.reviewerIds?.map(ref2) } : {},
|
|
225206
226317
|
...d.handActorId ? { handActor: ref2(d.handActorId) } : {},
|
|
225207
226318
|
phase: d.phase,
|
|
225208
226319
|
status: d.status,
|
|
225209
226320
|
...d.detail ? { detail: d.detail } : {},
|
|
225210
226321
|
artifacts: d.artifacts,
|
|
225211
226322
|
actions: d.actions,
|
|
225212
|
-
|
|
226323
|
+
...d.traceAvailable ? { traceAvailable: true } : {},
|
|
226324
|
+
...d.traceWorkIds?.length ? { traceWorkIds: d.traceWorkIds, traceAvailable: true } : {},
|
|
226325
|
+
.../* @__PURE__ */ ((r) => r ? { runId: r, traceAvailable: true } : {})(runIdOfCard(d))
|
|
225213
226326
|
}));
|
|
225214
226327
|
return { workorderId, cards, truncated };
|
|
225215
226328
|
}
|
|
@@ -225218,6 +226331,8 @@ var init_activity2 = __esm({
|
|
|
225218
226331
|
"../server/src/domains/collab/activity.ts"() {
|
|
225219
226332
|
"use strict";
|
|
225220
226333
|
init_src();
|
|
226334
|
+
init_src4();
|
|
226335
|
+
init_review_activity();
|
|
225221
226336
|
init_workorder_manager();
|
|
225222
226337
|
ACTIVITY_EVENT_KINDS = [
|
|
225223
226338
|
"plan.changed",
|
|
@@ -225347,6 +226462,14 @@ var init_activity2 = __esm({
|
|
|
225347
226462
|
* 留言 / 回压收口——**同样一句索引**。收口 note 与开口正文同源、同样能长到几千字:
|
|
225348
226463
|
* 全库实测 `ws:wo-56bba3c1` 那条收口后小状态仍是 **8096 字**。全文去留言线看。
|
|
225349
226464
|
*/
|
|
226465
|
+
/** 有人接手这条留言、开工了——由 `work.create` 的 `reasons` 引用该 issueId 判定。 */
|
|
226466
|
+
commentHandling: (executor, author) => `${executor} \u6B63\u5728\u5904\u7406\u6765\u81EA ${author} \u7684\u7559\u8A00\u3002`,
|
|
226467
|
+
/**
|
|
226468
|
+
* 处理完**并且真出了新一版**。
|
|
226469
|
+
* ⚠ 只在那一轮真有 conclusion 时才说「更新了一版」——resolve 也可能是「看了但不改」,
|
|
226470
|
+
* 那种情况说出了新版就是一句卡面兑现不了的断言(同「未经 X 确认」那一族)。
|
|
226471
|
+
*/
|
|
226472
|
+
commentAddressed: (executor, author, node2) => `${executor} \u5DF2\u57FA\u4E8E ${author} \u7684\u7559\u8A00\u66F4\u65B0\u4E86\u4E00\u7248\u300A${node2}\u300B\u3002`,
|
|
225350
226473
|
commentResolved: (by, node2) => `${by} \u5DF2\u5904\u7406\u300A${node2}\u300B\u7684\u7559\u8A00\u3002`,
|
|
225351
226474
|
issueResolved: (note) => note
|
|
225352
226475
|
};
|
|
@@ -225370,6 +226493,135 @@ function targetOf(cardId) {
|
|
|
225370
226493
|
return null;
|
|
225371
226494
|
}
|
|
225372
226495
|
function buildWorkorderActivityTrace(input) {
|
|
226496
|
+
const { workorderId, cardId, snap } = input;
|
|
226497
|
+
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
226498
|
+
const empty2 = { workorderId, cardId, attempts: [] };
|
|
226499
|
+
if (!snap) return empty2;
|
|
226500
|
+
const reviews = activityReviews(snap, input.events ?? []);
|
|
226501
|
+
const reviewTarget = reviewCardTarget(cardId, reviews);
|
|
226502
|
+
if (reviewTarget) return buildReviewHistory(input, reviewTarget.targetWorkId, reviewTarget.reviewerActorId, reviews);
|
|
226503
|
+
const card2 = input.events ? buildWorkorderActivity({
|
|
226504
|
+
workorderId,
|
|
226505
|
+
snap,
|
|
226506
|
+
events: input.events,
|
|
226507
|
+
ref: ref2
|
|
226508
|
+
}).cards.find((c) => c.id === cardId) : void 0;
|
|
226509
|
+
const gap = cardId.startsWith("issue:") ? snap.issues.find((i) => i.id === cardId.slice(6) && i.kind === "gap") : void 0;
|
|
226510
|
+
if (gap && !card2?.traceWorkIds?.length) return {
|
|
226511
|
+
...empty2,
|
|
226512
|
+
works: [],
|
|
226513
|
+
notice: "\u7F3A\u53E3\u8BB0\u5F55\u6CA1\u6709\u660E\u786E\u5173\u8054\u5230\u67D0\u6B21\u6267\u884C\uFF0C\u6682\u65F6\u65E0\u6CD5\u786E\u8BA4\u8FD0\u884C\u8F68\u8FF9\u3002"
|
|
226514
|
+
};
|
|
226515
|
+
const workIds = card2?.traceWorkIds ?? (cardId.startsWith("work:") ? [cardId.slice(5)] : []);
|
|
226516
|
+
if (workIds.length > 0) {
|
|
226517
|
+
let firstBriefing;
|
|
226518
|
+
const works = [];
|
|
226519
|
+
for (const workId of workIds) {
|
|
226520
|
+
const work = snap.works.find((w2) => w2.id === workId);
|
|
226521
|
+
if (!work) continue;
|
|
226522
|
+
const single = buildSingleTrace({ ...input, cardId: `work:${workId}` });
|
|
226523
|
+
const briefing = single.briefing;
|
|
226524
|
+
if (!briefing) continue;
|
|
226525
|
+
firstBriefing ??= briefing;
|
|
226526
|
+
const sources = sourcesOfWork(work, input, briefing);
|
|
226527
|
+
const artifacts = single.attempts.at(-1)?.artifacts ?? artifactsOfWork(work, input);
|
|
226528
|
+
works.push({
|
|
226529
|
+
workId,
|
|
226530
|
+
at: work.createdAt,
|
|
226531
|
+
nodeTitle: briefing.nodeTitle,
|
|
226532
|
+
...briefing.spec ? { spec: briefing.spec } : {},
|
|
226533
|
+
sources,
|
|
226534
|
+
attempts: single.attempts,
|
|
226535
|
+
...work.conclusion?.trim() ? { conclusion: work.conclusion.trim() } : {},
|
|
226536
|
+
artifacts,
|
|
226537
|
+
...single.attempts.length ? {} : { emptyReason: input.dispatches === void 0 ? "records_unavailable" : work.startedAt || work.endedAt || work.deadAt || work.cancelledAt || work.conclusion || work.status && work.status !== "running" ? "records_missing" : "not_dispatched" }
|
|
226538
|
+
});
|
|
226539
|
+
}
|
|
226540
|
+
if (works.length) {
|
|
226541
|
+
return {
|
|
226542
|
+
workorderId,
|
|
226543
|
+
cardId,
|
|
226544
|
+
briefing: firstBriefing,
|
|
226545
|
+
works,
|
|
226546
|
+
...gap && !gap.aboutWorkId ? { notice: "\u6839\u636E\u7F3A\u53E3\u63D0\u51FA\u65F6\u7684\u8BB0\u5F55\uFF0C\u5173\u8054\u5230\u8BE5\u8282\u70B9\u3001\u8BE5\u6267\u884C\u4EBA\u5F53\u65F6\u552F\u4E00\u7684\u6267\u884C\u3002" } : {},
|
|
226547
|
+
attempts: works.flatMap((w2) => w2.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId))
|
|
226548
|
+
};
|
|
226549
|
+
}
|
|
226550
|
+
}
|
|
226551
|
+
return buildSingleTrace(input);
|
|
226552
|
+
}
|
|
226553
|
+
function sourcesOfWork(work, input, briefing) {
|
|
226554
|
+
const snap = input.snap;
|
|
226555
|
+
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
226556
|
+
const created = input.events?.find((r) => r.event.kind === "work.create" && r.event.workId === work.id);
|
|
226557
|
+
const event = created?.event;
|
|
226558
|
+
const replyIssueId = replyIssueIdOf(work);
|
|
226559
|
+
const ids2 = work.frozenIssueIds ?? (event?.kind === "work.create" ? event.reasons.filter((r) => r.kind === "issue").map((r) => r.issueId) : replyIssueId ? [replyIssueId] : void 0);
|
|
226560
|
+
if (!ids2) return [{ kind: "unavailable", at: work.createdAt }];
|
|
226561
|
+
const sources = [];
|
|
226562
|
+
for (const issueId of new Set(ids2)) {
|
|
226563
|
+
const issue2 = snap.issues.find((i) => i.id === issueId);
|
|
226564
|
+
if (!issue2 || issue2.createdAt > work.createdAt) {
|
|
226565
|
+
sources.push({ kind: "unavailable", at: work.createdAt, issueId });
|
|
226566
|
+
continue;
|
|
226567
|
+
}
|
|
226568
|
+
if (issue2.kind !== "comment" && issue2.kind !== "change_request") continue;
|
|
226569
|
+
const replySeq = (id) => input.events?.find((e) => e.event.kind === "issue.reply" && e.event.replyId === id)?.seq;
|
|
226570
|
+
if (snap.issueReplies.some((r) => r.issueId === issueId && r.createdAt === work.createdAt && (!created || replySeq(r.id) === void 0))) {
|
|
226571
|
+
sources.push({ kind: "unavailable", at: work.createdAt, issueId });
|
|
226572
|
+
continue;
|
|
226573
|
+
}
|
|
226574
|
+
const replies = snap.issueReplies.filter((r) => {
|
|
226575
|
+
if (r.issueId !== issueId || r.createdAt > work.createdAt) return false;
|
|
226576
|
+
const seq = replySeq(r.id);
|
|
226577
|
+
return created && seq !== void 0 ? seq < created.seq : r.createdAt < work.createdAt;
|
|
226578
|
+
}).sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || (replySeq(a.id) ?? 0) - (replySeq(b2.id) ?? 0) || a.id.localeCompare(b2.id));
|
|
226579
|
+
const reply = replies.at(-1);
|
|
226580
|
+
if (reply?.viaWorkId && snap.works.some((w2) => w2.id === reply.viaWorkId && w2.nodeId === work.nodeId)) {
|
|
226581
|
+
sources.push({ kind: "unavailable", at: work.createdAt, issueId });
|
|
226582
|
+
continue;
|
|
226583
|
+
}
|
|
226584
|
+
sources.push({
|
|
226585
|
+
kind: issueId.startsWith("ann:rework:") ? "gap_resume" : "comment",
|
|
226586
|
+
actor: ref2(reply?.authorActorId ?? issue2.authorActorId),
|
|
226587
|
+
at: reply?.createdAt ?? issue2.createdAt,
|
|
226588
|
+
content: reply?.body ?? issue2.body,
|
|
226589
|
+
issueId,
|
|
226590
|
+
...reply ? { replyId: reply.id } : {}
|
|
226591
|
+
});
|
|
226592
|
+
}
|
|
226593
|
+
return sources.length ? sources : [{
|
|
226594
|
+
kind: "assignment",
|
|
226595
|
+
actor: briefing.actor,
|
|
226596
|
+
at: work.createdAt,
|
|
226597
|
+
...briefing.spec ? { content: briefing.spec } : {}
|
|
226598
|
+
}];
|
|
226599
|
+
}
|
|
226600
|
+
function artifactsOfWork(work, input) {
|
|
226601
|
+
const snap = input.snap;
|
|
226602
|
+
const workId = work.id;
|
|
226603
|
+
const nodeId = work.nodeId;
|
|
226604
|
+
const node2 = snap.nodes.find((n) => n.id === nodeId);
|
|
226605
|
+
const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
|
|
226606
|
+
const isRootBrief = node2?.type === "brief" && !nodesWithInEdges.has(nodeId);
|
|
226607
|
+
const list2 = [];
|
|
226608
|
+
for (const a of snap.artifacts) {
|
|
226609
|
+
if (a.workId !== workId) continue;
|
|
226610
|
+
const artifactNodeId = a.nodeId;
|
|
226611
|
+
const files = a.contentKind === "manifest" ? input.manifestFiles?.get(a.contentRef) : void 0;
|
|
226612
|
+
if (files && files.length > 0) {
|
|
226613
|
+
for (const path41 of files) {
|
|
226614
|
+
list2.push({ id: a.id, name: path41.split("/").pop() || path41, path: path41, nodeId: artifactNodeId });
|
|
226615
|
+
}
|
|
226616
|
+
} else if (isRootBrief) {
|
|
226617
|
+
list2.push({ id: a.id, name: "\u4EFB\u52A1\u76EE\u6807", path: "\u4EFB\u52A1\u76EE\u6807.md", nodeId: artifactNodeId });
|
|
226618
|
+
} else {
|
|
226619
|
+
list2.push({ id: a.id, name: a.name, nodeId: artifactNodeId });
|
|
226620
|
+
}
|
|
226621
|
+
}
|
|
226622
|
+
return list2;
|
|
226623
|
+
}
|
|
226624
|
+
function buildSingleTrace(input) {
|
|
225373
226625
|
const { workorderId, cardId, snap } = input;
|
|
225374
226626
|
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
225375
226627
|
const empty2 = { workorderId, cardId, attempts: [] };
|
|
@@ -225382,26 +226634,6 @@ function buildWorkorderActivityTrace(input) {
|
|
|
225382
226634
|
const nodeId = work?.nodeId ?? review?.nodeId;
|
|
225383
226635
|
if (!nodeId) return empty2;
|
|
225384
226636
|
const node2 = nodeById.get(nodeId);
|
|
225385
|
-
const nodesWithInEdges = new Set(snap.edges.map((e) => e.toNodeId));
|
|
225386
|
-
const isRootBrief = node2?.type === "brief" && !nodesWithInEdges.has(nodeId);
|
|
225387
|
-
const artifactsOfWork = (workId) => {
|
|
225388
|
-
const list2 = [];
|
|
225389
|
-
for (const a of snap.artifacts) {
|
|
225390
|
-
if (a.workId !== workId) continue;
|
|
225391
|
-
const artifactNodeId = a.nodeId;
|
|
225392
|
-
const files = a.contentKind === "manifest" ? input.manifestFiles?.get(a.contentRef) : void 0;
|
|
225393
|
-
if (files && files.length > 0) {
|
|
225394
|
-
for (const path41 of files) {
|
|
225395
|
-
list2.push({ id: a.id, name: path41.split("/").pop() || path41, path: path41, nodeId: artifactNodeId });
|
|
225396
|
-
}
|
|
225397
|
-
} else if (isRootBrief) {
|
|
225398
|
-
list2.push({ id: a.id, name: "\u4EFB\u52A1\u76EE\u6807", path: "\u4EFB\u52A1\u76EE\u6807.md", nodeId: artifactNodeId });
|
|
225399
|
-
} else {
|
|
225400
|
-
list2.push({ id: a.id, name: a.name, nodeId: artifactNodeId });
|
|
225401
|
-
}
|
|
225402
|
-
}
|
|
225403
|
-
return list2;
|
|
225404
|
-
};
|
|
225405
226637
|
const managerId = resolveWorkorderManager(snap) ?? snap.workorder.ownerActorId;
|
|
225406
226638
|
const startedAt = work?.createdAt ?? review?.createdAt;
|
|
225407
226639
|
const spec = node2?.spec?.trim();
|
|
@@ -225411,25 +226643,115 @@ function buildWorkorderActivityTrace(input) {
|
|
|
225411
226643
|
nodeTitle: node2?.title || nodeId.split(":").slice(2).join(":") || nodeId,
|
|
225412
226644
|
...spec ? { spec } : {}
|
|
225413
226645
|
};
|
|
225414
|
-
const rows = (input.dispatches ?? []).filter((d) => d.targetKind === target.kind && d.targetId === target.id).slice().sort((a, b2) => a.createdAt.localeCompare(b2.createdAt));
|
|
226646
|
+
const rows = (input.dispatches ?? []).filter((d) => d.targetKind === target.kind && d.targetId === target.id).slice().sort((a, b2) => a.createdAt.localeCompare(b2.createdAt) || a.id.localeCompare(b2.id));
|
|
225415
226647
|
const executorId = work?.assigneeActorId ?? review?.reviewerActorId;
|
|
225416
226648
|
const conclusion = work?.conclusion?.trim() || review?.note?.trim() || void 0;
|
|
225417
226649
|
const attempts = rows.map((d, i) => {
|
|
225418
226650
|
const isLast = i === rows.length - 1;
|
|
225419
|
-
const actorId =
|
|
226651
|
+
const actorId = d.actorId ?? executorId;
|
|
225420
226652
|
return {
|
|
225421
226653
|
runId: d.id,
|
|
225422
226654
|
at: d.createdAt,
|
|
225423
226655
|
actor: ref2(actorId),
|
|
225424
226656
|
...isLast && conclusion ? { conclusion } : {},
|
|
225425
|
-
artifacts: isLast && work ? artifactsOfWork(work
|
|
226657
|
+
artifacts: isLast && work ? artifactsOfWork(work, input) : []
|
|
225426
226658
|
};
|
|
225427
226659
|
});
|
|
225428
226660
|
return { workorderId, cardId, briefing, attempts };
|
|
225429
226661
|
}
|
|
226662
|
+
function buildReviewHistory(input, targetWorkId, reviewerActorId, allReviews) {
|
|
226663
|
+
const { workorderId, cardId } = input;
|
|
226664
|
+
const snap = input.snap;
|
|
226665
|
+
const ref2 = input.ref ?? ((id) => ({ id }));
|
|
226666
|
+
const rows = allReviews.filter((r) => r.targetWorkId === targetWorkId && r.reviewerActorId === reviewerActorId);
|
|
226667
|
+
if (!rows.length) return { workorderId, cardId, attempts: [] };
|
|
226668
|
+
const byId = new Map(rows.map((r) => [r.id, r]));
|
|
226669
|
+
const current = new Set(latestActivityReviews(rows, input.events ?? []).map((r) => r.id));
|
|
226670
|
+
const work = snap.works.find((w2) => w2.id === targetWorkId);
|
|
226671
|
+
const nodeId = rows[0].nodeId;
|
|
226672
|
+
const events = [];
|
|
226673
|
+
for (const rec of [...input.events ?? []].sort((a, b2) => a.seq - b2.seq)) {
|
|
226674
|
+
const e = rec.event;
|
|
226675
|
+
const r = "reviewId" in e && e.reviewId ? byId.get(e.reviewId) : void 0;
|
|
226676
|
+
const direct = e.kind === "review.response" && e.targetWorkId === targetWorkId && e.reviewerActorId === reviewerActorId;
|
|
226677
|
+
let retry = false;
|
|
226678
|
+
if (e.kind === "plan.node_retry" && e.nodeId === nodeId) {
|
|
226679
|
+
const preceding = snap.works.filter((w2) => w2.nodeId === nodeId && w2.createdAt <= rec.createdAt).sort((a, b2) => b2.createdAt.localeCompare(a.createdAt));
|
|
226680
|
+
retry = preceding[0]?.id === targetWorkId && rows.some((r2) => r2.createdAt <= rec.createdAt);
|
|
226681
|
+
}
|
|
226682
|
+
if (!r && !direct && !retry) continue;
|
|
226683
|
+
let label;
|
|
226684
|
+
let detail;
|
|
226685
|
+
switch (e.kind) {
|
|
226686
|
+
case "review.create":
|
|
226687
|
+
label = "\u521B\u5EFA\u5BA1\u6838";
|
|
226688
|
+
break;
|
|
226689
|
+
case "review.started":
|
|
226690
|
+
label = "\u5F00\u59CB\u5BA1\u6838";
|
|
226691
|
+
break;
|
|
226692
|
+
case "review.timeout":
|
|
226693
|
+
label = "\u5BA1\u6838\u6267\u884C\u8D85\u65F6 / \u5931\u8D25";
|
|
226694
|
+
break;
|
|
226695
|
+
case "review.kill":
|
|
226696
|
+
label = "\u5BA1\u6838\u5DF2\u505C\u6B62";
|
|
226697
|
+
detail = e.reason;
|
|
226698
|
+
break;
|
|
226699
|
+
case "review.response":
|
|
226700
|
+
label = e.verdict === "approve" ? "\u5BA1\u6838\u901A\u8FC7" : "\u5BA1\u6838\u4E0D\u901A\u8FC7";
|
|
226701
|
+
detail = e.note ?? void 0;
|
|
226702
|
+
break;
|
|
226703
|
+
case "plan.node_retry":
|
|
226704
|
+
label = "\u4ECB\u5165\u91CD\u8BD5";
|
|
226705
|
+
break;
|
|
226706
|
+
default:
|
|
226707
|
+
continue;
|
|
226708
|
+
}
|
|
226709
|
+
const actorId = e.kind === "plan.node_retry" ? e.by : "reviewerActorId" in e ? e.reviewerActorId : rec.actorId;
|
|
226710
|
+
events.push({
|
|
226711
|
+
seq: rec.seq,
|
|
226712
|
+
at: rec.createdAt,
|
|
226713
|
+
kind: e.kind,
|
|
226714
|
+
label,
|
|
226715
|
+
actor: ref2(actorId),
|
|
226716
|
+
...r ? { reviewId: r.id, reviewGroup: r.reviewGroup } : {},
|
|
226717
|
+
...detail ? { detail } : {}
|
|
226718
|
+
});
|
|
226719
|
+
}
|
|
226720
|
+
const reviews = rows.map((r) => {
|
|
226721
|
+
const single = buildSingleTrace({ ...input, snap: { ...snap, reviews: allReviews }, cardId: `review:${r.id}` });
|
|
226722
|
+
return {
|
|
226723
|
+
reviewId: r.id,
|
|
226724
|
+
reviewGroup: r.reviewGroup,
|
|
226725
|
+
actor: ref2(r.reviewerActorId),
|
|
226726
|
+
at: r.createdAt,
|
|
226727
|
+
...r.endedAt ?? r.decidedAt ?? r.cancelledAt ? { endedAt: r.endedAt ?? r.decidedAt ?? r.cancelledAt } : {},
|
|
226728
|
+
status: reviewLabel(r),
|
|
226729
|
+
current: current.has(r.id),
|
|
226730
|
+
...r.note ? { note: r.note } : {},
|
|
226731
|
+
attempts: single.attempts
|
|
226732
|
+
};
|
|
226733
|
+
});
|
|
226734
|
+
return {
|
|
226735
|
+
workorderId,
|
|
226736
|
+
cardId,
|
|
226737
|
+
attempts: reviews.flatMap((r) => r.attempts).sort((a, b2) => a.at.localeCompare(b2.at) || a.runId.localeCompare(b2.runId)),
|
|
226738
|
+
reviewHistory: {
|
|
226739
|
+
targetWorkId,
|
|
226740
|
+
nodeTitle: snap.nodes.find((n) => n.id === nodeId)?.title ?? nodeId,
|
|
226741
|
+
...work?.outputVersionNo != null ? { outputVersionNo: work.outputVersionNo } : {},
|
|
226742
|
+
artifacts: work ? artifactsOfWork(work, input) : [],
|
|
226743
|
+
reviews,
|
|
226744
|
+
events,
|
|
226745
|
+
...input.dispatches === void 0 ? { recordsUnavailable: true } : {}
|
|
226746
|
+
}
|
|
226747
|
+
};
|
|
226748
|
+
}
|
|
225430
226749
|
var init_activity_trace = __esm({
|
|
225431
226750
|
"../server/src/domains/collab/activity-trace.ts"() {
|
|
225432
226751
|
"use strict";
|
|
226752
|
+
init_src4();
|
|
226753
|
+
init_review_activity();
|
|
226754
|
+
init_activity2();
|
|
225433
226755
|
init_workorder_manager();
|
|
225434
226756
|
}
|
|
225435
226757
|
});
|
|
@@ -227283,10 +228605,7 @@ function collabDomain(opts) {
|
|
|
227283
228605
|
const hit = cache.get(companyId);
|
|
227284
228606
|
if (hit) return hit;
|
|
227285
228607
|
const e = await opts.resolveEngine(companyId);
|
|
227286
|
-
|
|
227287
|
-
cache.set(companyId, defaultCtx);
|
|
227288
|
-
return defaultCtx;
|
|
227289
|
-
}
|
|
228608
|
+
const artifacts = opts.resolveArtifacts ? await opts.resolveArtifacts(companyId) : e.kernel === opts.kernel ? opts.artifacts : void 0;
|
|
227290
228609
|
const ctx = {
|
|
227291
228610
|
kernel: e.kernel,
|
|
227292
228611
|
oplog: e.oplog,
|
|
@@ -227296,7 +228615,7 @@ function collabDomain(opts) {
|
|
|
227296
228615
|
// ★ 产物旁存也要按公司取(见 resolveArtifacts 的注释):漏了这一行,非默认公司的
|
|
227297
228616
|
// `/api/workorders/:id/files` 与 node_output 清单恒空——写侧写对了、读侧根本没接线。
|
|
227298
228617
|
// 抛出来就抛出来:宁可 500 说"这家公司的数据面解析不了",也不能回落默认公司的库。
|
|
227299
|
-
...
|
|
228618
|
+
...artifacts ? { artifacts } : {}
|
|
227300
228619
|
};
|
|
227301
228620
|
cache.set(companyId, ctx);
|
|
227302
228621
|
return ctx;
|
|
@@ -227517,6 +228836,7 @@ function collabDomain(opts) {
|
|
|
227517
228836
|
return {
|
|
227518
228837
|
status: 200,
|
|
227519
228838
|
body: buildWorkorderActivity({
|
|
228839
|
+
viewerActorId: req.auth.actor,
|
|
227520
228840
|
workorderId,
|
|
227521
228841
|
snap,
|
|
227522
228842
|
events,
|
|
@@ -227550,7 +228870,15 @@ function collabDomain(opts) {
|
|
|
227550
228870
|
...extra?.avatar ? { avatar: extra.avatar } : {}
|
|
227551
228871
|
};
|
|
227552
228872
|
};
|
|
227553
|
-
const snap = engineStore ? await engineStore.transaction((tx) =>
|
|
228873
|
+
const [snap, events] = engineStore ? await engineStore.transaction(async (tx) => [
|
|
228874
|
+
await tx.loadWorkorder(workorderId),
|
|
228875
|
+
await tx.listEvents(
|
|
228876
|
+
workorderId,
|
|
228877
|
+
0,
|
|
228878
|
+
cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? void 0 : ACTIVITY_EVENT_LIMIT,
|
|
228879
|
+
cardId.startsWith(REVIEW_CARD_PREFIX) || cardId.startsWith("review:") ? REVIEW_TRACE_EVENT_KINDS : ACTIVITY_EVENT_KINDS
|
|
228880
|
+
)
|
|
228881
|
+
]) : [null, []];
|
|
227554
228882
|
const manifestFiles = snap ? await resolveActivityManifestFiles(snap.artifacts, blobs) : void 0;
|
|
227555
228883
|
const dispatches = await listDispatchRows(workorderId, opts.dispatchesOfWorkorder);
|
|
227556
228884
|
return {
|
|
@@ -227559,6 +228887,7 @@ function collabDomain(opts) {
|
|
|
227559
228887
|
workorderId,
|
|
227560
228888
|
cardId,
|
|
227561
228889
|
snap,
|
|
228890
|
+
events,
|
|
227562
228891
|
ref: ref2,
|
|
227563
228892
|
...dispatches ? { dispatches } : {},
|
|
227564
228893
|
...manifestFiles ? { manifestFiles } : {}
|
|
@@ -227669,6 +228998,9 @@ function collabDomain(opts) {
|
|
|
227669
228998
|
return { status: 201, body: { item } };
|
|
227670
228999
|
});
|
|
227671
229000
|
router.post("/api/workorders/:id/hold", async (req) => {
|
|
229001
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
229002
|
+
return { status: 403, body: { error: { code: "forbidden", message: "\u6682\u505C\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
|
|
229003
|
+
}
|
|
227672
229004
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
227673
229005
|
const ws = req.params.id;
|
|
227674
229006
|
const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
|
|
@@ -227708,6 +229040,9 @@ function collabDomain(opts) {
|
|
|
227708
229040
|
return { status: 200, body: { cancelled } };
|
|
227709
229041
|
});
|
|
227710
229042
|
router.post("/api/workorders/:id/dispatch", async (req) => {
|
|
229043
|
+
if (!isHumanActor(req.auth.actor)) {
|
|
229044
|
+
return { status: 403, body: { error: { code: "forbidden", message: "\u6062\u590D\u6574\u4E2A\u5DE5\u5355\u662F\u6CBB\u7406\u64CD\u4F5C\uFF0C\u4EC5\u4EBA\u7C7B\u53EF\u6267\u884C" } } };
|
|
229045
|
+
}
|
|
227711
229046
|
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
227712
229047
|
const ws = req.params.id;
|
|
227713
229048
|
const already = !kernel.model.pausedWorkorders.has(ws);
|
|
@@ -228033,6 +229368,7 @@ var init_collab = __esm({
|
|
|
228033
229368
|
init_workorder_detail();
|
|
228034
229369
|
init_workorders();
|
|
228035
229370
|
init_activity2();
|
|
229371
|
+
init_review_activity();
|
|
228036
229372
|
init_activity_trace();
|
|
228037
229373
|
init_inbox();
|
|
228038
229374
|
init_escalation_attention();
|
|
@@ -231108,42 +232444,17 @@ function mapChatItemToSnapshotEntry(row) {
|
|
|
231108
232444
|
itemType,
|
|
231109
232445
|
role,
|
|
231110
232446
|
status,
|
|
231111
|
-
|
|
232447
|
+
// ADR-0510 附录 A:版本轴 = 会话逻辑时钟(`chat_items.version`)。
|
|
232448
|
+
// 此前这里填的是 `turnVersion`(轮级 resync 游标),而增量流填的是进程内每 item 计数——
|
|
232449
|
+
// 两套编号空间,一轮中途拉快照就把随后每一帧判 stale,进 resync 死循环。
|
|
232450
|
+
itemVersion: row.version,
|
|
231112
232451
|
seq: row.seq,
|
|
232452
|
+
ord: row.ord,
|
|
231113
232453
|
messageId: row.messageId,
|
|
231114
232454
|
updatedAt: row.updatedAt,
|
|
231115
232455
|
payload: row.payload
|
|
231116
232456
|
};
|
|
231117
232457
|
}
|
|
231118
|
-
function synthesizeMessageItem(msg, seq) {
|
|
231119
|
-
const attachments = Array.isArray(msg.attachments) ? msg.attachments.map((a) => ({
|
|
231120
|
-
name: a.name,
|
|
231121
|
-
...a.blobRef !== void 0 ? { blobRef: a.blobRef } : {},
|
|
231122
|
-
...a.contentType !== void 0 ? { contentType: a.contentType } : {}
|
|
231123
|
-
})) : void 0;
|
|
231124
|
-
const clientSubmitId = msg.role === "user" ? msg.clientSubmitId ?? clientSubmitIdFromMessageKey(msg.messageKey) : void 0;
|
|
231125
|
-
const role = msg.role === "user" || msg.role === "system" ? msg.role : "assistant";
|
|
231126
|
-
const status = msg.status === "running" ? "streaming" : msg.status === "error" ? "failed" : "completed";
|
|
231127
|
-
return {
|
|
231128
|
-
// ADR-0510 D1:有 csid 就用**与账本 user item、前端乐观桶位同名**的身份;没有(历史行 / 老客户端)
|
|
231129
|
-
// 才退回合成 id。三方同名,桶按 itemId 合并才闭环——否则同一条 user 消息画两个气泡。
|
|
231130
|
-
itemId: clientSubmitId ? optimisticUserItemId(clientSubmitId) : `chat-message:${msg.id}`,
|
|
231131
|
-
turnId: msg.runId ?? `chat-message-turn:${msg.id}`,
|
|
231132
|
-
itemType: "message",
|
|
231133
|
-
role,
|
|
231134
|
-
status,
|
|
231135
|
-
itemVersion: 0,
|
|
231136
|
-
seq,
|
|
231137
|
-
messageId: msg.id,
|
|
231138
|
-
updatedAt: msg.completedAt ?? msg.createdAt,
|
|
231139
|
-
payload: {
|
|
231140
|
-
role,
|
|
231141
|
-
text: msg.content ?? "",
|
|
231142
|
-
...attachments?.length ? { attachments } : {},
|
|
231143
|
-
...clientSubmitId ? { clientSubmitId } : {}
|
|
231144
|
-
}
|
|
231145
|
-
};
|
|
231146
|
-
}
|
|
231147
232458
|
function throwWorkdirError(code2) {
|
|
231148
232459
|
switch (code2) {
|
|
231149
232460
|
case "PATH_ESCAPE":
|
|
@@ -231413,7 +232724,8 @@ function createChatSessionsDomain(opts) {
|
|
|
231413
232724
|
...Object.prototype.hasOwnProperty.call(body2, "projectId") ? { projectId: body2.projectId } : {},
|
|
231414
232725
|
// 会话级模型覆盖:显式传 null / 空串 = 清除覆盖(回到旧解析);不传这一项 = 不动它。
|
|
231415
232726
|
...Object.prototype.hasOwnProperty.call(body2, "model") ? { model: normalizeModel(body2.model) } : {},
|
|
231416
|
-
|
|
232727
|
+
// 只有显式传了才写——改标题 / 换项目 / 换模型都不是「有新动静」,不该顶掉列表顺序与已读水位。
|
|
232728
|
+
...body2.touchedAt !== void 0 ? { touchedAt: body2.touchedAt } : {}
|
|
231417
232729
|
});
|
|
231418
232730
|
return { status: 200, body: await store2.getSession(req.params.id) };
|
|
231419
232731
|
});
|
|
@@ -231505,57 +232817,50 @@ function createChatSessionsDomain(opts) {
|
|
|
231505
232817
|
if (!session || session.humanActorId !== req.auth.actor) throw new ApiError(404, "NOT_FOUND", "session not found");
|
|
231506
232818
|
const chatSessionId = req.params.id;
|
|
231507
232819
|
const sinceVersion = Math.max(0, Number(req.query?.get("sinceVersion") ?? "0") || 0);
|
|
232820
|
+
const beforeOrdRaw = Number(req.query?.get("beforeOrd") ?? "0") || 0;
|
|
232821
|
+
const beforeOrd = beforeOrdRaw > 0 ? beforeOrdRaw : void 0;
|
|
232822
|
+
const limitRaw = Number(req.query?.get("limit") ?? "0") || 0;
|
|
232823
|
+
const limit = limitRaw > 0 ? Math.min(limitRaw, MAX_SNAPSHOT_ITEMS) : DEFAULT_SNAPSHOT_ITEMS;
|
|
231508
232824
|
const cursor = opts.liveChat?.cursorFor(chatSessionId) ?? null;
|
|
231509
232825
|
let turnId = cursor?.turnId ?? null;
|
|
231510
|
-
let inflightRunId = opts.liveChat?.runFor(chatSessionId) ?? null;
|
|
231511
232826
|
if (!turnId) {
|
|
231512
232827
|
const turns = await turnsFor(req);
|
|
231513
232828
|
const active = turns ? await turns.activeTurn(chatSessionId).catch(() => null) : null;
|
|
231514
232829
|
turnId = active?.id ?? null;
|
|
231515
|
-
inflightRunId ??= active?.assistantRunId ?? null;
|
|
231516
232830
|
}
|
|
231517
232831
|
const snapshotEntries = [];
|
|
231518
232832
|
let nextSinceVersion = sinceVersion;
|
|
231519
|
-
|
|
231520
|
-
|
|
232833
|
+
let hasMoreBefore = false;
|
|
232834
|
+
if (sinceVersion > 0) {
|
|
232835
|
+
if (turnId) {
|
|
232836
|
+
const rows = await items.listItemsByTurn(chatSessionId, turnId, { sinceTurnVersion: sinceVersion }).catch(() => []);
|
|
232837
|
+
for (const row of rows) {
|
|
232838
|
+
snapshotEntries.push(mapChatItemToSnapshotEntry(row));
|
|
232839
|
+
if (row.turnVersion > nextSinceVersion) nextSinceVersion = row.turnVersion;
|
|
232840
|
+
}
|
|
232841
|
+
}
|
|
232842
|
+
} else {
|
|
232843
|
+
const rows = await items.listSessionItems(chatSessionId, {
|
|
232844
|
+
limit,
|
|
232845
|
+
...beforeOrd !== void 0 ? { beforeOrd } : {}
|
|
232846
|
+
}).catch(() => []);
|
|
232847
|
+
hasMoreBefore = rows.length >= limit;
|
|
231521
232848
|
for (const row of rows) {
|
|
231522
232849
|
snapshotEntries.push(mapChatItemToSnapshotEntry(row));
|
|
231523
232850
|
if (row.turnVersion > nextSinceVersion) nextSinceVersion = row.turnVersion;
|
|
231524
232851
|
}
|
|
231525
232852
|
}
|
|
231526
|
-
const
|
|
231527
|
-
const HISTORY_TURNS = 12;
|
|
231528
|
-
const historicalEntries = [];
|
|
231529
|
-
if (sinceVersion === 0) {
|
|
231530
|
-
const listRecentTurns2 = store2.listRecentTurns;
|
|
231531
|
-
const messages = listRecentTurns2 ? (await listRecentTurns2.call(store2, chatSessionId, HISTORY_TURNS).catch(() => ({ messages: [], hasMoreBefore: false }))).messages : windowMessagesByTurns(await store2.listMessages(chatSessionId).catch(() => []), HISTORY_TURNS).messages;
|
|
231532
|
-
let historySeq = 0;
|
|
231533
|
-
for (const msg of messages) {
|
|
231534
|
-
if (!msg.id) continue;
|
|
231535
|
-
const rows = await items.listItemsByMessage(msg.id).catch(() => []);
|
|
231536
|
-
const historical = turnId ? rows.filter((row) => row.turnId !== turnId) : rows;
|
|
231537
|
-
if (rows.length > 0) {
|
|
231538
|
-
for (const row of [...historical].sort((a, b2) => a.seq - b2.seq)) {
|
|
231539
|
-
historySeq += 1;
|
|
231540
|
-
historicalEntries.push({ ...mapChatItemToSnapshotEntry(row), seq: historySeq });
|
|
231541
|
-
}
|
|
231542
|
-
continue;
|
|
231543
|
-
}
|
|
231544
|
-
if (msg.role === "assistant" && msg.status === "running" && inflightRunId && msg.runId === inflightRunId) continue;
|
|
231545
|
-
const synthesized = synthesizeMessageItem(msg, historySeq + 1);
|
|
231546
|
-
if (activeTurnItemIds.has(synthesized.itemId)) continue;
|
|
231547
|
-
historySeq += 1;
|
|
231548
|
-
historicalEntries.push(synthesized);
|
|
231549
|
-
}
|
|
231550
|
-
}
|
|
232853
|
+
const oldestOrd = snapshotEntries.find((e) => e.ord !== null)?.ord ?? null;
|
|
231551
232854
|
const body2 = {
|
|
231552
232855
|
chatSessionId,
|
|
231553
232856
|
turnId,
|
|
231554
|
-
//
|
|
231555
|
-
items:
|
|
232857
|
+
// 已按 ord 升序——服务端给什么顺序,客户端就按 ord 排出什么顺序,两边同一把尺子。
|
|
232858
|
+
items: snapshotEntries,
|
|
231556
232859
|
nextSinceVersion,
|
|
231557
232860
|
frameCursor: cursor ? { epoch: cursor.epoch, seq: cursor.seq } : { epoch: 0, seq: 0 },
|
|
231558
|
-
canAppend: cursor?.canAppend ?? false
|
|
232861
|
+
canAppend: cursor?.canAppend ?? false,
|
|
232862
|
+
oldestOrd,
|
|
232863
|
+
hasMoreBefore
|
|
231559
232864
|
};
|
|
231560
232865
|
return { status: 200, body: body2 };
|
|
231561
232866
|
});
|
|
@@ -231639,7 +232944,11 @@ function createChatSessionsDomain(opts) {
|
|
|
231639
232944
|
...record8.label ? { label: record8.label } : {},
|
|
231640
232945
|
...record8.taskDigest ? { taskDigest: record8.taskDigest } : {},
|
|
231641
232946
|
parentMessageId: record8.parentMessageId,
|
|
231642
|
-
|
|
232947
|
+
/* `messageId` 必须过 `touchAnchorMessageId`:流水行的主键是 `(delegation_id, message_id)`,
|
|
232948
|
+
服务端为此在锚点后缀了一段随机 token(`DelegationService.touchAnchor`)。那段后缀是存储
|
|
232949
|
+
的去重装置,直接发给前端等于给了一条 `messages[]` 里不存在的 id ——追加转达卡全部掉进
|
|
232950
|
+
floating 堆在流末尾,同一条消息上的两条委派还会因后缀不同被拆成两张单专家卡。 */
|
|
232951
|
+
touches: touches.map((t) => ({ messageId: touchAnchorMessageId(t.messageId), kind: t.kind, at: t.at })),
|
|
231643
232952
|
artifacts: collectDelegationArtifacts(record8.roundSummaries),
|
|
231644
232953
|
createdAt: record8.createdAt,
|
|
231645
232954
|
...record8.settledAt ? { settledAt: record8.settledAt } : {}
|
|
@@ -231908,7 +233217,7 @@ function createChatSessionsDomain(opts) {
|
|
|
231908
233217
|
});
|
|
231909
233218
|
};
|
|
231910
233219
|
}
|
|
231911
|
-
var import_node_crypto59, WORKDIR_READ_MAX_BYTES;
|
|
233220
|
+
var import_node_crypto59, DEFAULT_SNAPSHOT_ITEMS, MAX_SNAPSHOT_ITEMS, WORKDIR_READ_MAX_BYTES;
|
|
231912
233221
|
var init_chat_sessions = __esm({
|
|
231913
233222
|
"../server/src/domains/chat-sessions/index.ts"() {
|
|
231914
233223
|
"use strict";
|
|
@@ -231929,6 +233238,8 @@ var init_chat_sessions = __esm({
|
|
|
231929
233238
|
init_workorder_terminal();
|
|
231930
233239
|
init_session_workdir();
|
|
231931
233240
|
init_project_bundle();
|
|
233241
|
+
DEFAULT_SNAPSHOT_ITEMS = 400;
|
|
233242
|
+
MAX_SNAPSHOT_ITEMS = 1e3;
|
|
231932
233243
|
WORKDIR_READ_MAX_BYTES = 2 * 1024 * 1024;
|
|
231933
233244
|
}
|
|
231934
233245
|
});
|
|
@@ -236591,6 +237902,9 @@ function makeStoreCodeRepoResolver(deps) {
|
|
|
236591
237902
|
}
|
|
236592
237903
|
};
|
|
236593
237904
|
}
|
|
237905
|
+
function makeCompanyCodeRepoResolver(storesFor) {
|
|
237906
|
+
return async (ws, companyId) => makeStoreCodeRepoResolver(await storesFor(companyId)).resolveCodeRepo(ws);
|
|
237907
|
+
}
|
|
236594
237908
|
var init_resolve = __esm({
|
|
236595
237909
|
"../server/src/git/resolve.ts"() {
|
|
236596
237910
|
"use strict";
|
|
@@ -237541,16 +238855,28 @@ var init_fused_signal_router = __esm({
|
|
|
237541
238855
|
}
|
|
237542
238856
|
});
|
|
237543
238857
|
|
|
238858
|
+
// ../storage/src/pg-ident.ts
|
|
238859
|
+
function quoteIdent2(name) {
|
|
238860
|
+
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/.test(name)) throw new Error(`invalid schema name: ${name}`);
|
|
238861
|
+
return `"${name}"`;
|
|
238862
|
+
}
|
|
238863
|
+
var init_pg_ident = __esm({
|
|
238864
|
+
"../storage/src/pg-ident.ts"() {
|
|
238865
|
+
"use strict";
|
|
238866
|
+
}
|
|
238867
|
+
});
|
|
238868
|
+
|
|
237544
238869
|
// ../storage/src/postgres.ts
|
|
237545
238870
|
var import_node_crypto67, ident3, isUniqueViolation, PostgresOplogStore, PostgresBlobStore;
|
|
237546
238871
|
var init_postgres = __esm({
|
|
237547
238872
|
"../storage/src/postgres.ts"() {
|
|
237548
238873
|
"use strict";
|
|
237549
238874
|
import_node_crypto67 = require("node:crypto");
|
|
238875
|
+
init_pg_ident();
|
|
237550
238876
|
init_esm();
|
|
237551
238877
|
init_src();
|
|
237552
238878
|
ident3 = (s2) => {
|
|
237553
|
-
|
|
238879
|
+
quoteIdent2(s2);
|
|
237554
238880
|
return s2;
|
|
237555
238881
|
};
|
|
237556
238882
|
isUniqueViolation = (err) => typeof err === "object" && err !== null && err.code === "23505";
|
|
@@ -237772,7 +239098,9 @@ function rowToVariable(row) {
|
|
|
237772
239098
|
updatedAt: new Date(row.updated_at).toISOString(),
|
|
237773
239099
|
// ADR 凭据保险库补章 §D2.1:到期/取用时间戳(text 列存 ISO 字符串,原样带出;null/空 = undefined)。
|
|
237774
239100
|
...row.expires_at ? { expiresAt: row.expires_at } : {},
|
|
237775
|
-
...row.last_used_at ? { lastUsedAt: row.last_used_at } : {}
|
|
239101
|
+
...row.last_used_at ? { lastUsedAt: row.last_used_at } : {},
|
|
239102
|
+
// 展示名(存量行没有 → 不带这一格,展示侧回落成 key)。
|
|
239103
|
+
...row.name ? { name: row.name } : {}
|
|
237776
239104
|
};
|
|
237777
239105
|
}
|
|
237778
239106
|
var ident4, PostgresRegistryStore, rowToActor, rowToConfig, rowToAgentPrefs;
|
|
@@ -237780,9 +239108,10 @@ var init_postgres_registry = __esm({
|
|
|
237780
239108
|
"../storage/src/postgres-registry.ts"() {
|
|
237781
239109
|
"use strict";
|
|
237782
239110
|
init_esm();
|
|
239111
|
+
init_pg_ident();
|
|
237783
239112
|
init_src();
|
|
237784
239113
|
ident4 = (s2) => {
|
|
237785
|
-
|
|
239114
|
+
quoteIdent2(s2);
|
|
237786
239115
|
return s2;
|
|
237787
239116
|
};
|
|
237788
239117
|
PostgresRegistryStore = class _PostgresRegistryStore {
|
|
@@ -237938,6 +239267,7 @@ var init_postgres_registry = __esm({
|
|
|
237938
239267
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS project_id text`);
|
|
237939
239268
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS environment text`);
|
|
237940
239269
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS delivery_mode text`);
|
|
239270
|
+
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS name text`);
|
|
237941
239271
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS expires_at text`);
|
|
237942
239272
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS last_used_at text`);
|
|
237943
239273
|
await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_key_actor_uq"`);
|
|
@@ -238436,10 +239766,13 @@ var init_postgres_registry = __esm({
|
|
|
238436
239766
|
/* ---------- 变量 ---------- */
|
|
238437
239767
|
async putVariable(v2) {
|
|
238438
239768
|
await this.pool.query(
|
|
238439
|
-
`
|
|
238440
|
-
|
|
239769
|
+
/* `name` 走 COALESCE:**没传 = 不动**。连接器授权流程写变量时手上没有「名称」,
|
|
239770
|
+
普通赋值会把人在密钥表单里填的名字抹掉——同 connectors.connected_by 那条。 */
|
|
239771
|
+
`INSERT INTO ${this.s}.variables (key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at, expires_at, name)
|
|
239772
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
|
238441
239773
|
ON CONFLICT (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) DO UPDATE
|
|
238442
|
-
SET scope=$2, actor_id=$3, project_id=$4, connector_id=$5, overrides=$6, delivery_mode=$7, value_encrypted=$8, updated_at=$9, expires_at=$10
|
|
239774
|
+
SET scope=$2, actor_id=$3, project_id=$4, connector_id=$5, overrides=$6, delivery_mode=$7, value_encrypted=$8, updated_at=$9, expires_at=$10,
|
|
239775
|
+
name=COALESCE($11, ${this.s}.variables.name)`,
|
|
238443
239776
|
[
|
|
238444
239777
|
v2.key,
|
|
238445
239778
|
v2.scope,
|
|
@@ -238450,7 +239783,8 @@ var init_postgres_registry = __esm({
|
|
|
238450
239783
|
v2.deliveryMode ?? null,
|
|
238451
239784
|
v2.valueEncrypted,
|
|
238452
239785
|
v2.updatedAt,
|
|
238453
|
-
v2.expiresAt ?? null
|
|
239786
|
+
v2.expiresAt ?? null,
|
|
239787
|
+
v2.name ?? null
|
|
238454
239788
|
]
|
|
238455
239789
|
);
|
|
238456
239790
|
}
|
|
@@ -238546,17 +239880,6 @@ var init_postgres_registry = __esm({
|
|
|
238546
239880
|
}
|
|
238547
239881
|
});
|
|
238548
239882
|
|
|
238549
|
-
// ../storage/src/pg-ident.ts
|
|
238550
|
-
function quoteIdent2(name) {
|
|
238551
|
-
if (!/^[A-Za-z0-9_][A-Za-z0-9_.-]*$/.test(name)) throw new Error(`invalid schema name: ${name}`);
|
|
238552
|
-
return `"${name}"`;
|
|
238553
|
-
}
|
|
238554
|
-
var init_pg_ident = __esm({
|
|
238555
|
-
"../storage/src/pg-ident.ts"() {
|
|
238556
|
-
"use strict";
|
|
238557
|
-
}
|
|
238558
|
-
});
|
|
238559
|
-
|
|
238560
239883
|
// ../storage/src/postgres-assistant-store.ts
|
|
238561
239884
|
var ASSISTANT_LOCK_NAMESPACE, rowToBinding, rowToHistory, PostgresAssistantBindStore, PostgresHumanPrefsStore;
|
|
238562
239885
|
var init_postgres_assistant_store = __esm({
|
|
@@ -238865,8 +240188,9 @@ var init_postgres_channels = __esm({
|
|
|
238865
240188
|
"../storage/src/postgres-channels.ts"() {
|
|
238866
240189
|
"use strict";
|
|
238867
240190
|
init_esm();
|
|
240191
|
+
init_pg_ident();
|
|
238868
240192
|
ident5 = (s2) => {
|
|
238869
|
-
|
|
240193
|
+
quoteIdent2(s2);
|
|
238870
240194
|
return s2;
|
|
238871
240195
|
};
|
|
238872
240196
|
PostgresChannelStore = class _PostgresChannelStore {
|
|
@@ -239140,6 +240464,24 @@ var init_postgres_projects = __esm({
|
|
|
239140
240464
|
const r = await this.pool.query(`SELECT * FROM ${this.s}.projects ORDER BY created_at`);
|
|
239141
240465
|
return r.rows.map(rowToProject);
|
|
239142
240466
|
}
|
|
240467
|
+
/** 主行 + 三张子表一并删,一个事务里做完——四张表之间没有外键,分开删失败一半就留孤儿行。 */
|
|
240468
|
+
async deleteProject(id) {
|
|
240469
|
+
const client = await this.pool.connect();
|
|
240470
|
+
try {
|
|
240471
|
+
await client.query("BEGIN");
|
|
240472
|
+
await client.query(`DELETE FROM ${this.s}.project_members WHERE project_id = $1`, [id]);
|
|
240473
|
+
await client.query(`DELETE FROM ${this.s}.project_files WHERE project_id = $1`, [id]);
|
|
240474
|
+
await client.query(`DELETE FROM ${this.s}.project_content_locations WHERE project_id = $1`, [id]);
|
|
240475
|
+
await client.query(`DELETE FROM ${this.s}.projects WHERE id = $1`, [id]);
|
|
240476
|
+
await client.query("COMMIT");
|
|
240477
|
+
} catch (e) {
|
|
240478
|
+
await client.query("ROLLBACK").catch(() => {
|
|
240479
|
+
});
|
|
240480
|
+
throw e;
|
|
240481
|
+
} finally {
|
|
240482
|
+
client.release();
|
|
240483
|
+
}
|
|
240484
|
+
}
|
|
239143
240485
|
async replaceProjectMembers(projectId2, members) {
|
|
239144
240486
|
const client = await this.pool.connect();
|
|
239145
240487
|
try {
|
|
@@ -239934,8 +241276,9 @@ var init_postgres_control_plane = __esm({
|
|
|
239934
241276
|
"../storage/src/postgres-control-plane.ts"() {
|
|
239935
241277
|
"use strict";
|
|
239936
241278
|
init_esm();
|
|
241279
|
+
init_pg_ident();
|
|
239937
241280
|
ident7 = (s2) => {
|
|
239938
|
-
|
|
241281
|
+
quoteIdent2(s2);
|
|
239939
241282
|
return s2;
|
|
239940
241283
|
};
|
|
239941
241284
|
PostgresControlPlaneStore = class _PostgresControlPlaneStore {
|
|
@@ -240860,11 +242203,12 @@ var ident8, mergeContinuityRefs3, PostgresTraceStore, rowToRun, terminalHandoffO
|
|
|
240860
242203
|
var init_postgres_trace = __esm({
|
|
240861
242204
|
"../storage/src/postgres-trace.ts"() {
|
|
240862
242205
|
"use strict";
|
|
242206
|
+
init_pg_ident();
|
|
240863
242207
|
init_esm();
|
|
240864
242208
|
init_src();
|
|
240865
242209
|
init_pg_sanitize();
|
|
240866
242210
|
ident8 = (s2) => {
|
|
240867
|
-
|
|
242211
|
+
quoteIdent2(s2);
|
|
240868
242212
|
return s2;
|
|
240869
242213
|
};
|
|
240870
242214
|
mergeContinuityRefs3 = (...lists) => {
|
|
@@ -242849,10 +244193,11 @@ var init_postgres_automations = __esm({
|
|
|
242849
244193
|
"../storage/src/postgres-automations.ts"() {
|
|
242850
244194
|
"use strict";
|
|
242851
244195
|
init_esm();
|
|
244196
|
+
init_pg_ident();
|
|
242852
244197
|
init_postgres_chat_turns();
|
|
242853
244198
|
init_src();
|
|
242854
244199
|
ident10 = (s2) => {
|
|
242855
|
-
|
|
244200
|
+
quoteIdent2(s2);
|
|
242856
244201
|
return s2;
|
|
242857
244202
|
};
|
|
242858
244203
|
genId = (prefix) => `${prefix}_${Math.random().toString(36).slice(2, 12)}`;
|
|
@@ -244091,6 +245436,15 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244091
245436
|
args
|
|
244092
245437
|
);
|
|
244093
245438
|
}
|
|
245439
|
+
/** 删项目时把本公司里绑到它的会话回落成「不指定项目」。会话一条都不删,只清 project_id。 */
|
|
245440
|
+
async clearProjectBinding(projectId2) {
|
|
245441
|
+
if (!projectId2) return 0;
|
|
245442
|
+
const r = await this.pool.query(
|
|
245443
|
+
`UPDATE ${this.s}.chat_sessions SET project_id = NULL WHERE project_id = $1 AND company_id = $2`,
|
|
245444
|
+
[projectId2, this.companyId]
|
|
245445
|
+
);
|
|
245446
|
+
return r.rowCount ?? 0;
|
|
245447
|
+
}
|
|
244094
245448
|
async deleteSession(id) {
|
|
244095
245449
|
if (!await this.scopedSessionExists(id)) return;
|
|
244096
245450
|
await this.pool.query(`DELETE FROM ${this.s}.chat_messages WHERE session_id = $1`, [id]);
|
|
@@ -244100,6 +245454,64 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244100
245454
|
async appendMessage(m2, opts) {
|
|
244101
245455
|
return (await this.appendMessageOnce(m2, opts)).message;
|
|
244102
245456
|
}
|
|
245457
|
+
/**
|
|
245458
|
+
* 系统状态条的 `chat_items` 补行(ADR-0510 D3 的收口)。
|
|
245459
|
+
*
|
|
245460
|
+
* ## 为什么需要
|
|
245461
|
+
*
|
|
245462
|
+
* `role='system'` 的通告(委派回报「已完成 / 无法完成」、工单终态播报、授权卡状态条)走的是
|
|
245463
|
+
* `appendChatMessageOnce` → 本 store 直写 `chat_messages` 这条路,**不经 `ChatItemLedger`**
|
|
245464
|
+
* (`chat-system-message.ts` 文件头原话:「非 live 直写……不产生 chat_items」)。
|
|
245465
|
+
*
|
|
245466
|
+
* 在 D3 之前这没关系:`/items` 会给没有 item 行的消息合成一条快照条。D3 把历史收成
|
|
245467
|
+
* **只从 `chat_items` 出**、删掉了那条合成路之后,这些通告就从对话页上**凭空消失**了。
|
|
245468
|
+
*
|
|
245469
|
+
* 后果不只是「少看见一条通告」——委派产物的附件卡是**锚在那条回报通告之后的第一条 assistant**
|
|
245470
|
+
* 上的(ws:wo-4a2cd648 §①)。通告不在流里 ⇒ 找不到锚点 ⇒ 卡掉进 floating、渲染到末尾。
|
|
245471
|
+
* 2026-09-09 用户现场:`--continue` 续跑两个子会话,回报通告写进了 `chat_messages`
|
|
245472
|
+
* (07:29:56 / 07:30:12)但 `chat_items` 一行都没有,产物卡因此挂错位置。
|
|
245473
|
+
*
|
|
245474
|
+
* ## 为什么补在这一层
|
|
245475
|
+
*
|
|
245476
|
+
* `appendChatMessageOnce` 是所有系统消息的唯一入口,但那三个调用点(return-flow /
|
|
245477
|
+
* chat-broadcast / server.ts 授权卡)**都拿不到 `ChatItemStore`**,从上面递下来要动三份 deps 契约。
|
|
245478
|
+
* 而这里本来就握着同一个 schema 的连接池——把「用户能看见的消息必有 item 行」这条不变量
|
|
245479
|
+
* 落在存储边界上,比在三个业务处各接一次线更难漏。
|
|
245480
|
+
*
|
|
245481
|
+
* ## 失败了怎么办:吞掉
|
|
245482
|
+
*
|
|
245483
|
+
* item 写失败**不许**把消息本身带走——消息已经 INSERT 成功、幂等键已经占住,抛出去会让调用方
|
|
245484
|
+
* 当成「没写成」而重试,而重试会撞主键。所以这里只记日志。代价是那条通告在页面上仍然看不见,
|
|
245485
|
+
* 与修复前一致,不会更坏。
|
|
245486
|
+
*/
|
|
245487
|
+
async writeSystemMessageItem(m2) {
|
|
245488
|
+
try {
|
|
245489
|
+
await this.pool.query(
|
|
245490
|
+
`INSERT INTO ${this.s}.chat_items
|
|
245491
|
+
(id, session_id, message_id, turn_id, run_id, seq, kind, role, status,
|
|
245492
|
+
version, turn_version, provider_item_key, payload, metadata,
|
|
245493
|
+
created_at, updated_at, completed_at)
|
|
245494
|
+
VALUES ($1, $2, $3, $4, NULL, 1, 'text', $5, 'completed',
|
|
245495
|
+
COALESCE((SELECT MAX(version) FROM ${this.s}.chat_items WHERE session_id = $2), 0) + 1,
|
|
245496
|
+
1, NULL, $6::jsonb, '{"legacyContent": false}'::jsonb,
|
|
245497
|
+
$7::timestamptz, $7::timestamptz, $7::timestamptz)
|
|
245498
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
245499
|
+
[
|
|
245500
|
+
// 与 D3 之前 `/items` 合成条、以及 backfill 补的行**同一套身份**:同一条消息永远只有
|
|
245501
|
+
// 这一个 itemId,重复调用(幂等重写、backfill 再跑)不会造出第二条。
|
|
245502
|
+
`chat-message:${m2.id}`,
|
|
245503
|
+
m2.sessionId,
|
|
245504
|
+
m2.id,
|
|
245505
|
+
`chat-message-turn:${m2.id}`,
|
|
245506
|
+
m2.role,
|
|
245507
|
+
JSON.stringify({ role: m2.role, text: m2.content ?? "" }),
|
|
245508
|
+
m2.createdAt
|
|
245509
|
+
]
|
|
245510
|
+
);
|
|
245511
|
+
} catch (err) {
|
|
245512
|
+
console.warn(`[chat-items] \u7CFB\u7EDF\u72B6\u6001\u6761\u8865\u884C\u5931\u8D25\uFF08message=${m2.id} session=${m2.sessionId}\uFF09: ${String(err)}`);
|
|
245513
|
+
}
|
|
245514
|
+
}
|
|
244103
245515
|
async appendMessageOnce(m2, opts) {
|
|
244104
245516
|
await this.assertSessionInScope(m2.sessionId);
|
|
244105
245517
|
const insert = async () => {
|
|
@@ -244116,7 +245528,9 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244116
245528
|
};
|
|
244117
245529
|
for (let attempt = 0; ; attempt++) {
|
|
244118
245530
|
try {
|
|
244119
|
-
|
|
245531
|
+
const written = { message: { ...m2, seq: await insert() }, inserted: true };
|
|
245532
|
+
if ((opts?.source ?? "live") === "system") await this.writeSystemMessageItem(written.message);
|
|
245533
|
+
return written;
|
|
244120
245534
|
} catch (err) {
|
|
244121
245535
|
const isUniqueViolation5 = err && typeof err === "object" && err.code === "23505";
|
|
244122
245536
|
const constraint = String(err.constraint ?? "");
|
|
@@ -244768,11 +246182,17 @@ var init_postgres_chat_sessions = __esm({
|
|
|
244768
246182
|
model: row.model ?? null
|
|
244769
246183
|
});
|
|
244770
246184
|
rowToSessionWithQuality = (row) => {
|
|
246185
|
+
const status = row.last_assistant_status ?? void 0;
|
|
244771
246186
|
const quality = deriveLastTurnQuality({
|
|
244772
|
-
status
|
|
246187
|
+
status,
|
|
244773
246188
|
content: row.last_assistant_content ?? ""
|
|
244774
246189
|
});
|
|
244775
|
-
|
|
246190
|
+
const running = deriveSessionRunning({ status });
|
|
246191
|
+
return {
|
|
246192
|
+
...rowToSession(row),
|
|
246193
|
+
...quality ? { lastTurnQuality: quality } : {},
|
|
246194
|
+
...running ? { running: true } : {}
|
|
246195
|
+
};
|
|
244776
246196
|
};
|
|
244777
246197
|
rowToMessage = (row) => ({
|
|
244778
246198
|
id: row.id,
|
|
@@ -244914,12 +246334,15 @@ var init_postgres_chat_items = __esm({
|
|
|
244914
246334
|
const r = await this.pool.query(
|
|
244915
246335
|
`INSERT INTO ${this.s}.chat_items
|
|
244916
246336
|
(id, session_id, message_id, turn_id, run_id, seq, kind, role, status,
|
|
244917
|
-
version, turn_version, provider_item_key, payload, metadata, created_at, updated_at, completed_at)
|
|
246337
|
+
version, turn_version, provider_item_key, payload, metadata, created_at, updated_at, ord, completed_at)
|
|
244918
246338
|
SELECT $1, $2, $3, $4, $5,
|
|
244919
246339
|
COALESCE(MAX(c.seq), 0) + 1,
|
|
244920
|
-
$6, $7, $8::text, 0,
|
|
246340
|
+
$6, $7, $8::text, COALESCE($13::bigint, 0),
|
|
244921
246341
|
COALESCE(MAX(c.turn_version), 0) + 1,
|
|
244922
246342
|
$9, $10::jsonb, $11::jsonb, $12::timestamptz, $12::timestamptz,
|
|
246343
|
+
-- ord\uFF1A\u8D26\u672C\u9884\u6D3E\u4E86\u5C31\u7528\u5B83\uFF08\u5EFA\u884C\u5373\u6709\u53F7\u3001\u7B2C\u4E00\u5E27\u5C31\u80FD\u5E26\u4E0A wire\uFF09\uFF1B\u6CA1\u7ED9\u624D\u8D70\u5217 DEFAULT
|
|
246344
|
+
-- \u7684 nextval\u2014\u2014\u4E24\u6761\u8DEF\u53D6\u7684\u662F\u540C\u4E00\u6761\u8868\u7EA7\u5E8F\u5217\uFF0C\u8BED\u4E49\u76F8\u540C\uFF0C\u53EA\u5DEE\u62FF\u53F7\u53D1\u751F\u5728\u54EA\u4E00\u4FA7\u3002
|
|
246345
|
+
COALESCE($14::bigint, nextval('${this.s}.chat_items_ord_seq'::regclass)),
|
|
244923
246346
|
-- $12/$8 \u4E09\u5904\u90FD\u663E\u5F0F\u8F6C\u578B\uFF1A\u4E0D\u8F6C\u7684\u8BDD PG \u4F1A\u5728\u540C\u4E00\u6761\u8BED\u53E5\u91CC\u7ED9\u540C\u4E00\u4E2A\u53C2\u6570\u63A8\u51FA\u4E0D\u540C\u7C7B\u578B
|
|
244924
246347
|
-- \uFF08\u5217\u4F4D\u662F timestamptz/text\u3001CASE \u91CC\u662F unknown\uFF09\uFF0C\u76F4\u63A5\u62A5
|
|
244925
246348
|
-- inconsistent types deduced for parameter $12\u3002
|
|
@@ -244939,7 +246362,9 @@ var init_postgres_chat_items = __esm({
|
|
|
244939
246362
|
scrubPgString(input.providerItemKey ?? null),
|
|
244940
246363
|
payload,
|
|
244941
246364
|
metadata,
|
|
244942
|
-
at
|
|
246365
|
+
at,
|
|
246366
|
+
input.version ?? null,
|
|
246367
|
+
input.ord ?? null
|
|
244943
246368
|
]
|
|
244944
246369
|
);
|
|
244945
246370
|
return rowToItem(r.rows[0]);
|
|
@@ -244977,10 +246402,13 @@ var init_postgres_chat_items = __esm({
|
|
|
244977
246402
|
setExpr = `status = $3::text, completed_at = CASE WHEN $3 IN ('completed','failed','interrupted') THEN $2::timestamptz ELSE c.completed_at END`;
|
|
244978
246403
|
break;
|
|
244979
246404
|
}
|
|
246405
|
+
const versionParamIndex = args.length + 1;
|
|
246406
|
+
args.push(opts?.version ?? null);
|
|
244980
246407
|
const r = await this.pool.query(
|
|
244981
246408
|
`UPDATE ${this.s}.chat_items c
|
|
244982
246409
|
SET ${setExpr},
|
|
244983
|
-
version = c.version + 1
|
|
246410
|
+
version = CASE WHEN $${versionParamIndex}::bigint IS NULL THEN c.version + 1
|
|
246411
|
+
ELSE GREATEST(c.version, $${versionParamIndex}::bigint) END,
|
|
244984
246412
|
turn_version = ${bumpTurnVersion},
|
|
244985
246413
|
updated_at = $2
|
|
244986
246414
|
WHERE c.id = $1
|
|
@@ -245028,6 +246456,49 @@ var init_postgres_chat_items = __esm({
|
|
|
245028
246456
|
);
|
|
245029
246457
|
return r.rows.map((row) => rowToItem(row));
|
|
245030
246458
|
}
|
|
246459
|
+
/**
|
|
246460
|
+
* 一条会话的 item,按展示序升序(ADR-0510 D3:历史的唯一出口)。
|
|
246461
|
+
*
|
|
246462
|
+
* 排序键 `(ord IS NULL) DESC, ord DESC` 的意思是「取最新的一段」:带号的行按号从大到小、
|
|
246463
|
+
* 没号的存量行排在最后(= 最老)。截断之后再翻回升序返回。
|
|
246464
|
+
* 走索引 `chat_items_session_ord_idx (session_id, ord)`。
|
|
246465
|
+
*/
|
|
246466
|
+
async listSessionItems(sessionId, opts) {
|
|
246467
|
+
const limit = Math.max(1, Math.min(opts?.limit ?? 500, 2e3));
|
|
246468
|
+
const before = opts?.beforeOrd;
|
|
246469
|
+
const r = before === void 0 ? await this.pool.query(
|
|
246470
|
+
`SELECT * FROM ${this.s}.chat_items WHERE session_id = $1
|
|
246471
|
+
ORDER BY (ord IS NULL) ASC, ord DESC, seq DESC LIMIT $2`,
|
|
246472
|
+
[sessionId, limit]
|
|
246473
|
+
) : await this.pool.query(
|
|
246474
|
+
`SELECT * FROM ${this.s}.chat_items WHERE session_id = $1 AND ord IS NOT NULL AND ord < $2
|
|
246475
|
+
ORDER BY ord DESC LIMIT $3`,
|
|
246476
|
+
[sessionId, before, limit]
|
|
246477
|
+
);
|
|
246478
|
+
const rows = r.rows.map((row) => rowToItem(row));
|
|
246479
|
+
rows.reverse();
|
|
246480
|
+
return rows.sort((a, b2) => {
|
|
246481
|
+
if (a.ord === null && b2.ord === null) return a.seq - b2.seq;
|
|
246482
|
+
if (a.ord === null) return -1;
|
|
246483
|
+
if (b2.ord === null) return 1;
|
|
246484
|
+
return a.ord - b2.ord;
|
|
246485
|
+
});
|
|
246486
|
+
}
|
|
246487
|
+
async sessionVersionCursor(sessionId) {
|
|
246488
|
+
const r = await this.pool.query(
|
|
246489
|
+
`SELECT COALESCE(MAX(version), 0) AS v FROM ${this.s}.chat_items WHERE session_id = $1`,
|
|
246490
|
+
[sessionId]
|
|
246491
|
+
);
|
|
246492
|
+
return Number(r.rows[0]?.v ?? 0);
|
|
246493
|
+
}
|
|
246494
|
+
async reserveOrds(count2) {
|
|
246495
|
+
const n = Math.max(1, Math.min(Math.floor(count2), 1e3));
|
|
246496
|
+
const r = await this.pool.query(
|
|
246497
|
+
`SELECT nextval('${this.s}.chat_items_ord_seq'::regclass) AS ord FROM generate_series(1, $1)`,
|
|
246498
|
+
[n]
|
|
246499
|
+
);
|
|
246500
|
+
return r.rows.map((row) => Number(row.ord));
|
|
246501
|
+
}
|
|
245031
246502
|
async turnCursor(sessionId, turnId) {
|
|
245032
246503
|
const r = await this.pool.query(
|
|
245033
246504
|
`SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq, COALESCE(MAX(turn_version), 0) + 1 AS next_turn_version
|
|
@@ -245364,8 +246835,14 @@ function planSessionOrd(sessionId, messages, items, turns, opts) {
|
|
|
245364
246835
|
}
|
|
245365
246836
|
async function listChatItemsSchemas(pool) {
|
|
245366
246837
|
const r = await pool.query(
|
|
245367
|
-
`SELECT n.nspname AS s
|
|
245368
|
-
|
|
246838
|
+
`SELECT n.nspname AS s
|
|
246839
|
+
FROM pg_class c
|
|
246840
|
+
JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
246841
|
+
WHERE c.relname = 'chat_items' AND c.relkind = 'r'
|
|
246842
|
+
AND (SELECT count(*) FROM information_schema.columns col
|
|
246843
|
+
WHERE col.table_schema = n.nspname AND col.table_name = 'chat_items'
|
|
246844
|
+
AND col.column_name IN ('session_id', 'turn_id', 'seq', 'kind', 'payload')) = 5
|
|
246845
|
+
ORDER BY 1`
|
|
245369
246846
|
);
|
|
245370
246847
|
return r.rows.map((x2) => x2.s);
|
|
245371
246848
|
}
|
|
@@ -245959,9 +247436,10 @@ var init_postgres_nodes = __esm({
|
|
|
245959
247436
|
"../storage/src/postgres-nodes.ts"() {
|
|
245960
247437
|
"use strict";
|
|
245961
247438
|
import_node_crypto68 = require("node:crypto");
|
|
247439
|
+
init_pg_ident();
|
|
245962
247440
|
init_esm();
|
|
245963
247441
|
ident12 = (s2) => {
|
|
245964
|
-
|
|
247442
|
+
quoteIdent2(s2);
|
|
245965
247443
|
return s2;
|
|
245966
247444
|
};
|
|
245967
247445
|
PostgresNodeStore = class _PostgresNodeStore {
|
|
@@ -246226,8 +247704,9 @@ var init_postgres_model_prices = __esm({
|
|
|
246226
247704
|
"../storage/src/postgres-model-prices.ts"() {
|
|
246227
247705
|
"use strict";
|
|
246228
247706
|
init_esm();
|
|
247707
|
+
init_pg_ident();
|
|
246229
247708
|
ident13 = (s2) => {
|
|
246230
|
-
|
|
247709
|
+
quoteIdent2(s2);
|
|
246231
247710
|
return s2;
|
|
246232
247711
|
};
|
|
246233
247712
|
PostgresModelPriceStore = class _PostgresModelPriceStore {
|
|
@@ -246437,10 +247916,11 @@ var init_postgres_actor_memory = __esm({
|
|
|
246437
247916
|
"../storage/src/postgres-actor-memory.ts"() {
|
|
246438
247917
|
"use strict";
|
|
246439
247918
|
import_node_crypto69 = require("node:crypto");
|
|
247919
|
+
init_pg_ident();
|
|
246440
247920
|
init_src();
|
|
246441
247921
|
matchClause = (q2) => q2.requireMatch && q2.keywords.length > 0 ? " WHERE m > 0" : "";
|
|
246442
247922
|
ident14 = (s2) => {
|
|
246443
|
-
|
|
247923
|
+
quoteIdent2(s2);
|
|
246444
247924
|
return s2;
|
|
246445
247925
|
};
|
|
246446
247926
|
PostgresActorMemoryStore = class _PostgresActorMemoryStore {
|
|
@@ -246746,9 +248226,10 @@ var init_postgres_inbox_read = __esm({
|
|
|
246746
248226
|
"../storage/src/postgres-inbox-read.ts"() {
|
|
246747
248227
|
"use strict";
|
|
246748
248228
|
init_esm();
|
|
248229
|
+
init_pg_ident();
|
|
246749
248230
|
SEED_MARKER_ACTOR = "actor:system";
|
|
246750
248231
|
ident15 = (s2) => {
|
|
246751
|
-
|
|
248232
|
+
quoteIdent2(s2);
|
|
246752
248233
|
return s2;
|
|
246753
248234
|
};
|
|
246754
248235
|
PostgresReadMarkerStore = class _PostgresReadMarkerStore {
|
|
@@ -252000,10 +253481,11 @@ var init_postgres_knowledge = __esm({
|
|
|
252000
253481
|
"../storage/src/postgres-knowledge.ts"() {
|
|
252001
253482
|
"use strict";
|
|
252002
253483
|
init_esm();
|
|
253484
|
+
init_pg_ident();
|
|
252003
253485
|
init_pg_sanitize();
|
|
252004
253486
|
init_knowledge_snapshot_crypto();
|
|
252005
253487
|
ident17 = (value2) => {
|
|
252006
|
-
|
|
253488
|
+
quoteIdent2(value2);
|
|
252007
253489
|
return value2;
|
|
252008
253490
|
};
|
|
252009
253491
|
TABLES = [
|
|
@@ -255625,6 +257107,36 @@ async function startServe(opts) {
|
|
|
255625
257107
|
projectId2,
|
|
255626
257108
|
afterName
|
|
255627
257109
|
);
|
|
257110
|
+
},
|
|
257111
|
+
/**
|
|
257112
|
+
* 删项目后的跨域清理(发起人 2026-09-09 拍板的口径里,projects 域自己管不到的那两件):
|
|
257113
|
+
* ① 绑到该项目的会话 `project_id` 置空 —— 会话一条都不删,回到「不指定项目」。不清的话
|
|
257114
|
+
* 项目 chip 会按既定口径显示一串裸项目 id(`pages/chat/FRAME-DATA-MAP.md`)。
|
|
257115
|
+
* ② 该项目作用域下的变量/密钥删掉 —— 项目没了,`resolveProjectVariables` 再也解析不到它们,
|
|
257116
|
+
* 留着只是一堆谁也读不到的明文凭据。
|
|
257117
|
+
* 与改名钩子同款:`chatStoreFor` / `engineRouter` 的 const 都在本域构造之后,所以只能在
|
|
257118
|
+
* handler 体内(删除发生时)解析,不能在工厂里提前引用——那会撞 TDZ。
|
|
257119
|
+
*/
|
|
257120
|
+
onProjectDeletedFor: (companyId) => async ({ projectId: projectId2 }) => {
|
|
257121
|
+
const failures = [];
|
|
257122
|
+
try {
|
|
257123
|
+
const sessions = await chatStoreFor(companyId);
|
|
257124
|
+
if (sessions.clearProjectBinding) await sessions.clearProjectBinding(projectId2);
|
|
257125
|
+
else failures.push("\u4F1A\u8BDD store \u4E0D\u652F\u6301 clearProjectBinding\uFF0C\u4F1A\u8BDD\u7684\u9879\u76EE\u7ED1\u5B9A\u672A\u6E05");
|
|
257126
|
+
} catch (e) {
|
|
257127
|
+
failures.push(`\u6E05\u4F1A\u8BDD\u9879\u76EE\u7ED1\u5B9A\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
257128
|
+
}
|
|
257129
|
+
try {
|
|
257130
|
+
const registry2 = companyId && pgPool ? (await engineRouter.getEngine(companyId)).registry : registryStore;
|
|
257131
|
+
if (registry2.resolveProjectVariables) {
|
|
257132
|
+
for (const row of await registry2.resolveProjectVariables(projectId2)) {
|
|
257133
|
+
await registry2.deleteVariable(row.key, void 0, { projectId: projectId2 });
|
|
257134
|
+
}
|
|
257135
|
+
}
|
|
257136
|
+
} catch (e) {
|
|
257137
|
+
failures.push(`\u6E05\u9879\u76EE\u53D8\u91CF\u5931\u8D25\uFF1A${e instanceof Error ? e.message : String(e)}`);
|
|
257138
|
+
}
|
|
257139
|
+
if (failures.length) throw new Error(failures.join("\uFF1B"));
|
|
255628
257140
|
}
|
|
255629
257141
|
});
|
|
255630
257142
|
const currentArtifactProjection = async () => {
|
|
@@ -256611,6 +258123,16 @@ async function startServe(opts) {
|
|
|
256611
258123
|
if (!stores.artifacts) throw new Error(`[work-order-files] \u516C\u53F8 ${companyId} \u6CA1\u6709 artifacts store\uFF0C\u62D2\u7EDD\u56DE\u843D\u9ED8\u8BA4\u516C\u53F8`);
|
|
256612
258124
|
return stores.artifacts;
|
|
256613
258125
|
};
|
|
258126
|
+
const projectStateFor = async (companyId) => {
|
|
258127
|
+
if (!chatStoreRouter) return projectStateStore;
|
|
258128
|
+
const stores = await chatStoreRouter.open(companyId, "work-order-projects");
|
|
258129
|
+
if (!stores.projects) throw new Error(`[work-order-projects] \u516C\u53F8 ${companyId} \u6CA1\u6709 projects store\uFF0C\u62D2\u7EDD\u56DE\u843D\u9ED8\u8BA4\u516C\u53F8`);
|
|
258130
|
+
return stores.projects;
|
|
258131
|
+
};
|
|
258132
|
+
const resolveCompanyCodeRepo = makeCompanyCodeRepoResolver(async (companyId) => ({
|
|
258133
|
+
projects: await projectStateFor(companyId),
|
|
258134
|
+
bindings: await artifactStateFor(companyId)
|
|
258135
|
+
}));
|
|
256614
258136
|
if (pgDsn && pgPool && chatStoreRouter) {
|
|
256615
258137
|
const router = chatStoreRouter;
|
|
256616
258138
|
void (async () => {
|
|
@@ -256799,7 +258321,6 @@ async function startServe(opts) {
|
|
|
256799
258321
|
}
|
|
256800
258322
|
return token;
|
|
256801
258323
|
};
|
|
256802
|
-
let resolveCodeRepoRef;
|
|
256803
258324
|
let coordinatorRef = null;
|
|
256804
258325
|
let coordinatorSpawnProbe = null;
|
|
256805
258326
|
const dispatchTimers = /* @__PURE__ */ new Set();
|
|
@@ -256959,9 +258480,9 @@ async function startServe(opts) {
|
|
|
256959
258480
|
port: opts.port ?? 7320,
|
|
256960
258481
|
projectState: projectStateStore,
|
|
256961
258482
|
artifactState: artifactStateStore,
|
|
256962
|
-
//
|
|
258483
|
+
// 建单的项目校验、绑定与输入文件按当前公司落库;与项目/任务读侧使用同一个 router。
|
|
256963
258484
|
// 装了路由(有 PG)才给——dev 文件模式没有 schema 概念,沿用上面那个单实例。
|
|
256964
|
-
...chatStoreRouter ? { artifactStateFor } : {},
|
|
258485
|
+
...chatStoreRouter ? { artifactStateFor, projectStateFor } : {},
|
|
256965
258486
|
schema,
|
|
256966
258487
|
registry: registryStore,
|
|
256967
258488
|
// 会话级模型覆盖的候选清单:与 `GET /api/runtimes/:id/models` **同一份**实现
|
|
@@ -257031,7 +258552,7 @@ async function startServe(opts) {
|
|
|
257031
258552
|
});
|
|
257032
258553
|
return { plannerActor: planner.id, text: chunks.join("") };
|
|
257033
258554
|
},
|
|
257034
|
-
resolveCodeRepo:
|
|
258555
|
+
resolveCodeRepo: resolveCompanyCodeRepo,
|
|
257035
258556
|
// propose sha 校验兼镜像根:verifyExternalPinSha 在 ls-remote 未命中(祖先提交)时 `git fetch <remote> <sha>`
|
|
257036
258557
|
// 进此目录留档(校验即留档,见 git/collect.ts)。目录按需 `init --bare`。(历史:曾是合并器/CI 闸的 git-mirrors 根,已删。)
|
|
257037
258558
|
gitMirrorRoot: path29.join(opts.dir, "propose-mirrors"),
|
|
@@ -257108,8 +258629,8 @@ async function startServe(opts) {
|
|
|
257108
258629
|
collabDomain({
|
|
257109
258630
|
kernel: defaultCompanyKernel,
|
|
257110
258631
|
oplog,
|
|
257111
|
-
blobs,
|
|
257112
|
-
//
|
|
258632
|
+
blobs: engine.blobs,
|
|
258633
|
+
// 页头正文读写与内容接口同源:默认公司的事实柜。
|
|
257113
258634
|
// 资产柜:POST /api/workorders/:id/files 上传字节落这里(与 uploadChatAttachment 同一柜),
|
|
257114
258635
|
// 再让 artifacts.addWorkOrderFile 写行——手动上传不进 oplog / events,删了不伤重放。
|
|
257115
258636
|
assets,
|
|
@@ -259128,11 +260649,10 @@ async function startServe(opts) {
|
|
|
259128
260649
|
for (const w2 of projectsCfg?.workspaces ?? []) {
|
|
259129
260650
|
await artifactStateStore.upsertWorkspaceBinding({ workOrderId: w2.id, projectId: w2.projectId });
|
|
259130
260651
|
}
|
|
259131
|
-
const {
|
|
260652
|
+
const { resolveProject } = makeStoreCodeRepoResolver({
|
|
259132
260653
|
projects: projectStateStore,
|
|
259133
260654
|
bindings: artifactStateStore
|
|
259134
260655
|
});
|
|
259135
|
-
resolveCodeRepoRef = resolveCodeRepo;
|
|
259136
260656
|
if (opts.dispatch) {
|
|
259137
260657
|
const startDispatcher = (companyId, companyEngine) => {
|
|
259138
260658
|
if (dispatchers.has(companyId)) return;
|
|
@@ -259408,8 +260928,7 @@ async function startServe(opts) {
|
|
|
259408
260928
|
} : {},
|
|
259409
260929
|
schema: liveSchemaMap,
|
|
259410
260930
|
// 活视图:类型目录变更后由 rebuildLiveSchema 原地重填(Dispatcher 每轮现读 opts.schema)
|
|
259411
|
-
resolveCodeRepo,
|
|
259412
|
-
// §4/C:异步从权威源(store)解析;dispatcher 装配前 await
|
|
260931
|
+
resolveCodeRepo: (ws) => resolveCompanyCodeRepo(ws, companyId),
|
|
259413
260932
|
// #2 派单开关:工单"待派发"(控制面旁存标 held)时不派其活。held 集通常空、表极小,每次查可接受。
|
|
259414
260933
|
resolveDispatchHold: async (ws) => (await artifactStateStore.listWorkspaceDispatchHeld()).includes(ws),
|
|
259415
260934
|
// §D3 评审人连续性:锁定审核人不可用(熔断/离岗)→ 系统身份幂等升级,交管理者改派(不静默换人)。
|
|
@@ -260631,7 +262150,7 @@ ${nodeFault}` : "");
|
|
|
260631
262150
|
log: (m2) => console.log(m2),
|
|
260632
262151
|
batchLimit: envNum("OASIS_AUTOMATION_EVENT_BATCH", 200),
|
|
260633
262152
|
// 工单→项目绑定是合法薄旁存(B5),不是内核事实——列项目下的工单只认它。
|
|
260634
|
-
listProjectWorkspaces: async ({ projectId: projectId2 }) => (await
|
|
262153
|
+
listProjectWorkspaces: async ({ companyId, projectId: projectId2 }) => (await (await artifactStateFor(companyId)).listWorkspaceBindings()).filter((b2) => b2.projectId === projectId2).map((b2) => b2.workOrderId),
|
|
260635
262154
|
// 完成判据必须复用 D3 resolver(不复制 deriveStage),且与成员快照同一次读取。
|
|
260636
262155
|
// ensureTracked:内核跟踪集只在启动时装载一次、此后只随本进程写入增长——多实例部署下,
|
|
260637
262156
|
// 另一实例建出来的工单在这里会被读成 unknown(既不触发也不留痕,QA OBS-1 实测)。
|
|
@@ -260642,7 +262161,7 @@ ${nodeFault}` : "");
|
|
|
260642
262161
|
return makeAutomationWorkorderSnapshotResolver(engine2.kernel)(workspace);
|
|
260643
262162
|
},
|
|
260644
262163
|
// 触发时复查归因主体的项目读取权(与配置期同一口径)。
|
|
260645
|
-
projectReadable: async ({ projectId: projectId2 }) => await
|
|
262164
|
+
projectReadable: async ({ companyId, projectId: projectId2 }) => await (await projectStateFor(companyId)).getProject(projectId2) !== null
|
|
260646
262165
|
});
|
|
260647
262166
|
automationEventTimer = setInterval(() => {
|
|
260648
262167
|
void eventPoller.tick().catch((err) => console.error(`[project-event-poller] tick \u5931\u8D25: ${String(err)}`));
|
|
@@ -262581,7 +264100,7 @@ var COMMAND_DECLS = {
|
|
|
262581
264100
|
{ name: "as", desc: "\u7ED9\u8FD9\u6B21\u59D4\u6D3E\u8D77\u4E2A\u77ED\u540D\uFF0C\u4E4B\u540E\u7528\u5B83 --continue\uFF1B\u649E\u540D\u670D\u52A1\u7AEF\u81EA\u52A8\u52A0\u540E\u7F00\uFF0C**\u4EE5\u8F93\u51FA\u91CC\u90A3\u4E2A\u4E3A\u51C6**" },
|
|
262582
264101
|
{ name: "file", desc: "\u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF08\u4F60\u5DE5\u4F5C\u76EE\u5F55\u4E0B\u7684\u76F8\u5BF9\u8DEF\u5F84\uFF0C\u53EF\u7ED9\u591A\u6B21\uFF1B\u5355\u4E2A \u22642MB\u3001\u4E00\u6B21 \u226420 \u4E2A / 20MB\uFF09" },
|
|
262583
264102
|
{ name: "continue", desc: "\u5F80\u5DF2\u6709\u59D4\u6D3E\u91CC\u518D\u8F6C\u8FBE\u4E00\u53E5\uFF1A\u7ED9 label / \u4E13\u5BB6\u540D / delegationId \u524D\u7F00" },
|
|
262584
|
-
{ name: "text", desc: "\u8F6C\u8FBE\u5185\u5BB9\uFF08\u914D --continue\uFF09\u3002\u6539\u65B9\u5411\u5C31\u660E\u8BF4\u300C\u6539\u65B9\u5411\u300D\uFF0C\u4E13\u5BB6\u4F1A\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u653E\u5F03\u624B\u4E0A\u90A3\u6BB5" },
|
|
264103
|
+
{ name: "text", desc: "\u8F6C\u8FBE\u5185\u5BB9\uFF08\u914D --continue\uFF09\u3002\u6539\u65B9\u5411\u5C31\u660E\u8BF4\u300C\u6539\u65B9\u5411\u300D\uFF0C\u4E13\u5BB6\u4F1A\u81EA\u5DF1\u5224\u65AD\u8981\u4E0D\u8981\u653E\u5F03\u624B\u4E0A\u90A3\u6BB5\u3002**\u770B\u56DE\u6267**\uFF1A\u63D2\u8FDB\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E / \u8D77\u4E86\u65B0\u7684\u4E00\u8F6E / \u8FD8\u6392\u5728\u961F\u5217\u91CC\u2014\u2014\u6392\u961F\u65F6\u4E13\u5BB6\u8981\u7B49\u672C\u8F6E\u8DD1\u5B8C\u624D\u770B\u89C1\uFF0C\u8BE5\u8DDF\u7528\u6237\u8BF4\u4E00\u58F0" },
|
|
262585
264104
|
{ name: "status", desc: "\u770B\u4E00\u4E2A\u59D4\u6D3E\u7684\u8BE6\u60C5\uFF1A\u5168\u91CF\u5404\u8F6E\u5C0F\u7ED3 + \u4EA7\u7269\u6E05\u5355" },
|
|
262586
264105
|
{ name: "list", desc: "\u5217\u51FA\u8FD9\u6761\u5BF9\u8BDD\u6D3E\u51FA\u53BB\u7684\u6D3B\uFF1B\u9ED8\u8BA4\u53EA\u7ED9\u8FDB\u884C\u4E2D + \u6700\u8FD1 10 \u6761", boolean: true },
|
|
262587
264106
|
{ name: "all", desc: "\u914D --list\uFF1A\u8FDE\u5386\u53F2\u59D4\u6D3E\u4E00\u8D77\u5217", boolean: true },
|
|
@@ -265067,15 +266586,26 @@ ${round.text}`);
|
|
|
265067
266586
|
const text5 = flags.get("text");
|
|
265068
266587
|
if (!text5) throw new Error('--continue \u8981\u914D --text "<\u8981\u8F6C\u8FBE\u7684\u8BDD>"');
|
|
265069
266588
|
const target = await resolveRef2(continueRef);
|
|
265070
|
-
await api.post(
|
|
266589
|
+
const outcome = await api.post(
|
|
265071
266590
|
`/api/delegations/${encodeURIComponent(target.id)}/messages`,
|
|
265072
266591
|
{ text: text5, ...fileArgs.length ? { files: fileArgs } : {} }
|
|
265073
266592
|
);
|
|
265074
266593
|
if (asJson2) {
|
|
265075
|
-
println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs }, null, 2));
|
|
266594
|
+
println(JSON.stringify({ delegationId: target.id, label: target.label, files: fileArgs, ...outcome }, null, 2));
|
|
265076
266595
|
break;
|
|
265077
266596
|
}
|
|
265078
|
-
|
|
266597
|
+
const who = `${target.expertName ?? target.expertActorId}\uFF08${target.label ?? target.id.slice(0, 8)}\uFF09`;
|
|
266598
|
+
if (outcome?.outcome === "inserted") {
|
|
266599
|
+
println(`\u5DF2\u63D2\u8FDB ${who} \u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E\u2014\u2014\u5B83\u628A\u5F53\u524D\u8FD9\u4E00\u6B65\u7B54\u5B8C\u5C31\u4F1A\u770B\u5230\u3002`);
|
|
266600
|
+
} else if (outcome?.outcome === "started-round") {
|
|
266601
|
+
println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\uFF1A\u5B83\u4E0A\u4E00\u8F6E\u5DF2\u7ECF\u7ED3\u675F\uFF0C\u8FD9\u53E5\u8BDD\u5F53\u573A\u8D77\u4E86\u65B0\u7684\u4E00\u8F6E\u3002`);
|
|
266602
|
+
} else if (outcome?.outcome === "queued") {
|
|
266603
|
+
println(`\u5DF2\u6536\u4E0B\uFF0C\u4F46${who}**\u73B0\u5728\u8FD8\u770B\u4E0D\u5230**\uFF1A${outcome.reason ?? "\u63D2\u4E0D\u8FDB\u6B63\u5728\u8DD1\u7684\u90A3\u4E00\u8F6E"}\u3002`);
|
|
266604
|
+
println(` \u8FD9\u53E5\u8BDD\u6392\u5728\u5F85\u8F6C\u8FBE\u961F\u5217\u91CC\uFF08\u8FD8\u6709 ${outcome.pendingInputs} \u53E5\u6CA1\u9001\u5230\uFF09\uFF0C\u672C\u8F6E\u8DD1\u5B8C\u4F1A\u4F5C\u4E3A\u4E0B\u4E00\u8F6E\u53D1\u51FA\u3002`);
|
|
266605
|
+
println(" \u5B83\u8FD9\u4E00\u8F6E\u53EF\u80FD\u8FD8\u5728\u6309\u65E7\u65B9\u5411\u505A\u2014\u2014\u8981\u4E0D\u8981\u5728\u8FD9\u6761\u5BF9\u8BDD\u91CC\u5148\u8DDF\u7528\u6237\u8BF4\u660E\uFF0C\u4F60\u81EA\u5DF1\u5224\u65AD\u3002");
|
|
266606
|
+
} else {
|
|
266607
|
+
println(`\u5DF2\u8F6C\u8FBE\u7ED9 ${who}\u3002`);
|
|
266608
|
+
}
|
|
265079
266609
|
if (fileArgs.length) println(` \u4E00\u8D77\u5E26\u8FC7\u53BB\u7684\u6587\u4EF6\uFF1A${fileArgs.join("\u3001")}`);
|
|
265080
266610
|
println("\u8FD9\u4E00\u8F6E\u5230\u6B64\u4E3A\u6B62\uFF1A\u7ED3\u679C\u4F1A\u81EA\u5DF1\u56DE\u5230\u8FD9\u6761\u5BF9\u8BDD\uFF0C\u4E0D\u8981\u5728\u8FD9\u91CC\u7B49\u3002");
|
|
265081
266611
|
break;
|
|
@@ -267871,7 +269401,7 @@ function shimScript() {
|
|
|
267871
269401
|
}
|
|
267872
269402
|
|
|
267873
269403
|
// src/index.ts
|
|
267874
|
-
var PKG_VERSION = true ? "2.2.
|
|
269404
|
+
var PKG_VERSION = true ? "2.2.3" : "dev";
|
|
267875
269405
|
var LOCAL_BIN = localBin();
|
|
267876
269406
|
var NPM_PREFIX = npmPrefix();
|
|
267877
269407
|
var INSTANCE = DEFAULT_INSTANCE;
|