oasis_test_v2 2.2.10 → 2.2.12
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 +777 -373
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -4666,8 +4666,6 @@ var init_kill = __esm({
|
|
|
4666
4666
|
function scan(state, ctx) {
|
|
4667
4667
|
if (state.workorder.dispatchPaused) return { events: [], terminated: false };
|
|
4668
4668
|
const events = [];
|
|
4669
|
-
const now = ctx.at;
|
|
4670
|
-
const policy = ctx.policy ?? CODE_DEFAULT_POLICY;
|
|
4671
4669
|
for (const node2 of state.liveNodes()) {
|
|
4672
4670
|
const lw = node2.latestWorkId ? state.work(node2.latestWorkId) : null;
|
|
4673
4671
|
if (!lw) {
|
|
@@ -4701,12 +4699,7 @@ function scan(state, ctx) {
|
|
|
4701
4699
|
continue;
|
|
4702
4700
|
}
|
|
4703
4701
|
if (wstate === "running") {
|
|
4704
|
-
|
|
4705
|
-
const elapsed = new Date(now).getTime() - new Date(start).getTime();
|
|
4706
|
-
const wcell = policy.resolve({ executor: classify(node2.assigneeActorId), scenario: "work" });
|
|
4707
|
-
if (wcell.timeoutAction.kind === "kill-rebuild" && elapsed > wcell.deadlineMs) {
|
|
4708
|
-
events.push({ kind: "work.timeout", workorderId: state.workorder.id, workId: lw.id });
|
|
4709
|
-
}
|
|
4702
|
+
checkRunningWorkTimeout(state, node2, lw, ctx, events);
|
|
4710
4703
|
continue;
|
|
4711
4704
|
}
|
|
4712
4705
|
const blockers = scanBlockersForNode(state, node2, lw);
|
|
@@ -4771,10 +4764,9 @@ function scan(state, ctx) {
|
|
|
4771
4764
|
workorderId: state.workorder.id,
|
|
4772
4765
|
nodeId: node2.id,
|
|
4773
4766
|
// ★ 合并写:保留 source ∈ {type, manual, model} 的原有行,覆写 source='closure' 的行。
|
|
4774
|
-
// `plan.update_review_requirements`
|
|
4767
|
+
// `plan.update_review_requirements` 的名单仍是**全量替换**,
|
|
4775
4768
|
// 所以必须把手动 / 类型 / 模型来源的行一并带上——漏一条即误删协调者手动指派的审核人。
|
|
4776
|
-
//
|
|
4777
|
-
// or 只含 closure 之外的组),本轮内 kill 无影响。
|
|
4769
|
+
// 总线为新事件写 preserveReviews:未变组的当前审核继续,只有新增 closure 组待派。
|
|
4778
4770
|
reviewers: mergedClosureRequirements(state, node2.id, closureActors)
|
|
4779
4771
|
});
|
|
4780
4772
|
continue;
|
|
@@ -4877,6 +4869,15 @@ function scan(state, ctx) {
|
|
|
4877
4869
|
const terminated = events.length === 0 && checkTerminated(state);
|
|
4878
4870
|
return { events, terminated };
|
|
4879
4871
|
}
|
|
4872
|
+
function checkRunningWorkTimeout(state, node2, work, ctx, events) {
|
|
4873
|
+
const start = work.lastActivityAt ?? work.startedAt ?? work.createdAt;
|
|
4874
|
+
const elapsed = new Date(ctx.at).getTime() - new Date(start).getTime();
|
|
4875
|
+
const policy = ctx.policy ?? CODE_DEFAULT_POLICY;
|
|
4876
|
+
const wcell = policy.resolve({ executor: classify(node2.assigneeActorId), scenario: "work" });
|
|
4877
|
+
if (wcell.timeoutAction.kind === "kill-rebuild" && elapsed > wcell.deadlineMs) {
|
|
4878
|
+
events.push({ kind: "work.timeout", workorderId: state.workorder.id, workId: work.id });
|
|
4879
|
+
}
|
|
4880
|
+
}
|
|
4880
4881
|
function openCommentBlockers(state, node2) {
|
|
4881
4882
|
if (!node2.assigneeActorId) return [];
|
|
4882
4883
|
return state.allIssues().filter((i) => i.kind === "comment" && i.resolvedAt === null && i.aboutNodeId === node2.id).map((i) => ({
|
|
@@ -4961,6 +4962,13 @@ function tryToRun(state, nodeId, ctx, events) {
|
|
|
4961
4962
|
const node2 = state.node(nodeId);
|
|
4962
4963
|
if (!node2) return;
|
|
4963
4964
|
if (!node2.assigneeActorId) return;
|
|
4965
|
+
const latest = (node2.latestWorkId ? state.work(node2.latestWorkId) : null) ?? null;
|
|
4966
|
+
if (unexemptedVetoBlockers(scanBlockersForNode(state, node2, latest), "dispatch").length > 0) {
|
|
4967
|
+
if (latest && workState(latest, hasOutput(latest, state.artifactsOf(latest.id).length)) === "running") {
|
|
4968
|
+
checkRunningWorkTimeout(state, node2, latest, ctx, events);
|
|
4969
|
+
}
|
|
4970
|
+
return;
|
|
4971
|
+
}
|
|
4964
4972
|
const inbound = state.edgesInto(nodeId);
|
|
4965
4973
|
for (const e of inbound) {
|
|
4966
4974
|
if (e.kind !== "data" || !e.required) continue;
|
|
@@ -12011,7 +12019,7 @@ function markNodeForRetry(state, nodeId, at, resetRejectedBudget = false) {
|
|
|
12011
12019
|
}
|
|
12012
12020
|
}
|
|
12013
12021
|
}
|
|
12014
|
-
var planUpdateSpec, planAddNode, planDeleteNode, planAddEdge, planDeleteEdge, planAssignActor, planUpdateReviewRequirements, planNodeRetry, planUpdateFields, planApply;
|
|
12022
|
+
var planUpdateSpec, planAddNode, planDeleteNode, planAddEdge, planDeleteEdge, planAssignActor, reviewerKey, planUpdateReviewRequirements, planNodeRetry, planUpdateFields, planApply;
|
|
12015
12023
|
var init_plan = __esm({
|
|
12016
12024
|
"../engine/src/handlers/plan.ts"() {
|
|
12017
12025
|
"use strict";
|
|
@@ -12134,11 +12142,51 @@ var init_plan = __esm({
|
|
|
12134
12142
|
});
|
|
12135
12143
|
}
|
|
12136
12144
|
};
|
|
12145
|
+
reviewerKey = (r) => JSON.stringify([r.reviewerActorId, r.reviewGroup]);
|
|
12137
12146
|
planUpdateReviewRequirements = {
|
|
12138
12147
|
name: "plan/update-review-requirements",
|
|
12139
12148
|
kind: "plan.update_review_requirements",
|
|
12140
12149
|
apply(e, state, ctx) {
|
|
12141
|
-
|
|
12150
|
+
const node2 = state.node(e.nodeId);
|
|
12151
|
+
if (!node2) return;
|
|
12152
|
+
if (e.preserveReviews) {
|
|
12153
|
+
const previous3 = state.requirementsOf(e.nodeId);
|
|
12154
|
+
const previousByKey = new Map(previous3.map((r) => [reviewerKey(r), r]));
|
|
12155
|
+
const nextByKey = new Map(e.reviewers.map((r) => [reviewerKey(r), r]));
|
|
12156
|
+
const membershipChanged = previousByKey.size !== nextByKey.size || [...previousByKey.keys()].some((key) => !nextByKey.has(key));
|
|
12157
|
+
const currentByGroup = /* @__PURE__ */ new Map();
|
|
12158
|
+
for (const req of previous3) {
|
|
12159
|
+
const r = req.latestReviewId ? state.review(req.latestReviewId) : void 0;
|
|
12160
|
+
if (r && r.nodeId === e.nodeId && r.targetWorkId === node2.latestProposedWorkId && r.reviewGroup === req.reviewGroup && previousByKey.has(reviewerKey(r)) && nextByKey.has(reviewerKey(r))) {
|
|
12161
|
+
currentByGroup.set(req.reviewGroup, r.id);
|
|
12162
|
+
}
|
|
12163
|
+
}
|
|
12164
|
+
const next = [...nextByKey.values()].map((r) => {
|
|
12165
|
+
const old = previousByKey.get(reviewerKey(r));
|
|
12166
|
+
return {
|
|
12167
|
+
nodeId: e.nodeId,
|
|
12168
|
+
reviewerActorId: r.reviewerActorId,
|
|
12169
|
+
reviewGroup: r.reviewGroup,
|
|
12170
|
+
source: r.source,
|
|
12171
|
+
assignedBy: old?.assignedBy ?? ctx.actorId,
|
|
12172
|
+
// assignedAt 同时是历史判决回填的名单纪元。名单变更必须推进,避免拿缩减后的组集
|
|
12173
|
+
// 去补判旧 work;本轮有效票由 durable 指针保留,不靠时间戳继承(ADR 0533)。
|
|
12174
|
+
assignedAt: membershipChanged ? ctx.at : old?.assignedAt ?? ctx.at,
|
|
12175
|
+
latestReviewId: currentByGroup.get(r.reviewGroup) ?? null
|
|
12176
|
+
};
|
|
12177
|
+
});
|
|
12178
|
+
for (const r of state.allReviews()) {
|
|
12179
|
+
if (r.nodeId === e.nodeId && !nextByKey.has(reviewerKey(r)) && isReviewKillable(r)) {
|
|
12180
|
+
killReviewRow(state, r, ctx.at);
|
|
12181
|
+
}
|
|
12182
|
+
}
|
|
12183
|
+
const unchanged = previous3.length === next.length && next.every((r) => {
|
|
12184
|
+
const old = previousByKey.get(reviewerKey(r));
|
|
12185
|
+
return old?.source === r.source && old.latestReviewId === r.latestReviewId;
|
|
12186
|
+
});
|
|
12187
|
+
if (!unchanged) state.replaceRequirements(e.nodeId, next);
|
|
12188
|
+
return;
|
|
12189
|
+
}
|
|
12142
12190
|
killNodeReviews(state, e.nodeId, ctx.at);
|
|
12143
12191
|
state.replaceRequirements(e.nodeId, e.reviewers.map((r) => ({
|
|
12144
12192
|
nodeId: e.nodeId,
|
|
@@ -12149,6 +12197,9 @@ var init_plan = __esm({
|
|
|
12149
12197
|
assignedAt: ctx.at,
|
|
12150
12198
|
latestReviewId: null
|
|
12151
12199
|
})));
|
|
12200
|
+
},
|
|
12201
|
+
async effect(e, ctx) {
|
|
12202
|
+
if (e.preserveReviews) await ctx.cancelKilledSessions(e.workorderId, { nodeId: e.nodeId, retained: e.reviewers });
|
|
12152
12203
|
}
|
|
12153
12204
|
};
|
|
12154
12205
|
planNodeRetry = {
|
|
@@ -13236,6 +13287,8 @@ function versionCommandEvent(event) {
|
|
|
13236
13287
|
return { ...event, semanticsVersion: 3 };
|
|
13237
13288
|
}
|
|
13238
13289
|
switch (event.kind) {
|
|
13290
|
+
case "plan.update_review_requirements":
|
|
13291
|
+
return { ...event, preserveReviews: true };
|
|
13239
13292
|
case "work.accept":
|
|
13240
13293
|
case "work.kill":
|
|
13241
13294
|
case "work.handoff_recorded":
|
|
@@ -13801,7 +13854,7 @@ var init_bus = __esm({
|
|
|
13801
13854
|
// ★ 设计 §workorder.sealed/paused「按 work.kill 逻辑杀」:killAllWorks 只置状态,会话取消在这里做——
|
|
13802
13855
|
// apply 已把被杀 work/review 置 deadAt/cancelledAt,这里按 store 找它们仍挂着的会话 cancel
|
|
13803
13856
|
//(deadAt 且 endedAt 未置 = 刚被杀未退出的在途会话)。没会话 / 已退 → no-op。
|
|
13804
|
-
cancelKilledSessions: async () => {
|
|
13857
|
+
cancelKilledSessions: async (_workorderId, reviewScope) => {
|
|
13805
13858
|
let snap;
|
|
13806
13859
|
try {
|
|
13807
13860
|
snap = await this.store.transaction((tx) => tx.loadWorkorder(record8.workorderId));
|
|
@@ -13817,10 +13870,13 @@ var init_bus = __esm({
|
|
|
13817
13870
|
console.warn(`[bus] cancelKilledSessions ${what} ${id}: ${String(err)}`);
|
|
13818
13871
|
}
|
|
13819
13872
|
};
|
|
13820
|
-
for (const w2 of snap.works) {
|
|
13873
|
+
for (const w2 of reviewScope === void 0 ? snap.works : []) {
|
|
13821
13874
|
if (w2.deadAt && w2.endedAt === null) await cancel(w2.id, "work");
|
|
13822
13875
|
}
|
|
13823
13876
|
for (const r of snap.reviews) {
|
|
13877
|
+
if (reviewScope && (r.nodeId !== reviewScope.nodeId || reviewScope.retained.some(
|
|
13878
|
+
(req) => req.reviewerActorId === r.reviewerActorId && req.reviewGroup === r.reviewGroup
|
|
13879
|
+
))) continue;
|
|
13824
13880
|
if (r.cancelledAt && r.endedAt === null) await cancel(r.id, "review");
|
|
13825
13881
|
}
|
|
13826
13882
|
},
|
|
@@ -16273,6 +16329,10 @@ var init_dispatcher = __esm({
|
|
|
16273
16329
|
// 凭证是执行者属性,要人改(等待策略:熔断)
|
|
16274
16330
|
[NO_BINDING_REASON]: "A",
|
|
16275
16331
|
// 僵尸账号:只有人给它一个有效绑定才可能变
|
|
16332
|
+
// 绑到了**别家公司**的机器上(ADR-0164 §5 的租户闸)。与上一条同构:绑定本身就是错的,
|
|
16333
|
+
// 只有人改绑才可能变——而「改绑」正是 A 轴的第一条触发(executorSignature 含 nodeId),
|
|
16334
|
+
// 改完自动复位、不必再等一次人工干预。落 C 也不算错(安全默认),但会让人多做一步。
|
|
16335
|
+
"node-not-in-company": "A",
|
|
16276
16336
|
"clean": "B",
|
|
16277
16337
|
// 机器好好的、就是没交东西 → 换个东西给它做才有意义
|
|
16278
16338
|
"timeout": "B",
|
|
@@ -21195,16 +21255,18 @@ var init_kernel_bridge = __esm({
|
|
|
21195
21255
|
const artId = op.artifactId;
|
|
21196
21256
|
const opReviewers = op.reviewers;
|
|
21197
21257
|
const editReviewRoles = op.reviewRoles;
|
|
21258
|
+
const closureReviewers = (artifacts.get(artId)?.reviewers ?? []).filter((r) => r.source === "closure");
|
|
21198
21259
|
if (artId && (opReviewers || editReviewRoles !== void 0)) {
|
|
21199
21260
|
const art = artifacts.get(artId);
|
|
21200
21261
|
const effectiveRoles = editReviewRoles !== void 0 ? editReviewRoles === null ? void 0 : editReviewRoles : art?.reviewRoles;
|
|
21201
|
-
const effectiveExplicit = opReviewers ?? (art?.reviewers ?? []).map((r) => ({ actor: r.actor, source: r.source }));
|
|
21262
|
+
const effectiveExplicit = opReviewers ?? (art?.reviewers ?? []).filter((r) => r.source !== "closure").map((r) => ({ actor: r.actor, source: r.source }));
|
|
21202
21263
|
const { reviewers: nextReqs, staffingIssues } = resolveNodeReviewers({
|
|
21203
21264
|
typeName: art?.type ?? "",
|
|
21204
21265
|
typeDef: art ? this.schema.get(art.type) : void 0,
|
|
21205
21266
|
...effectiveRoles !== void 0 ? { reviewRoles: effectiveRoles } : {},
|
|
21206
21267
|
explicitReviewers: effectiveExplicit,
|
|
21207
|
-
roleIndex: this.roleIndex
|
|
21268
|
+
roleIndex: this.roleIndex,
|
|
21269
|
+
closureGate: closureReviewers.map((r) => r.actor)
|
|
21208
21270
|
});
|
|
21209
21271
|
for (const issue2 of staffingIssues) {
|
|
21210
21272
|
reviewerStaffing.push({ nodeId: artId, type: art?.type ?? "", role: issue2.role, from: issue2.from });
|
|
@@ -21263,7 +21325,7 @@ var init_kernel_bridge = __esm({
|
|
|
21263
21325
|
...opDescription !== void 0 ? { description: opDescription } : {},
|
|
21264
21326
|
...fields ? { fields: { ...previous3.fields, ...fields } } : {},
|
|
21265
21327
|
...editReviewRoles !== void 0 ? { reviewRoles: editReviewRoles ?? void 0 } : {},
|
|
21266
|
-
...opReviewers ? { reviewers: opReviewers } : {}
|
|
21328
|
+
...opReviewers ? { reviewers: [...opReviewers, ...closureReviewers] } : {}
|
|
21267
21329
|
});
|
|
21268
21330
|
applied++;
|
|
21269
21331
|
break;
|
|
@@ -28721,6 +28783,8 @@ var init_chat_item_ledger = __esm({
|
|
|
28721
28783
|
* 也不算进本段的 assistant item 计数。
|
|
28722
28784
|
*/
|
|
28723
28785
|
recordUserInput(text5, attachments) {
|
|
28786
|
+
const payload = { role: "user", text: text5 };
|
|
28787
|
+
if (attachments?.length) payload.attachments = attachments;
|
|
28724
28788
|
this.insert({
|
|
28725
28789
|
kind: "text",
|
|
28726
28790
|
role: "user",
|
|
@@ -28731,6 +28795,7 @@ var init_chat_item_ledger = __esm({
|
|
|
28731
28795
|
inContent: false,
|
|
28732
28796
|
persist: true,
|
|
28733
28797
|
forceNullMessage: true,
|
|
28798
|
+
payload,
|
|
28734
28799
|
metadata: {
|
|
28735
28800
|
[CHAT_ITEM_META_LEGACY_CONTENT]: false,
|
|
28736
28801
|
...attachments?.length ? { attachments } : {}
|
|
@@ -29049,7 +29114,8 @@ var init_chat_item_ledger = __esm({
|
|
|
29049
29114
|
ord: created.ord,
|
|
29050
29115
|
version: writeVersion,
|
|
29051
29116
|
role: input.role,
|
|
29052
|
-
text: input.text
|
|
29117
|
+
text: input.text,
|
|
29118
|
+
payload
|
|
29053
29119
|
});
|
|
29054
29120
|
} catch {
|
|
29055
29121
|
}
|
|
@@ -29324,6 +29390,8 @@ function runBackgroundAssistantChatTurn(deps) {
|
|
|
29324
29390
|
...assistantMsgId ? { messageId: assistantMsgId } : {},
|
|
29325
29391
|
log: log3
|
|
29326
29392
|
});
|
|
29393
|
+
const liveTurn = deps.liveChat && deps.liveHandle ? deps.liveChat.start(deps.chatSessionId, deps.liveHandle, { items: itemLedger }) : void 0;
|
|
29394
|
+
if (assistantMsgId) liveTurn?.setAssistantMessageId(assistantMsgId);
|
|
29327
29395
|
const normalizedSource = typeof deps.session.onNormalizedProviderEvent === "function" ? {
|
|
29328
29396
|
onNormalizedProviderEvent: deps.session.onNormalizedProviderEvent.bind(deps.session),
|
|
29329
29397
|
finishTurn: (signal) => deps.session.finishNormalizedTurn?.(signal)
|
|
@@ -29349,7 +29417,8 @@ function runBackgroundAssistantChatTurn(deps) {
|
|
|
29349
29417
|
let ckptDirty = false;
|
|
29350
29418
|
normalizedSource.onNormalizedProviderEvent((event) => {
|
|
29351
29419
|
try {
|
|
29352
|
-
|
|
29420
|
+
if (liveTurn) liveTurn.applyNormalizedEvent(event);
|
|
29421
|
+
else itemLedger.apply(event);
|
|
29353
29422
|
} catch {
|
|
29354
29423
|
}
|
|
29355
29424
|
ckptDirty = true;
|
|
@@ -29387,34 +29456,59 @@ function runBackgroundAssistantChatTurn(deps) {
|
|
|
29387
29456
|
}
|
|
29388
29457
|
const finalize = async (status) => {
|
|
29389
29458
|
if (ckptTimer) clearInterval(ckptTimer);
|
|
29390
|
-
|
|
29391
|
-
|
|
29392
|
-
|
|
29393
|
-
|
|
29394
|
-
|
|
29395
|
-
|
|
29396
|
-
|
|
29397
|
-
|
|
29398
|
-
|
|
29399
|
-
|
|
29400
|
-
|
|
29401
|
-
|
|
29402
|
-
|
|
29403
|
-
|
|
29404
|
-
|
|
29405
|
-
|
|
29406
|
-
|
|
29407
|
-
|
|
29408
|
-
|
|
29409
|
-
|
|
29410
|
-
|
|
29459
|
+
try {
|
|
29460
|
+
await checkpointChain.catch(() => void 0);
|
|
29461
|
+
normalizedSource.finishTurn(status === "done" ? "completed" : "failed");
|
|
29462
|
+
itemLedger.finish(status === "done" ? "completed" : "failed");
|
|
29463
|
+
await itemLedger.drain();
|
|
29464
|
+
const projectedContent = itemLedger.projectContent();
|
|
29465
|
+
const parts = collectedParts();
|
|
29466
|
+
await finalizeAssistantRow({
|
|
29467
|
+
chatStore: deps.chatStore,
|
|
29468
|
+
chatSessionId: deps.chatSessionId,
|
|
29469
|
+
assistantMsgId,
|
|
29470
|
+
content: projectedContent,
|
|
29471
|
+
parts,
|
|
29472
|
+
status,
|
|
29473
|
+
...runId ? { runId } : {},
|
|
29474
|
+
now
|
|
29475
|
+
});
|
|
29476
|
+
if (typeof deps.chatStore.updateSession === "function") {
|
|
29477
|
+
try {
|
|
29478
|
+
await deps.chatStore.updateSession(deps.chatSessionId, {
|
|
29479
|
+
runtimeSessionId: deps.session.nativeSessionId ?? null,
|
|
29480
|
+
touchedAt: now()
|
|
29481
|
+
});
|
|
29482
|
+
} catch {
|
|
29483
|
+
}
|
|
29484
|
+
}
|
|
29485
|
+
if (liveTurn) {
|
|
29486
|
+
if (projectedContent) liveTurn.emit({ type: "text", text: projectedContent });
|
|
29487
|
+
liveTurn.emitLive({
|
|
29488
|
+
protocolVersion: 2,
|
|
29489
|
+
streamId: `background:${deps.chatSessionId}`,
|
|
29490
|
+
turnId: deps.turnId ?? fallbackTurnId(runId),
|
|
29491
|
+
itemId: `background:${assistantMsgId ?? runId}`,
|
|
29492
|
+
itemType: "message",
|
|
29493
|
+
operation: "completed",
|
|
29494
|
+
payload: { role: "assistant", text: projectedContent }
|
|
29411
29495
|
});
|
|
29496
|
+
}
|
|
29497
|
+
try {
|
|
29498
|
+
deps.onSettled?.();
|
|
29412
29499
|
} catch {
|
|
29413
29500
|
}
|
|
29414
|
-
}
|
|
29415
|
-
|
|
29416
|
-
|
|
29417
|
-
|
|
29501
|
+
} finally {
|
|
29502
|
+
liveTurn?.emit({ type: "done" });
|
|
29503
|
+
liveTurn?.emitLive({
|
|
29504
|
+
protocolVersion: 2,
|
|
29505
|
+
streamId: `background:${deps.chatSessionId}`,
|
|
29506
|
+
turnId: deps.turnId ?? fallbackTurnId(runId),
|
|
29507
|
+
itemId: "background:terminal",
|
|
29508
|
+
itemType: "control",
|
|
29509
|
+
operation: status === "done" ? "turn_completed" : "turn_failed"
|
|
29510
|
+
});
|
|
29511
|
+
liveTurn?.finish(status);
|
|
29418
29512
|
}
|
|
29419
29513
|
};
|
|
29420
29514
|
void Promise.resolve(deps.session.done).then(() => finalize("done").catch(() => void 0)).catch((err) => {
|
|
@@ -142333,71 +142427,6 @@ var init_collect = __esm({
|
|
|
142333
142427
|
}
|
|
142334
142428
|
});
|
|
142335
142429
|
|
|
142336
|
-
// ../server/src/domains/projects/ephemeral-project.ts
|
|
142337
|
-
function isEphemeralProject(projectId2) {
|
|
142338
|
-
return projectId2.startsWith(EPHEMERAL_PROJECT_PREFIX);
|
|
142339
|
-
}
|
|
142340
|
-
function isUncategorizedProjectId(projectId2) {
|
|
142341
|
-
return projectId2 === UNCATEGORIZED_PROJECT_ID;
|
|
142342
|
-
}
|
|
142343
|
-
function ephemeralProjectIdFor(workOrderId) {
|
|
142344
|
-
return `${EPHEMERAL_PROJECT_PREFIX}${workOrderId.replace(/[^a-zA-Z0-9_-]+/g, "_")}`;
|
|
142345
|
-
}
|
|
142346
|
-
function ephemeralProjectNameFor(workOrderId, title) {
|
|
142347
|
-
return `\u4E34\u65F6\u9879\u76EE \xB7 ${title?.trim() || workOrderId}`;
|
|
142348
|
-
}
|
|
142349
|
-
async function ensureWorkorderProject(deps) {
|
|
142350
|
-
const { workOrderId, projectId: projectId2, title, createdBy } = deps;
|
|
142351
|
-
try {
|
|
142352
|
-
if (projectId2) {
|
|
142353
|
-
await deps.upsertBinding({ workOrderId, projectId: projectId2 });
|
|
142354
|
-
return projectId2;
|
|
142355
|
-
}
|
|
142356
|
-
const id = ephemeralProjectIdFor(workOrderId);
|
|
142357
|
-
await deps.createProject({
|
|
142358
|
-
id,
|
|
142359
|
-
name: ephemeralProjectNameFor(workOrderId, title),
|
|
142360
|
-
description: `\u5EFA\u5355\u65F6\u672A\u6307\u5B9A\u9879\u76EE\uFF0C\u7CFB\u7EDF\u81EA\u52A8\u4E3A\u5DE5\u5355 ${workOrderId} \u521B\u5EFA\u7684\u4E34\u65F6\u9879\u76EE\u3002\u53EF\u7528 \`PATCH /api/work-orders/${workOrderId}/project\` \u6539\u6302\u5230\u6B63\u5F0F\u9879\u76EE\u3002`,
|
|
142361
|
-
...createdBy ? { createdBy } : {}
|
|
142362
|
-
});
|
|
142363
|
-
await deps.upsertBinding({ workOrderId, projectId: id });
|
|
142364
|
-
return id;
|
|
142365
|
-
} catch (e) {
|
|
142366
|
-
deps.onWarn?.(`[project] \u5DE5\u5355 ${workOrderId} \u7ED1\u5B9A\u9879\u76EE\u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u5EFA\u5355\uFF09\uFF1A${String(e)}`);
|
|
142367
|
-
return null;
|
|
142368
|
-
}
|
|
142369
|
-
}
|
|
142370
|
-
function makeEnsureProjectFromStore(store) {
|
|
142371
|
-
return async (input) => {
|
|
142372
|
-
const existing = await store.getProject(input.id);
|
|
142373
|
-
if (existing) return { id: existing.id };
|
|
142374
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
142375
|
-
const createdById = input.createdBy?.trim();
|
|
142376
|
-
const createdBy = createdById && (createdById.startsWith("actor:human:") || createdById.startsWith("actor:agent:")) ? { id: createdById, kind: projectActorKind(createdById), name: null, avatar: null } : void 0;
|
|
142377
|
-
await store.upsertProject({
|
|
142378
|
-
id: input.id,
|
|
142379
|
-
name: input.name,
|
|
142380
|
-
...input.description !== void 0 ? { description: input.description } : {},
|
|
142381
|
-
...createdBy ? { createdBy } : {},
|
|
142382
|
-
createdAt: now,
|
|
142383
|
-
updatedAt: now
|
|
142384
|
-
});
|
|
142385
|
-
return { id: input.id };
|
|
142386
|
-
};
|
|
142387
|
-
}
|
|
142388
|
-
var EPHEMERAL_PROJECT_PREFIX, UNCATEGORIZED_PROJECT_ID, UNCATEGORIZED_PROJECT_NAME, UNCATEGORIZED_PROJECT_DESCRIPTION, UNCATEGORIZED_UNKNOWN_TIME;
|
|
142389
|
-
var init_ephemeral_project = __esm({
|
|
142390
|
-
"../server/src/domains/projects/ephemeral-project.ts"() {
|
|
142391
|
-
"use strict";
|
|
142392
|
-
init_src();
|
|
142393
|
-
EPHEMERAL_PROJECT_PREFIX = "proj_tmp_";
|
|
142394
|
-
UNCATEGORIZED_PROJECT_ID = "proj:uncategorized";
|
|
142395
|
-
UNCATEGORIZED_PROJECT_NAME = "\u65E0\u5206\u7C7B\u9879\u76EE";
|
|
142396
|
-
UNCATEGORIZED_PROJECT_DESCRIPTION = "\u6240\u6709\u672A\u9009\u62E9\u9879\u76EE\u7684\u5BF9\u8BDD\u76F8\u5173\u6587\u4EF6\u548C\u4EFB\u52A1\u81EA\u52A8\u5E76\u5165\u8FD9\u91CC\uFF0C\u7EC4\u7EC7\u5185\u6240\u6709\u4EBA\u53EF\u89C1\u3002";
|
|
142397
|
-
UNCATEGORIZED_UNKNOWN_TIME = "1970-01-01T00:00:00.000Z";
|
|
142398
|
-
}
|
|
142399
|
-
});
|
|
142400
|
-
|
|
142401
142430
|
// ../server/src/domains/collab/timeline.ts
|
|
142402
142431
|
function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
142403
142432
|
if (!snap) return { artifactId, items: [] };
|
|
@@ -159818,8 +159847,9 @@ var init_live_chat = __esm({
|
|
|
159818
159847
|
DEFAULT_LIVE_COALESCE_MS = 24;
|
|
159819
159848
|
DEFAULT_LIVE_COALESCE_MAX_CHUNKS = 32;
|
|
159820
159849
|
DEFAULT_LIVE_COALESCE_MAX_CHARS = 64 * 1024;
|
|
159821
|
-
LiveChatRegistry = class {
|
|
159850
|
+
LiveChatRegistry = class _LiveChatRegistry {
|
|
159822
159851
|
turns = /* @__PURE__ */ new Map();
|
|
159852
|
+
turnStartWaiters = /* @__PURE__ */ new Map();
|
|
159823
159853
|
epochs = /* @__PURE__ */ new Map();
|
|
159824
159854
|
bufferMax;
|
|
159825
159855
|
graceMs;
|
|
@@ -159865,6 +159895,15 @@ var init_live_chat = __esm({
|
|
|
159865
159895
|
v3Items: /* @__PURE__ */ new Map()
|
|
159866
159896
|
};
|
|
159867
159897
|
this.turns.set(chatSessionId, turn);
|
|
159898
|
+
this.flushPendingUserItems(chatSessionId);
|
|
159899
|
+
const waiters = this.turnStartWaiters.get(chatSessionId);
|
|
159900
|
+
this.turnStartWaiters.delete(chatSessionId);
|
|
159901
|
+
for (const wake of waiters ?? []) {
|
|
159902
|
+
try {
|
|
159903
|
+
wake();
|
|
159904
|
+
} catch {
|
|
159905
|
+
}
|
|
159906
|
+
}
|
|
159868
159907
|
return {
|
|
159869
159908
|
runtimeSessionId: turn.runtimeSessionId,
|
|
159870
159909
|
epoch: turn.liveEpoch,
|
|
@@ -159937,6 +159976,26 @@ var init_live_chat = __esm({
|
|
|
159937
159976
|
...opts.items ? { items: opts.items } : {}
|
|
159938
159977
|
};
|
|
159939
159978
|
}
|
|
159979
|
+
/** 只通知开轮,不承载内容。先检查再登记均为同步,避免空闲检查与订阅之间漏掉开轮。 */
|
|
159980
|
+
onNextTurn(chatSessionId, wake) {
|
|
159981
|
+
if (this.isRunning(chatSessionId)) {
|
|
159982
|
+
wake();
|
|
159983
|
+
return () => {
|
|
159984
|
+
};
|
|
159985
|
+
}
|
|
159986
|
+
let waiters = this.turnStartWaiters.get(chatSessionId);
|
|
159987
|
+
if (!waiters) {
|
|
159988
|
+
waiters = /* @__PURE__ */ new Set();
|
|
159989
|
+
this.turnStartWaiters.set(chatSessionId, waiters);
|
|
159990
|
+
}
|
|
159991
|
+
waiters.add(wake);
|
|
159992
|
+
return () => {
|
|
159993
|
+
waiters.delete(wake);
|
|
159994
|
+
if (waiters.size === 0 && this.turnStartWaiters.get(chatSessionId) === waiters) {
|
|
159995
|
+
this.turnStartWaiters.delete(chatSessionId);
|
|
159996
|
+
}
|
|
159997
|
+
};
|
|
159998
|
+
}
|
|
159940
159999
|
/**
|
|
159941
160000
|
* 归一层中间事件 → 带 `itemId` 的 item op(ADR D4 第 2 跳)+ v3 帧 dual-emit(S3)。
|
|
159942
160001
|
*
|
|
@@ -160164,9 +160223,34 @@ var init_live_chat = __esm({
|
|
|
160164
160223
|
* (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
|
|
160165
160224
|
* 前端退回「等下一次快照」,与修复前一致,不会更坏。
|
|
160166
160225
|
*/
|
|
160226
|
+
/**
|
|
160227
|
+
* 「user item 已落库、但这条会话的 live 轮还没 start」时的暂存位(见 {@link publishUserItem})。
|
|
160228
|
+
*
|
|
160229
|
+
* 每会话只留最近几条:它的唯一用途是补发**当前这一次提交**,不是可靠队列。留太多等于把
|
|
160230
|
+
* 一条早就过期的 user 帧在下一轮开头推出去,那比丢了更糟(用户会看到上一轮的话冒出来)。
|
|
160231
|
+
*/
|
|
160232
|
+
pendingUserItems = /* @__PURE__ */ new Map();
|
|
160233
|
+
/** 单会话暂存上限。正常只会有 1 条(一次提交一条);给到 4 是为了并发提交时不至于丢。 */
|
|
160234
|
+
static PENDING_USER_ITEM_MAX = 4;
|
|
160235
|
+
bufferUserItem(chatSessionId, item) {
|
|
160236
|
+
const list2 = this.pendingUserItems.get(chatSessionId) ?? [];
|
|
160237
|
+
list2.push(item);
|
|
160238
|
+
while (list2.length > _LiveChatRegistry.PENDING_USER_ITEM_MAX) list2.shift();
|
|
160239
|
+
this.pendingUserItems.set(chatSessionId, list2);
|
|
160240
|
+
}
|
|
160241
|
+
/** `start` 之后立刻调:把暂存的 user item 补发出去。发不出去就丢掉,不再往下一轮带。 */
|
|
160242
|
+
flushPendingUserItems(chatSessionId) {
|
|
160243
|
+
const list2 = this.pendingUserItems.get(chatSessionId);
|
|
160244
|
+
if (!list2?.length) return;
|
|
160245
|
+
this.pendingUserItems.delete(chatSessionId);
|
|
160246
|
+
for (const item of list2) this.publishUserItem(chatSessionId, item);
|
|
160247
|
+
}
|
|
160167
160248
|
publishUserItem(chatSessionId, item) {
|
|
160168
160249
|
const turn = this.turns.get(chatSessionId);
|
|
160169
|
-
if (!turn)
|
|
160250
|
+
if (!turn) {
|
|
160251
|
+
this.bufferUserItem(chatSessionId, item);
|
|
160252
|
+
return false;
|
|
160253
|
+
}
|
|
160170
160254
|
const payload = { role: "user", text: item.text };
|
|
160171
160255
|
if (item.clientSubmitId) payload.clientSubmitId = item.clientSubmitId;
|
|
160172
160256
|
if (item.attachments?.length) payload.attachments = item.attachments;
|
|
@@ -160442,6 +160526,7 @@ var init_live_chat = __esm({
|
|
|
160442
160526
|
if (!turn || turn.status !== "running") {
|
|
160443
160527
|
return say({ accepted: false, reason: "no-live-turn" }, { turnStatus: turn?.status ?? "<no-turn>" });
|
|
160444
160528
|
}
|
|
160529
|
+
if (turn.recovering) return say({ accepted: false, reason: "recovering" }, { recovering: true });
|
|
160445
160530
|
const fn = turn.handle.appendInput;
|
|
160446
160531
|
if (typeof fn !== "function") return say({ accepted: false, reason: "unsupported" });
|
|
160447
160532
|
const pendingItemId = !opts?.system && opts?.clientMessageKey ? `oasis-user:${opts.clientMessageKey}` : void 0;
|
|
@@ -160587,6 +160672,40 @@ var init_live_chat = __esm({
|
|
|
160587
160672
|
}
|
|
160588
160673
|
});
|
|
160589
160674
|
|
|
160675
|
+
// ../server/src/chat-turn-wait.ts
|
|
160676
|
+
function waitForChatTurn(res, liveChat, sessionId, startKeepalive, ready = false) {
|
|
160677
|
+
res.writeHead(200, {
|
|
160678
|
+
"content-type": "application/x-oasis-live-v3+ndjson; charset=utf-8",
|
|
160679
|
+
"cache-control": "no-cache, no-transform",
|
|
160680
|
+
"x-accel-buffering": "no",
|
|
160681
|
+
"access-control-expose-headers": "X-Chat-Session-Watch, X-Chat-Waiting, X-Can-Append",
|
|
160682
|
+
"x-chat-session-watch": "1",
|
|
160683
|
+
"x-chat-waiting": "1",
|
|
160684
|
+
"x-can-append": "0"
|
|
160685
|
+
});
|
|
160686
|
+
res.flushHeaders();
|
|
160687
|
+
const stopKeepalive = startKeepalive(res);
|
|
160688
|
+
let detach = () => {
|
|
160689
|
+
};
|
|
160690
|
+
const cleanup = () => {
|
|
160691
|
+
stopKeepalive();
|
|
160692
|
+
detach();
|
|
160693
|
+
};
|
|
160694
|
+
res.once("close", cleanup);
|
|
160695
|
+
const wake = () => {
|
|
160696
|
+
cleanup();
|
|
160697
|
+
if (!res.destroyed) res.end('{"type":"turn_available"}\n');
|
|
160698
|
+
};
|
|
160699
|
+
if (ready) wake();
|
|
160700
|
+
else detach = liveChat.onNextTurn(sessionId, wake);
|
|
160701
|
+
if (res.destroyed) cleanup();
|
|
160702
|
+
}
|
|
160703
|
+
var init_chat_turn_wait = __esm({
|
|
160704
|
+
"../server/src/chat-turn-wait.ts"() {
|
|
160705
|
+
"use strict";
|
|
160706
|
+
}
|
|
160707
|
+
});
|
|
160708
|
+
|
|
160590
160709
|
// ../server/src/chat-attachment-files.ts
|
|
160591
160710
|
function safeAttachmentName(raw) {
|
|
160592
160711
|
return (raw || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
|
|
@@ -160683,7 +160802,7 @@ async function appendNowThroughQueue(chatSessionId, text5, deps, clientKey, atta
|
|
|
160683
160802
|
...displayText !== merged ? { displayText } : {},
|
|
160684
160803
|
...attachmentRefs.length ? { attachments: attachmentRefs } : {}
|
|
160685
160804
|
});
|
|
160686
|
-
const DEFINITELY_NOT_DELIVERED = /* @__PURE__ */ new Set(["unsupported", "no-live-turn", "session-closing", "turn-finished"]);
|
|
160805
|
+
const DEFINITELY_NOT_DELIVERED = /* @__PURE__ */ new Set(["unsupported", "no-live-turn", "session-closing", "turn-finished", "recovering"]);
|
|
160687
160806
|
const nextState = res.accepted ? "delivered" : DEFINITELY_NOT_DELIVERED.has(res.reason ?? "") ? "queued" : "unconfirmed";
|
|
160688
160807
|
await deps.store.markPendingMessages?.(chatSessionId, ids2, nextState, res.reason).catch(() => void 0);
|
|
160689
160808
|
return { ...res, queued: true, delivered: merged, batch, ...prepared.paths.length ? { attachmentPaths: prepared.paths } : {} };
|
|
@@ -160784,13 +160903,24 @@ var init_closure_report = __esm({
|
|
|
160784
160903
|
});
|
|
160785
160904
|
|
|
160786
160905
|
// ../server/src/governance/workorder-drafts.ts
|
|
160787
|
-
var import_node_fs5, DRAFT_EDIT_LOCK_TTL_MS, WorkorderDraftStore, ident, seqNoOf, PostgresWorkorderDraftStore;
|
|
160906
|
+
var import_node_fs5, DRAFT_EDIT_LOCK_TTL_MS, WorkorderDraftTagMismatchError, WorkorderDraftStore, ident, seqNoOf, PostgresWorkorderDraftStore;
|
|
160788
160907
|
var init_workorder_drafts = __esm({
|
|
160789
160908
|
"../server/src/governance/workorder-drafts.ts"() {
|
|
160790
160909
|
"use strict";
|
|
160791
160910
|
import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
160792
160911
|
init_closure_report();
|
|
160793
160912
|
DRAFT_EDIT_LOCK_TTL_MS = 12e4;
|
|
160913
|
+
WorkorderDraftTagMismatchError = class extends Error {
|
|
160914
|
+
constructor(draftId, expectedWorkspace, actualWorkspace) {
|
|
160915
|
+
super(
|
|
160916
|
+
actualWorkspace === void 0 ? `\u5EFA\u5355\u8349\u6848 ${draftId} \u4E0D\u5728\u672C store\uFF08\u5F88\u53EF\u80FD\u5EFA\u5728\u53E6\u4E00\u4E2A schema \u7684\u5E93\u91CC\uFF09\u2014\u2014\u62D2\u7EDD\u6253\u6807` : `\u5EFA\u5355\u8349\u6848 ${draftId} \u7684 workspace \u662F ${actualWorkspace}\uFF0C\u4E0E\u672C\u6B21\u5EFA\u5355\u7684 ${String(expectedWorkspace)} \u4E0D\u7B26\u2014\u2014\u62D2\u7EDD\u6253\u6807\uFF08\u8DE8\u5E93\u649E\u53F7\u4F1A\u6539\u7ED1\u522B\u4EBA\u7684\u8349\u6848\uFF09`
|
|
160917
|
+
);
|
|
160918
|
+
this.draftId = draftId;
|
|
160919
|
+
this.expectedWorkspace = expectedWorkspace;
|
|
160920
|
+
this.actualWorkspace = actualWorkspace;
|
|
160921
|
+
this.name = "WorkorderDraftTagMismatchError";
|
|
160922
|
+
}
|
|
160923
|
+
};
|
|
160794
160924
|
WorkorderDraftStore = class {
|
|
160795
160925
|
constructor(file) {
|
|
160796
160926
|
this.file = file;
|
|
@@ -160894,14 +161024,28 @@ var init_workorder_drafts = __esm({
|
|
|
160894
161024
|
}
|
|
160895
161025
|
/** 给草案打上 chat 会话标(建单经 chat 路时,/api/cmd 拿到 X-Chat-Session-Id 后调)。
|
|
160896
161026
|
* 同时(若拿得到)盖上产出它的那一轮 runId,供 friday 重进对话页把该版草案归位到那条助手消息。
|
|
160897
|
-
* sourceRunId 一旦盖上不再改(同一版草案就属于那一轮)。
|
|
160898
|
-
|
|
161027
|
+
* sourceRunId 一旦盖上不再改(同一版草案就属于那一轮)。
|
|
161028
|
+
*
|
|
161029
|
+
* **`expectedWorkspace` 是防跨库改绑的那道闸(2026-09-10 线上事故)**:草案 id 形如
|
|
161030
|
+
* `wodraft:<n>`,而**每个 schema 各有一台发号器**(共享库一台、每家公司各一台,见
|
|
161031
|
+
* `PostgresWorkorderDraftStore.open` 建的 `workorder_draft_seq`)。于是「共享库的 70 号」和
|
|
161032
|
+
* 「didi 库的 70 号」是两份毫不相干的草案。一旦建草案与打标落在**不同的 store**上
|
|
161033
|
+
* (公司上下文解析不出来 → 建落共享库;打标按公司解析 → 打进公司库),这里就会拿着同一个
|
|
161034
|
+
* 号在另一个库里翻出**别人的草案**,把它的 chatSessionId / sourceRunId 覆盖成本次会话——
|
|
161035
|
+
* 别人的建单卡就这样出现在你的对话里,而原归属被覆盖后再也找不回来。
|
|
161036
|
+
*
|
|
161037
|
+
* 所以调用方**必须**把「我刚建的那张单的 workspace」一起传进来:对不上就抛,绝不写。
|
|
161038
|
+
* 查不到 id 同理——旧实现在这里是 `if (d) {}` 的静默 no-op,那正是这次事故连一行日志都没留下的原因。
|
|
161039
|
+
* 抛出的是 {@link WorkorderDraftTagMismatchError},调用方该**记账后继续**(草案本身已经建好,
|
|
161040
|
+
* 不该因为打标失败把整个建单回执变成失败),见 `/api/cmd` 与 playbooks 两处调用点。 */
|
|
161041
|
+
async tagSession(draftId, chatSessionId, sourceRunId, expectedWorkspace) {
|
|
160899
161042
|
const d = this.drafts.get(draftId);
|
|
160900
|
-
if (d) {
|
|
160901
|
-
|
|
160902
|
-
if (sourceRunId && !d.sourceRunId) d.sourceRunId = sourceRunId;
|
|
160903
|
-
await this.persist({ kind: "upsert", draft: d });
|
|
161043
|
+
if (!d || expectedWorkspace !== void 0 && d.workspace !== expectedWorkspace) {
|
|
161044
|
+
throw new WorkorderDraftTagMismatchError(draftId, expectedWorkspace, d?.workspace);
|
|
160904
161045
|
}
|
|
161046
|
+
d.chatSessionId = chatSessionId;
|
|
161047
|
+
if (sourceRunId && !d.sourceRunId) d.sourceRunId = sourceRunId;
|
|
161048
|
+
await this.persist({ kind: "upsert", draft: d });
|
|
160905
161049
|
}
|
|
160906
161050
|
/** 草案被就地编辑后(editDraftPlan 改了 plan.ops)调用:bump `at` 让 friday 重载据此察觉,
|
|
160907
161051
|
* 并**落盘**——否则编辑过的草案在 apply 前进程重启就丢改动(草案落盘的初衷正是扛重启)。 */
|
|
@@ -161694,9 +161838,14 @@ var init_assistants = __esm({
|
|
|
161694
161838
|
await this.opts.actors.disableActor(agentId, by).catch(() => {
|
|
161695
161839
|
});
|
|
161696
161840
|
}
|
|
161841
|
+
/** 本服务的公司 scope;未启用公司维度(单租户装配/单测)时 undefined = 不过滤,行为不变。 */
|
|
161842
|
+
scope() {
|
|
161843
|
+
const companyId = this.opts.companyId();
|
|
161844
|
+
return companyId ? { companyId } : void 0;
|
|
161845
|
+
}
|
|
161697
161846
|
/* ---------- runtime 挑选(ADR §3.3.a):负载均衡 + 幂等 tie-break ---------- */
|
|
161698
161847
|
async pickAssistantRuntime() {
|
|
161699
|
-
const runtimes = (await this.opts.listRuntimes()).filter((r) => r.status === "online");
|
|
161848
|
+
const runtimes = (await this.opts.listRuntimes(void 0, this.scope())).filter((r) => r.status === "online");
|
|
161700
161849
|
if (runtimes.length === 0) return null;
|
|
161701
161850
|
let defaultKind = DEFAULT_ASSISTANT_RUNTIME_KIND;
|
|
161702
161851
|
for (const r of runtimes) {
|
|
@@ -161723,7 +161872,7 @@ var init_assistants = __esm({
|
|
|
161723
161872
|
if (!actor.roles.includes("assistant")) return false;
|
|
161724
161873
|
const binding = await this.opts.registry.getBinding(agentId);
|
|
161725
161874
|
if (!binding || binding.status !== "active") return false;
|
|
161726
|
-
const runtime = (await this.opts.listRuntimes(binding.nodeId)).find((r) => r.kind === binding.runtimeKind);
|
|
161875
|
+
const runtime = (await this.opts.listRuntimes(binding.nodeId, this.scope())).find((r) => r.kind === binding.runtimeKind);
|
|
161727
161876
|
if (!runtime || runtime.status !== "online") return false;
|
|
161728
161877
|
return true;
|
|
161729
161878
|
}
|
|
@@ -163729,7 +163878,8 @@ async function linkChatAttachmentsToWorkOrder(input) {
|
|
|
163729
163878
|
...candidate.contentType ?? meta?.contentType ? { contentType: candidate.contentType ?? meta?.contentType } : {},
|
|
163730
163879
|
sourceSessionId: input.chatSessionId,
|
|
163731
163880
|
sourceMessageId: candidate.messageId,
|
|
163732
|
-
linkedAt: input.linkedAt
|
|
163881
|
+
linkedAt: input.linkedAt,
|
|
163882
|
+
...input.uploadedBy ? { uploadedBy: input.uploadedBy } : {}
|
|
163733
163883
|
};
|
|
163734
163884
|
try {
|
|
163735
163885
|
await input.artifacts.addWorkOrderFile(file);
|
|
@@ -186756,12 +186906,13 @@ var init_governance_service = __esm({
|
|
|
186756
186906
|
const binding = await this.options.projects.getWorkspaceBinding(ctx.organizationId, workorderId);
|
|
186757
186907
|
if (!binding) {
|
|
186758
186908
|
if (claimedProjectId) throw new KnowledgeGovernanceError(409, "KNOWLEDGE_PROJECT_BINDING_MISSING", "\u5DE5\u5355\u6CA1\u6709\u53EF\u4FE1\u9879\u76EE\u7ED1\u5B9A");
|
|
186909
|
+
await this.requireOrganizationActor(ctx);
|
|
186759
186910
|
return void 0;
|
|
186760
186911
|
}
|
|
186761
186912
|
if (claimedProjectId && binding.projectId !== claimedProjectId) {
|
|
186762
186913
|
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_PROJECT_MISMATCH", "\u8BF7\u6C42\u9879\u76EE\u4E0E\u5DE5\u5355\u7ED1\u5B9A\u4E0D\u4E00\u81F4");
|
|
186763
186914
|
}
|
|
186764
|
-
if (this.options.projects.
|
|
186915
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, binding.projectId)) {
|
|
186765
186916
|
await this.requireOrganizationActor(ctx);
|
|
186766
186917
|
return void 0;
|
|
186767
186918
|
}
|
|
@@ -186969,8 +187120,8 @@ var init_governance_service = __esm({
|
|
|
186969
187120
|
const projectId2 = input.scopeType === "project" ? input.scopeId : void 0;
|
|
186970
187121
|
if (input.scopeType === "project") {
|
|
186971
187122
|
if (!projectId2) throw new KnowledgeGovernanceError(400, "KNOWLEDGE_PROJECT_REQUIRED", "\u9879\u76EE Space \u5FC5\u987B\u6307\u5B9A\u9879\u76EE");
|
|
186972
|
-
if (this.options.projects.
|
|
186973
|
-
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_EPHEMERAL_PROJECT", "\
|
|
187123
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2)) {
|
|
187124
|
+
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_EPHEMERAL_PROJECT", "\u8BE5\u9879\u76EE\u4E0D\u652F\u6301\u72EC\u7ACB\u77E5\u8BC6 Space");
|
|
186974
187125
|
}
|
|
186975
187126
|
await this.authorizeProjectResource(ctx, projectId2, "configure");
|
|
186976
187127
|
} else if (input.scopeId) {
|
|
@@ -187096,7 +187247,7 @@ var init_governance_service = __esm({
|
|
|
187096
187247
|
async reconfigureKnowledgeTree(ctx, store, organizationSpace, connection, _config) {
|
|
187097
187248
|
const projectEntries = [];
|
|
187098
187249
|
for (const project of await this.options.projects.listProjects(ctx.organizationId)) {
|
|
187099
|
-
if (this.options.projects.
|
|
187250
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, project.id)) continue;
|
|
187100
187251
|
const projectSpace = (await store.list(this.projectScope(ctx, project.id), "space")).filter((space) => space.status !== "archived").sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
187101
187252
|
if (projectSpace) projectEntries.push({ project, space: projectSpace });
|
|
187102
187253
|
}
|
|
@@ -187315,8 +187466,8 @@ var init_governance_service = __esm({
|
|
|
187315
187466
|
async resolveRunTarget(ctx, projectId2) {
|
|
187316
187467
|
await this.requireOrganizationActor(ctx);
|
|
187317
187468
|
if (projectId2) {
|
|
187318
|
-
if (this.options.projects.
|
|
187319
|
-
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_EPHEMERAL_PROJECT", "\
|
|
187469
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2)) {
|
|
187470
|
+
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_EPHEMERAL_PROJECT", "\u8BE5\u9879\u76EE\u6CA1\u6709\u72EC\u7ACB\u9879\u76EE\u77E5\u8BC6\u5E93\uFF0C\u8BF7\u9009\u62E9\u7EC4\u7EC7\u77E5\u8BC6\u5E93");
|
|
187320
187471
|
}
|
|
187321
187472
|
await this.authorizeProjectResource(ctx, projectId2, "publish");
|
|
187322
187473
|
}
|
|
@@ -187532,7 +187683,7 @@ var init_governance_service = __esm({
|
|
|
187532
187683
|
if (actor.kind !== "system") await this.requireOrganizationAdmin(ctx);
|
|
187533
187684
|
const project = await this.options.projects.getProject(ctx.organizationId, projectId2);
|
|
187534
187685
|
if (!project) throw new KnowledgeGovernanceError(404, "KNOWLEDGE_PROJECT_NOT_FOUND", "\u9879\u76EE\u4E0D\u5B58\u5728");
|
|
187535
|
-
if (this.options.projects.
|
|
187686
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2)) return null;
|
|
187536
187687
|
const store = await this.options.storeFor(ctx.organizationId);
|
|
187537
187688
|
const scope = this.projectScope(ctx, projectId2);
|
|
187538
187689
|
const existingSpaces = await store.list(scope, "space");
|
|
@@ -190232,7 +190383,7 @@ var init_governance_service = __esm({
|
|
|
190232
190383
|
if (event.claimedProjectId !== trusted.projectId) {
|
|
190233
190384
|
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_PROJECT_MISMATCH", "\u8BF7\u6C42\u9879\u76EE\u4E0E\u53EF\u4FE1\u6765\u6E90\u5F52\u5C5E\u4E0D\u4E00\u81F4");
|
|
190234
190385
|
}
|
|
190235
|
-
const targetProjectId = trusted.projectId && !this.options.projects.
|
|
190386
|
+
const targetProjectId = trusted.projectId && !this.options.projects.isProjectScopeUnsupported(ctx.organizationId, trusted.projectId) ? trusted.projectId : void 0;
|
|
190236
190387
|
const discoveryActor = await this.requireActiveActor(ctx.actorId, ctx.organizationId);
|
|
190237
190388
|
if (discoveryActor.kind !== "system") await this.requireOrganizationActor(ctx);
|
|
190238
190389
|
if (targetProjectId) {
|
|
@@ -190498,7 +190649,7 @@ var init_governance_service = __esm({
|
|
|
190498
190649
|
runs.push(...await this.enqueueDiscoveryEvent(ctx, event, runtime ?? void 0));
|
|
190499
190650
|
} catch (error2) {
|
|
190500
190651
|
const store = await this.options.storeFor(ctx.organizationId);
|
|
190501
|
-
const taskProjectId = projectId2 && !this.options.projects.
|
|
190652
|
+
const taskProjectId = projectId2 && !this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2) ? projectId2 : void 0;
|
|
190502
190653
|
const scope = this.scopeFor(ctx, taskProjectId);
|
|
190503
190654
|
const task = this.governanceTask(ctx, "discovery_failed", event.sourceType, `${event.sourceId}:${event.sourceVersionId}`, {
|
|
190504
190655
|
eventType: event.eventType,
|
|
@@ -196615,6 +196766,7 @@ var init_continue_outcome = __esm({
|
|
|
196615
196766
|
"use strict";
|
|
196616
196767
|
REASON_TEXT = {
|
|
196617
196768
|
"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",
|
|
196769
|
+
recovering: "\u670D\u52A1\u521A\u91CD\u542F\uFF0C\u4E13\u5BB6\u90A3\u4E00\u8F6E\u8FD8\u5728\u6062\u590D\u4E2D\uFF08\u8282\u70B9\u91CD\u8FDE\u540E\u5C31\u80FD\u63D2\uFF1B\u8FD9\u53E5\u8BDD\u5DF2\u6392\u961F\uFF09",
|
|
196618
196770
|
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",
|
|
196619
196771
|
"session-closing": "\u4E13\u5BB6\u90A3\u4E00\u8F6E\u6B63\u5728\u6536\u5C3E\uFF0C\u8FD9\u53E5\u8BDD\u6765\u665A\u4E86\u4E00\u6B65",
|
|
196620
196772
|
"turn-finished": "\u6295\u9012\u8FC7\u7A0B\u4E2D\u4E13\u5BB6\u90A3\u4E00\u8F6E\u5DF2\u7ECF\u6536\u5C3E\u4E86",
|
|
@@ -198699,9 +198851,13 @@ async function mintInstallationToken(args, deps = {}) {
|
|
|
198699
198851
|
const token = typeof body2["token"] === "string" ? body2["token"] : "";
|
|
198700
198852
|
if (!token) throw new Error("github app: token missing in response");
|
|
198701
198853
|
const parsed = typeof body2["expires_at"] === "string" ? Date.parse(body2["expires_at"]) : NaN;
|
|
198854
|
+
const rawPerms = body2["permissions"];
|
|
198855
|
+
const permissions = {};
|
|
198856
|
+
for (const [k2, v2] of Object.entries(rawPerms ?? {})) if (typeof v2 === "string") permissions[k2] = v2;
|
|
198702
198857
|
return {
|
|
198703
198858
|
token,
|
|
198704
|
-
expiresAtMs: Number.isFinite(parsed) ? parsed : nowMs + 60 * 60 * 1e3
|
|
198859
|
+
expiresAtMs: Number.isFinite(parsed) ? parsed : nowMs + 60 * 60 * 1e3,
|
|
198860
|
+
permissions
|
|
198705
198861
|
};
|
|
198706
198862
|
}
|
|
198707
198863
|
async function listAppInstallations(args, deps = {}) {
|
|
@@ -198725,7 +198881,10 @@ async function listAppInstallations(args, deps = {}) {
|
|
|
198725
198881
|
const id = item["id"] === void 0 ? "" : String(item["id"]);
|
|
198726
198882
|
if (!id) return [];
|
|
198727
198883
|
const account = item["account"];
|
|
198728
|
-
|
|
198884
|
+
const rawPerms = item["permissions"];
|
|
198885
|
+
const permissions = {};
|
|
198886
|
+
for (const [k2, v2] of Object.entries(rawPerms ?? {})) if (typeof v2 === "string") permissions[k2] = v2;
|
|
198887
|
+
return [{ installationId: id, account: String(account?.["login"] ?? ""), permissions }];
|
|
198729
198888
|
});
|
|
198730
198889
|
}
|
|
198731
198890
|
async function fetchAppSelfMetadata(args, deps = {}) {
|
|
@@ -202522,20 +202681,6 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
202522
202681
|
const project = await ctx.projectState.getProject(projectId2);
|
|
202523
202682
|
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
202524
202683
|
await bindingState.upsertWorkspaceBinding({ workOrderId: workspace, projectId: projectId2 });
|
|
202525
|
-
} else if (createdViaArg === "seed" && ctx.projectState && bindingState) {
|
|
202526
|
-
await ensureWorkorderProject({
|
|
202527
|
-
workOrderId: workspace,
|
|
202528
|
-
...optStr(args, "title") ? { title: optStr(args, "title") } : {},
|
|
202529
|
-
// ADR 0207「对话中创建则为对话人」:优先记**陪聊解析出的那个真人**
|
|
202530
|
-
// (`ctx.workorderInitiator.humanActorId`,与 brief.owner 同源),
|
|
202531
|
-
// 没有会话上下文时退到调用者本人——但**只在他是人时**;
|
|
202532
|
-
// agent 替人跑 spawn 不该把自己写成项目创建人,那种情况落 NULL,
|
|
202533
|
-
// 由读侧从工单 brief.owner 派生(service.resolveProjectCreator)。
|
|
202534
|
-
...ctx.workorderInitiator?.humanActorId ? { createdBy: ctx.workorderInitiator.humanActorId } : !isAgent(actor) ? { createdBy: actor } : {},
|
|
202535
|
-
createProject: makeEnsureProjectFromStore(ctx.projectState),
|
|
202536
|
-
upsertBinding: (b2) => bindingState.upsertWorkspaceBinding(b2),
|
|
202537
|
-
onWarn: (m2) => console.warn(m2)
|
|
202538
|
-
});
|
|
202539
202684
|
}
|
|
202540
202685
|
if (schemaForSeed) {
|
|
202541
202686
|
const title = optStr(args, "title") ?? optStr(args, "description") ?? workspace;
|
|
@@ -204027,6 +204172,18 @@ async function startOasisServer(opts) {
|
|
|
204027
204172
|
chatStore: store,
|
|
204028
204173
|
chatSessionId: sessionId,
|
|
204029
204174
|
session: bgSession,
|
|
204175
|
+
liveChat,
|
|
204176
|
+
liveHandle: {
|
|
204177
|
+
runtimeSessionId: session.id,
|
|
204178
|
+
...session.runId ? { runId: session.runId } : {},
|
|
204179
|
+
kill: () => {
|
|
204180
|
+
void session.kill?.();
|
|
204181
|
+
},
|
|
204182
|
+
...session.appendInput ? { appendInput: (input) => session.appendInput(input) } : {},
|
|
204183
|
+
get canAppendInput() {
|
|
204184
|
+
return typeof session.appendInput === "function" && session.canAppendInput !== false;
|
|
204185
|
+
}
|
|
204186
|
+
},
|
|
204030
204187
|
...itemStore ? { itemStore } : {},
|
|
204031
204188
|
// 广播/委派回流带上 held turn id(B3);授权卡入口在别处,那条路本轮不接账本。
|
|
204032
204189
|
...turnId ? { turnId } : {}
|
|
@@ -204585,7 +204742,15 @@ async function startOasisServer(opts) {
|
|
|
204585
204742
|
if (body2.command === "spawn" && wodrafts) {
|
|
204586
204743
|
const draftId = result?.data?.draftId;
|
|
204587
204744
|
const chatSid = req.headers["x-chat-session-id"];
|
|
204588
|
-
if (draftId && typeof chatSid === "string")
|
|
204745
|
+
if (draftId && typeof chatSid === "string") {
|
|
204746
|
+
const draftWorkspace = result?.data?.workspace;
|
|
204747
|
+
await wodrafts.tagSession(draftId, chatSid, liveChat.runFor(chatSid), draftWorkspace).catch((err) => {
|
|
204748
|
+
console.error(
|
|
204749
|
+
`[workorder-draft] \u8349\u6848 ${draftId}\uFF08ws=${String(draftWorkspace)}\uFF09\u672A\u80FD\u5173\u8054\u4F1A\u8BDD ${chatSid}\uFF1A`,
|
|
204750
|
+
err
|
|
204751
|
+
);
|
|
204752
|
+
});
|
|
204753
|
+
}
|
|
204589
204754
|
}
|
|
204590
204755
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
|
|
204591
204756
|
} catch (err) {
|
|
@@ -204914,7 +205079,9 @@ async function startOasisServer(opts) {
|
|
|
204914
205079
|
blobMeta: blobMetaAcrossCabinets,
|
|
204915
205080
|
// 工单就是这一刻建出来的,所以「现在」就是任务创建时间。取一次、全部文件共用,
|
|
204916
205081
|
// 保证同一次建单带进来的文件在任务侧显示的是同一个时刻(排期序 28 原文口径)。
|
|
204917
|
-
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
205082
|
+
linkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
205083
|
+
// 建单发起人就是把这些附件带进任务的人——项目页「创建人」列与预览副标据此署名。
|
|
205084
|
+
uploadedBy: actor
|
|
204918
205085
|
}).catch((err) => ({
|
|
204919
205086
|
linked: 0,
|
|
204920
205087
|
warnings: [`\u672A\u80FD\u628A\u672C\u6B21\u5BF9\u8BDD\u7684\u9644\u4EF6\u5E26\u5165\u4EFB\u52A1\u6587\u4EF6\uFF1A${err instanceof Error ? err.message : String(err)}`]
|
|
@@ -205592,6 +205759,13 @@ async function startOasisServer(opts) {
|
|
|
205592
205759
|
};
|
|
205593
205760
|
const wantsV3 = url.searchParams.get("protocol") === "v3";
|
|
205594
205761
|
if (wantsV3) {
|
|
205762
|
+
const watchSession = url.searchParams.get("watch") === "1";
|
|
205763
|
+
const fromEpoch = url.searchParams.has("epoch") ? Number(url.searchParams.get("epoch")) : void 0;
|
|
205764
|
+
const cursor = liveChat.cursorFor(sid);
|
|
205765
|
+
if (watchSession && fromEpoch !== void 0 && cursor && cursor.epoch !== fromEpoch) {
|
|
205766
|
+
waitForChatTurn(res, liveChat, sid, startStreamKeepalive, true);
|
|
205767
|
+
return;
|
|
205768
|
+
}
|
|
205595
205769
|
const sub2 = (frame) => {
|
|
205596
205770
|
if (res.destroyed) return;
|
|
205597
205771
|
res.write(`${JSON.stringify(frame)}
|
|
@@ -205603,6 +205777,11 @@ async function startOasisServer(opts) {
|
|
|
205603
205777
|
await reopenRecoveringTurn();
|
|
205604
205778
|
att2 = liveChat.attachLiveV3(sid, fromSeq, sub2);
|
|
205605
205779
|
}
|
|
205780
|
+
if (watchSession && (!att2 || att2.status !== "running" && att2.replay.length === 0)) {
|
|
205781
|
+
att2?.detach();
|
|
205782
|
+
waitForChatTurn(res, liveChat, sid, startStreamKeepalive);
|
|
205783
|
+
return;
|
|
205784
|
+
}
|
|
205606
205785
|
if (!att2) {
|
|
205607
205786
|
res.writeHead(204).end();
|
|
205608
205787
|
return;
|
|
@@ -205610,7 +205789,8 @@ async function startOasisServer(opts) {
|
|
|
205610
205789
|
res.writeHead(200, {
|
|
205611
205790
|
"content-type": "application/x-oasis-live-v3+ndjson; charset=utf-8",
|
|
205612
205791
|
"transfer-encoding": "chunked",
|
|
205613
|
-
"access-control-expose-headers": "X-Session-Id, X-Replay-Gap, X-Live-Epoch, X-Can-Append",
|
|
205792
|
+
"access-control-expose-headers": "X-Session-Id, X-Replay-Gap, X-Live-Epoch, X-Can-Append, X-Chat-Session-Watch",
|
|
205793
|
+
...watchSession ? { "x-chat-session-watch": "1" } : {},
|
|
205614
205794
|
"x-session-id": cs.runtimeSessionId ?? "",
|
|
205615
205795
|
"x-live-epoch": String(att2.epoch),
|
|
205616
205796
|
"x-can-append": liveChat.canAppend(sid) ? "1" : "0",
|
|
@@ -206062,13 +206242,16 @@ ${composed}`;
|
|
|
206062
206242
|
只推 user:assistant 的每一条本来就走 `emitV3`。 */
|
|
206063
206243
|
onPersisted: (info) => {
|
|
206064
206244
|
if (info.role !== "user") return;
|
|
206245
|
+
const rowPayload = info.payload && typeof info.payload === "object" && !Array.isArray(info.payload) ? info.payload : void 0;
|
|
206246
|
+
const rowCsid = typeof rowPayload?.["clientSubmitId"] === "string" ? rowPayload["clientSubmitId"] : void 0;
|
|
206247
|
+
const rowAttachments = Array.isArray(rowPayload?.["attachments"]) ? rowPayload["attachments"] : void 0;
|
|
206065
206248
|
opts.liveChat?.publishUserItem(persistTarget?.id ?? session.id, {
|
|
206066
206249
|
itemId: info.itemId,
|
|
206067
206250
|
ord: info.ord,
|
|
206068
206251
|
version: info.version,
|
|
206069
206252
|
text: info.text,
|
|
206070
|
-
...
|
|
206071
|
-
...
|
|
206253
|
+
...rowCsid ? { clientSubmitId: rowCsid } : {},
|
|
206254
|
+
...rowAttachments?.length ? { attachments: rowAttachments } : {}
|
|
206072
206255
|
});
|
|
206073
206256
|
},
|
|
206074
206257
|
sessionId: persistTarget?.id ?? session.id,
|
|
@@ -206990,19 +207173,60 @@ ${composed}`;
|
|
|
206990
207173
|
return;
|
|
206991
207174
|
}
|
|
206992
207175
|
const meta = await fetchAppSelfMetadata({ appId, privateKeyPem });
|
|
206993
|
-
const
|
|
206994
|
-
const
|
|
207176
|
+
const storedInstallationId = await svc.revealResolvedVariable("GITHUB_APP_INSTALLATION_ID", actorId);
|
|
207177
|
+
const installs = await listAppInstallations({ appId, privateKeyPem }).catch(() => []);
|
|
207178
|
+
const chosen = installs.find((i) => i.installationId === storedInstallationId) ?? (installs.length === 1 ? installs[0] : void 0);
|
|
207179
|
+
const installationStale = Boolean(storedInstallationId) && installs.length > 0 && !chosen;
|
|
207180
|
+
const installationId = chosen?.installationId || storedInstallationId;
|
|
207181
|
+
let grantedFrom = "app";
|
|
207182
|
+
let effective = meta.permissions;
|
|
207183
|
+
if (installationId) {
|
|
207184
|
+
const minted = await mintInstallationToken({ appId, privateKeyPem, installationId }).catch(() => null);
|
|
207185
|
+
if (minted && Object.keys(minted.permissions).length > 0) {
|
|
207186
|
+
effective = minted.permissions;
|
|
207187
|
+
grantedFrom = "token";
|
|
207188
|
+
} else if (chosen && Object.keys(chosen.permissions).length > 0) {
|
|
207189
|
+
effective = chosen.permissions;
|
|
207190
|
+
grantedFrom = "installation";
|
|
207191
|
+
} else {
|
|
207192
|
+
effective = await fetchInstallationPermissions({ appId, privateKeyPem, installationId });
|
|
207193
|
+
grantedFrom = "installation";
|
|
207194
|
+
}
|
|
207195
|
+
}
|
|
206995
207196
|
const missing = missingAppPermissions(effective);
|
|
207197
|
+
const requestedAll = Object.entries(meta.permissions).filter(([, level]) => Boolean(level)).map(([key, level]) => ({ key, level: String(level) })).sort((a, b2) => a.key.localeCompare(b2.key));
|
|
207198
|
+
const grantedKeys = new Set(
|
|
207199
|
+
Object.entries(effective).filter(([, level]) => Boolean(level)).map(([k2]) => k2)
|
|
207200
|
+
);
|
|
206996
207201
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
|
|
206997
207202
|
configured: true,
|
|
206998
207203
|
slug: meta.slug,
|
|
206999
207204
|
ownerLogin: meta.ownerLogin,
|
|
207205
|
+
/** App 申请的全集(英文原名 + 级别)。界面用它回答「我配了多少项」。 */
|
|
207206
|
+
requested: requestedAll,
|
|
207000
207207
|
/** true=读的是这次安装真正被授予的权限;false=只拿到 App 申请的,可能偏乐观。 */
|
|
207001
207208
|
fromInstallation: Boolean(installationId),
|
|
207002
|
-
/**
|
|
207003
|
-
|
|
207209
|
+
/** 「已授予」这一列是从哪问出来的:token(最准)/ 安装记录 / 只有 App 申请。 */
|
|
207210
|
+
grantedFrom,
|
|
207211
|
+
/** 读的是哪一处安装——同一个 App 装多处时,人得看得见自己批的是不是这一处。 */
|
|
207212
|
+
installationAccount: chosen?.account ?? "",
|
|
207213
|
+
/** 库里存的 installation 已不在这个 App 的安装列表里(卸载重装过)。 */
|
|
207214
|
+
installationStale,
|
|
207215
|
+
/**
|
|
207216
|
+
* 这个 App 的**其它**安装及各自的权限项数。只在装了不止一处时才有内容。
|
|
207217
|
+
* 它专门回答那句「我明明已经批过了」:批在哪一处,这里一眼看得见。
|
|
207218
|
+
*/
|
|
207219
|
+
otherInstallations: installs.filter((i) => i.installationId !== installationId).map((i) => ({ account: i.account, count: Object.keys(i.permissions).length })),
|
|
207004
207220
|
/**
|
|
207005
|
-
*
|
|
207221
|
+
* App 申请了、但**这一处安装铸出来的 token 里没有**。
|
|
207222
|
+
* **在全集上做差**,不再只看本仓必需的那八项(否则人另外勾的永远报不出来)。
|
|
207223
|
+
*
|
|
207224
|
+
* ⚠ 差集只说明「这一处安装现在给不了」,不等于「人没批」——同一个 App 装多处时
|
|
207225
|
+
* 批准是逐处的(见 `otherInstallations`)。界面别把这层推断说死。
|
|
207226
|
+
*/
|
|
207227
|
+
awaitingApproval: requestedAll.filter((r) => !grantedKeys.has(r.key)).map((r) => r.key),
|
|
207228
|
+
/**
|
|
207229
|
+
* agent **真正拿得到**的全部权限(优先读 token 自己的权限位),一律用 GitHub 的英文原名 + 级别
|
|
207006
207230
|
* (2026-09-10 发起人:「所有权限都用英文原文,不区分是不是八项权限」)。
|
|
207007
207231
|
*
|
|
207008
207232
|
* 此前这里是 `GITHUB_APP_PERMISSION_LABELS.filter(...)` —— 那张表只有本仓必需的
|
|
@@ -207152,7 +207376,8 @@ ${composed}`;
|
|
|
207152
207376
|
return;
|
|
207153
207377
|
}
|
|
207154
207378
|
try {
|
|
207155
|
-
const
|
|
207379
|
+
const startSvc = await connectorActorsService();
|
|
207380
|
+
const appSecret = startSvc ? await startSvc.revealResolvedVariable("FEISHU_APP_SECRET", actorId) : null;
|
|
207156
207381
|
if (!appSecret) throw new Error("FEISHU_APP_SECRET not found \u2014 please complete app setup first");
|
|
207157
207382
|
const basicAuth = Buffer.from(`${appId}:${appSecret}`).toString("base64");
|
|
207158
207383
|
const resp = await fetch("https://accounts.feishu.cn/oauth/v1/device_authorization", {
|
|
@@ -207182,7 +207407,8 @@ ${composed}`;
|
|
|
207182
207407
|
return;
|
|
207183
207408
|
}
|
|
207184
207409
|
try {
|
|
207185
|
-
const
|
|
207410
|
+
const pollSvc = await connectorActorsService();
|
|
207411
|
+
const appSecret = pollSvc ? await pollSvc.revealResolvedVariable("FEISHU_APP_SECRET", actorId) : null;
|
|
207186
207412
|
if (!appSecret) throw new Error("FEISHU_APP_SECRET not found \u2014 please complete app setup first");
|
|
207187
207413
|
const resp = await fetch("https://open.feishu.cn/open-apis/authen/v2/oauth/token", {
|
|
207188
207414
|
method: "POST",
|
|
@@ -207208,6 +207434,7 @@ ${composed}`;
|
|
|
207208
207434
|
if (svc) {
|
|
207209
207435
|
if (actorId) {
|
|
207210
207436
|
await svc.putVariable({ key: "FEISHU_APP_ID", value: appId, scope: "personal", actorId, connectorId: "feishu", encrypted: true });
|
|
207437
|
+
await svc.putVariable({ key: "FEISHU_APP_SECRET", value: appSecret, scope: "personal", actorId, connectorId: "feishu", encrypted: true });
|
|
207211
207438
|
} else {
|
|
207212
207439
|
await svc.putVariable({ key: "FEISHU_APP_ID", value: appId, scope: "global", connectorId: "feishu", encrypted: true });
|
|
207213
207440
|
await svc.upsertConnector({ id: "feishu", name: "\u98DE\u4E66", mode: "oauth", status: "connected", account: appId });
|
|
@@ -207318,7 +207545,9 @@ ${composed}`;
|
|
|
207318
207545
|
const svc = await connectorActorsService();
|
|
207319
207546
|
if (svc) {
|
|
207320
207547
|
const connId = "github";
|
|
207321
|
-
|
|
207548
|
+
if (!actorIdParam) {
|
|
207549
|
+
await svc.upsertConnector({ id: connId, name: "GitHub", mode: "oauth", status: "connected", account: "github" });
|
|
207550
|
+
}
|
|
207322
207551
|
for (const [k2, v2] of [["GIT_AUTHOR_NAME", actorNameParam], ["GIT_AUTHOR_EMAIL", ""], ["GIT_COMMITTER_NAME", actorNameParam], ["GIT_COMMITTER_EMAIL", ""], ["EMAIL", ""]])
|
|
207323
207552
|
await svc.putVariable({ key: k2, value: v2, scope: "personal", actorId: actorIdParam, connectorId: connId, encrypted: false });
|
|
207324
207553
|
await svc.putVariable({ key: "GITHUB_TOKEN", value: p2.access_token, scope: "personal", actorId: actorIdParam, connectorId: connId, encrypted: true });
|
|
@@ -207453,7 +207682,6 @@ var init_server3 = __esm({
|
|
|
207453
207682
|
init_src7();
|
|
207454
207683
|
init_collect();
|
|
207455
207684
|
init_remote_util();
|
|
207456
|
-
init_ephemeral_project();
|
|
207457
207685
|
init_timeline();
|
|
207458
207686
|
init_node_state();
|
|
207459
207687
|
init_src5();
|
|
@@ -207462,6 +207690,7 @@ var init_server3 = __esm({
|
|
|
207462
207690
|
init_router();
|
|
207463
207691
|
init_infra_error();
|
|
207464
207692
|
init_live_chat();
|
|
207693
|
+
init_chat_turn_wait();
|
|
207465
207694
|
init_append_now();
|
|
207466
207695
|
init_workorder_drafts();
|
|
207467
207696
|
init_draft_edit();
|
|
@@ -208068,10 +208297,15 @@ var init_memory_registry_store = __esm({
|
|
|
208068
208297
|
const mk = this._varKey(v2.key, v2.actorId, v2.projectId);
|
|
208069
208298
|
const ex = this.variables.get(mk);
|
|
208070
208299
|
const keptName = v2.name ?? ex?.name;
|
|
208300
|
+
const keptCreatedBy = ex?.createdBy ?? v2.createdBy;
|
|
208301
|
+
const keptCreatedAt = ex?.createdAt ?? v2.createdAt;
|
|
208071
208302
|
this.variables.set(mk, {
|
|
208072
208303
|
...v2,
|
|
208073
208304
|
...ex?.lastUsedAt !== void 0 ? { lastUsedAt: ex.lastUsedAt } : {},
|
|
208074
|
-
...keptName ? { name: keptName } : {}
|
|
208305
|
+
...keptName ? { name: keptName } : {},
|
|
208306
|
+
...keptCreatedBy ? { createdBy: keptCreatedBy } : {},
|
|
208307
|
+
...keptCreatedAt ? { createdAt: keptCreatedAt } : {},
|
|
208308
|
+
...v2.updatedBy ? { updatedBy: v2.updatedBy } : {}
|
|
208075
208309
|
});
|
|
208076
208310
|
}
|
|
208077
208311
|
async touchVariableUsage(key, actorId, projectId2, when) {
|
|
@@ -210527,6 +210761,32 @@ var init_projection = __esm({
|
|
|
210527
210761
|
}
|
|
210528
210762
|
});
|
|
210529
210763
|
|
|
210764
|
+
// ../server/src/domains/projects/uncategorized-project.ts
|
|
210765
|
+
function isUncategorizedProjectId(projectId2) {
|
|
210766
|
+
return projectId2 === UNCATEGORIZED_PROJECT_ID;
|
|
210767
|
+
}
|
|
210768
|
+
async function bindWorkorderProject(deps) {
|
|
210769
|
+
const { workOrderId, projectId: projectId2 } = deps;
|
|
210770
|
+
if (!projectId2) return null;
|
|
210771
|
+
try {
|
|
210772
|
+
await deps.upsertBinding({ workOrderId, projectId: projectId2 });
|
|
210773
|
+
return projectId2;
|
|
210774
|
+
} catch (e) {
|
|
210775
|
+
deps.onWarn?.(`[project] \u5DE5\u5355 ${workOrderId} \u7ED1\u5B9A\u9879\u76EE\u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u5EFA\u5355\uFF09\uFF1A${String(e)}`);
|
|
210776
|
+
return null;
|
|
210777
|
+
}
|
|
210778
|
+
}
|
|
210779
|
+
var UNCATEGORIZED_PROJECT_ID, UNCATEGORIZED_PROJECT_NAME, UNCATEGORIZED_PROJECT_DESCRIPTION, UNCATEGORIZED_UNKNOWN_TIME;
|
|
210780
|
+
var init_uncategorized_project = __esm({
|
|
210781
|
+
"../server/src/domains/projects/uncategorized-project.ts"() {
|
|
210782
|
+
"use strict";
|
|
210783
|
+
UNCATEGORIZED_PROJECT_ID = "proj:uncategorized";
|
|
210784
|
+
UNCATEGORIZED_PROJECT_NAME = "\u65E0\u5206\u7C7B\u9879\u76EE";
|
|
210785
|
+
UNCATEGORIZED_PROJECT_DESCRIPTION = "\u6240\u6709\u672A\u9009\u62E9\u9879\u76EE\u7684\u5BF9\u8BDD\u76F8\u5173\u6587\u4EF6\u548C\u4EFB\u52A1\u81EA\u52A8\u5E76\u5165\u8FD9\u91CC\uFF0C\u7EC4\u7EC7\u5185\u6240\u6709\u4EBA\u53EF\u89C1\u3002";
|
|
210786
|
+
UNCATEGORIZED_UNKNOWN_TIME = "1970-01-01T00:00:00.000Z";
|
|
210787
|
+
}
|
|
210788
|
+
});
|
|
210789
|
+
|
|
210530
210790
|
// ../server/src/domains/projects/service.ts
|
|
210531
210791
|
function sanitizeCreatorActorId(raw) {
|
|
210532
210792
|
const id = raw?.trim();
|
|
@@ -210562,8 +210822,14 @@ function foldSingleNodeTasks(items) {
|
|
|
210562
210822
|
paths.add(item.path);
|
|
210563
210823
|
nodeFoldersByTask.set(item.taskId, paths);
|
|
210564
210824
|
}
|
|
210825
|
+
const tasksWithRootFiles = /* @__PURE__ */ new Set();
|
|
210826
|
+
for (const item of items) {
|
|
210827
|
+
if (item.kind === "file" && item.taskId && item.path?.startsWith(`wo:${item.taskId}/file:`)) {
|
|
210828
|
+
tasksWithRootFiles.add(item.taskId);
|
|
210829
|
+
}
|
|
210830
|
+
}
|
|
210565
210831
|
const folded = new Set(
|
|
210566
|
-
[...nodeFoldersByTask].filter(([, paths]) => paths.size === 1).map(([taskId]) => taskId)
|
|
210832
|
+
[...nodeFoldersByTask].filter(([taskId, paths]) => paths.size === 1 && !tasksWithRootFiles.has(taskId)).map(([taskId]) => taskId)
|
|
210567
210833
|
);
|
|
210568
210834
|
if (folded.size === 0) return folded;
|
|
210569
210835
|
for (const item of items) {
|
|
@@ -210799,7 +211065,7 @@ var init_service4 = __esm({
|
|
|
210799
211065
|
init_src();
|
|
210800
211066
|
init_src5();
|
|
210801
211067
|
init_projection();
|
|
210802
|
-
|
|
211068
|
+
init_uncategorized_project();
|
|
210803
211069
|
init_workorders();
|
|
210804
211070
|
init_node_state();
|
|
210805
211071
|
DEFAULT_AGENT_ACTOR = "actor:agent:system";
|
|
@@ -210935,6 +211201,9 @@ var init_service4 = __esm({
|
|
|
210935
211201
|
async listWorkspaceBindings() {
|
|
210936
211202
|
return [...this.workspaceBindings.values()].map((binding) => ({ ...binding }));
|
|
210937
211203
|
}
|
|
211204
|
+
async deleteWorkspaceBinding(workOrderId) {
|
|
211205
|
+
this.workspaceBindings.delete(workOrderId);
|
|
211206
|
+
}
|
|
210938
211207
|
async setWorkspaceDispatchHold(workOrderId, held) {
|
|
210939
211208
|
if (held) this.dispatchHeld.add(workOrderId);
|
|
210940
211209
|
else this.dispatchHeld.delete(workOrderId);
|
|
@@ -211061,15 +211330,8 @@ var init_service4 = __esm({
|
|
|
211061
211330
|
return { project, artifacts, files };
|
|
211062
211331
|
}
|
|
211063
211332
|
async collectProjectFiles(projectId2) {
|
|
211064
|
-
if (
|
|
211065
|
-
|
|
211066
|
-
const ephemeralIds = stored.filter((p2) => isEphemeralProject(p2.id)).map((p2) => p2.id);
|
|
211067
|
-
const out = [];
|
|
211068
|
-
for (const id of ephemeralIds) {
|
|
211069
|
-
const files = await this.projects.listProjectFiles(id);
|
|
211070
|
-
out.push(...files.map((f2) => ({ ...f2, projectId: UNCATEGORIZED_PROJECT_ID })));
|
|
211071
|
-
}
|
|
211072
|
-
return out.sort((a, b2) => b2.uploadedAt.localeCompare(a.uploadedAt));
|
|
211333
|
+
if (isUncategorizedProjectId(projectId2)) return [];
|
|
211334
|
+
return this.projects.listProjectFiles(projectId2);
|
|
211073
211335
|
}
|
|
211074
211336
|
/**
|
|
211075
211337
|
* B2 投影同步:写命令落 oplog 后,从 oplog 重放重建 `this.artifacts` 文档层投影。
|
|
@@ -211126,23 +211388,18 @@ var init_service4 = __esm({
|
|
|
211126
211388
|
return project ? this.withProjectDerivedState(project) : null;
|
|
211127
211389
|
}
|
|
211128
211390
|
/**
|
|
211129
|
-
*
|
|
211391
|
+
* 「无分类项目」虚拟卡:把所有**没有项目绑定**的工单聚合成一张固定项目,供列表/详情/关联任务/相关文件消费。
|
|
211130
211392
|
* 存储里没有真行——这就是本卡与真项目最大的区别:
|
|
211131
211393
|
* - 写侧(成员、内容位置、上传文件)一律拒收(写到真项目 or 走真工单流程);
|
|
211132
|
-
* - 派生态(artifactSummary / workOrderCount
|
|
211394
|
+
* - 派生态(artifactSummary / workOrderCount)按「无绑定工单」算并集;
|
|
211133
211395
|
* - 帧上的「项目负责人:系统」由前端自选文案渲染,本层只给一个稳定的空 members 名单。
|
|
211396
|
+
*
|
|
211397
|
+
* ⚠ `workOrderCount` **走 listProjectWorkorders 同一条路**,不另写一份 bindings 过滤——
|
|
211398
|
+
* 两处各算各的正是「卡片计数与点进去的列表条数对不上」那类缺陷的来路。
|
|
211134
211399
|
*/
|
|
211135
211400
|
async buildUncategorizedProject() {
|
|
211136
|
-
const
|
|
211137
|
-
const
|
|
211138
|
-
stored.filter((project) => isEphemeralProject(project.id)).map((project) => project.id)
|
|
211139
|
-
);
|
|
211140
|
-
const bindings = await this.artifacts.listWorkspaceBindings();
|
|
211141
|
-
const workOrderCount = bindings.filter((b2) => ephemeralIds.has(b2.projectId)).length;
|
|
211142
|
-
const artifactList = await this.aggregateUncategorizedArtifacts(ephemeralIds, bindings);
|
|
211143
|
-
const ephemeralProjects = stored.filter((project) => ephemeralIds.has(project.id));
|
|
211144
|
-
const createdAts = ephemeralProjects.map((p2) => p2.createdAt).sort();
|
|
211145
|
-
const updatedAts = ephemeralProjects.map((p2) => p2.updatedAt).sort();
|
|
211401
|
+
const workorders = await this.listProjectWorkorders(UNCATEGORIZED_PROJECT_ID);
|
|
211402
|
+
const artifactList = await this.aggregateUncategorizedArtifacts();
|
|
211146
211403
|
return {
|
|
211147
211404
|
id: UNCATEGORIZED_PROJECT_ID,
|
|
211148
211405
|
name: UNCATEGORIZED_PROJECT_NAME,
|
|
@@ -211150,46 +211407,33 @@ var init_service4 = __esm({
|
|
|
211150
211407
|
memberCount: 0,
|
|
211151
211408
|
members: [],
|
|
211152
211409
|
artifactSummary: artifactList.summary,
|
|
211153
|
-
workOrderCount,
|
|
211410
|
+
workOrderCount: workorders.length,
|
|
211154
211411
|
// ADR 0205:本卡的「项目负责人:系统」是**前端按帧自选的文案**,后端不出 `"system"` 哨兵——
|
|
211155
211412
|
// 出 null 保持「取不到就 null」的域内红线,也不把一个不存在的 actor id 塞进契约。
|
|
211156
211413
|
createdBy: null,
|
|
211157
|
-
createdAt:
|
|
211158
|
-
updatedAt:
|
|
211414
|
+
createdAt: UNCATEGORIZED_UNKNOWN_TIME,
|
|
211415
|
+
updatedAt: UNCATEGORIZED_UNKNOWN_TIME
|
|
211159
211416
|
};
|
|
211160
211417
|
}
|
|
211161
|
-
async aggregateUncategorizedArtifacts(
|
|
211162
|
-
const
|
|
211163
|
-
const artifacts = [];
|
|
211164
|
-
for (const projectId2 of ephemeralIds) {
|
|
211165
|
-
const list2 = await this.listAllProjectArtifacts(projectId2);
|
|
211166
|
-
artifacts.push(...list2.map((a) => ({ ...a, projectId: UNCATEGORIZED_PROJECT_ID })));
|
|
211167
|
-
}
|
|
211418
|
+
async aggregateUncategorizedArtifacts() {
|
|
211419
|
+
const artifacts = await this.listAllProjectArtifacts(UNCATEGORIZED_PROJECT_ID);
|
|
211168
211420
|
artifacts.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
|
|
211169
211421
|
return { artifacts, summary: summarizeArtifacts(artifacts) };
|
|
211170
211422
|
}
|
|
211171
211423
|
/**
|
|
211172
211424
|
* 项目列表(带派生态)。
|
|
211173
211425
|
*
|
|
211174
|
-
*
|
|
211175
|
-
*
|
|
211176
|
-
*
|
|
211177
|
-
*
|
|
211178
|
-
*
|
|
211179
|
-
* 期间别的请求从 pg 连接池取连接排不上号、撞 `connectionTimeoutMillis`(5s),
|
|
211180
|
-
* 前端看到的是「项目列表加载失败:timeout exceeded when trying to connect」——像数据库挂了,
|
|
211181
|
-
* 其实是被自己堵死的(实测并发时 /api/automations 从 0.24s 被拖到 7.1s,自动化页面因此空白)。
|
|
211182
|
-
*
|
|
211183
|
-
* 只对**真要返回的那些**算派生态。默认口径与路由一致(不含临时项目),排查用 `includeEphemeral`。
|
|
211426
|
+
* 库里的每一行都是**人建的真项目**,全部返回——临时项目(`proj_tmp_*`)退役后,这里不再有
|
|
211427
|
+
* "算完就扔"的过滤:旧版 dev 环境 294 个项目里 272 个是一单一个的临时项目,
|
|
211428
|
+
* `withProjectDerivedState` 对它们逐个跑 `buildWorkorderSummaries`(全量工单)+ 遍历全部 artifact,
|
|
211429
|
+
* 93% 的计算白算,还把单线程事件循环整块堵住 3~11 秒(并发时 /api/automations 从 0.24s 被拖到 7.1s,
|
|
211430
|
+
* 前端看到的是「项目列表加载失败:timeout」——像数据库挂了,其实是被自己堵死的)。
|
|
211184
211431
|
*/
|
|
211185
211432
|
async listProjects(opts) {
|
|
211186
211433
|
const stored = await this.projects.listProjects();
|
|
211187
|
-
const wanted = opts?.includeEphemeral === true ? stored : stored.filter((project) => !isEphemeralProject(project.id));
|
|
211188
211434
|
const creatorCache = /* @__PURE__ */ new Map();
|
|
211189
|
-
const derived = await Promise.all(
|
|
211435
|
+
const derived = await Promise.all(stored.map((project) => this.withProjectDerivedState(project, creatorCache)));
|
|
211190
211436
|
if (opts?.includeUncategorized === false) return derived;
|
|
211191
|
-
const wantUncategorized = opts?.includeUncategorized ?? !(opts?.includeEphemeral === true);
|
|
211192
|
-
if (!wantUncategorized) return derived;
|
|
211193
211437
|
const virtual = await this.buildUncategorizedProject();
|
|
211194
211438
|
return [virtual, ...derived];
|
|
211195
211439
|
}
|
|
@@ -211268,10 +211512,10 @@ var init_service4 = __esm({
|
|
|
211268
211512
|
* 项目创建人(ADR 0205)——**读时解析**,三条来路按优先级:
|
|
211269
211513
|
*
|
|
211270
211514
|
* 1. 库里落了 `created_by` → 按它解析 name/avatar;
|
|
211271
|
-
* 2.
|
|
211272
|
-
*
|
|
211273
|
-
*
|
|
211274
|
-
*
|
|
211515
|
+
* 2. 没落 → `null`(老的手建项目),前端诚实显示「—」。
|
|
211516
|
+
*
|
|
211517
|
+
* 旧版还有一档「临时项目从它绑定的那张工单 `brief.owner` 派生」——临时项目退役后没有这一档了
|
|
211518
|
+
* (见 uncategorized-project.ts 文件头)。
|
|
211275
211519
|
*
|
|
211276
211520
|
* **绝不拿别的字段顶替**(`members[0]`、项目名、updatedAt 都不行)——未指定成员时全组织都是成员,
|
|
211277
211521
|
* 按 actorId 排序等于随机点一个 agent 当负责人。这是本域红线,见 resolveActorName 的注释。
|
|
@@ -211279,9 +211523,7 @@ var init_service4 = __esm({
|
|
|
211279
211523
|
async resolveProjectCreator(project, workorders, cache) {
|
|
211280
211524
|
const stored = project.createdBy?.id;
|
|
211281
211525
|
if (stored) return this.resolveActorRef(stored, cache);
|
|
211282
|
-
|
|
211283
|
-
const ownerId = workorders.map((wo) => wo.owner?.id).find((id) => typeof id === "string" && id.startsWith("actor:"));
|
|
211284
|
-
return ownerId ? this.resolveActorRef(ownerId, cache) : null;
|
|
211526
|
+
return null;
|
|
211285
211527
|
}
|
|
211286
211528
|
/**
|
|
211287
211529
|
* actorId → {@link ProjectActorRef}:`kind` 按 id 前缀派生,`name` / `avatar` 走本公司名册现取。
|
|
@@ -211344,7 +211586,8 @@ var init_service4 = __esm({
|
|
|
211344
211586
|
*/
|
|
211345
211587
|
async reassignWorkorder(workOrderId, projectId2) {
|
|
211346
211588
|
if (isUncategorizedProjectId(projectId2)) {
|
|
211347
|
-
|
|
211589
|
+
await this.unlinkWorkorder(workOrderId);
|
|
211590
|
+
return { workOrderId, projectId: null };
|
|
211348
211591
|
}
|
|
211349
211592
|
const project = await this.projects.getProject(projectId2);
|
|
211350
211593
|
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
@@ -211353,77 +211596,46 @@ var init_service4 = __esm({
|
|
|
211353
211596
|
return binding;
|
|
211354
211597
|
}
|
|
211355
211598
|
/**
|
|
211356
|
-
*
|
|
211599
|
+
* 「取消任务关联」——**删掉工单的项目绑定行**,工单从此没有项目(读侧落进「无分类项目」桶)。
|
|
211357
211600
|
*
|
|
211358
|
-
*
|
|
211359
|
-
*
|
|
211360
|
-
*
|
|
211601
|
+
* 2026-09-10 之前这里是"回落到工单专属的临时项目 `proj_tmp_<slug>`",理由是「契约里每个工单
|
|
211602
|
+
* 都该属于某项目」。那套兜底已退役(见 uncategorized-project.ts 文件头):兜底容器让每张未挂靠
|
|
211603
|
+
* 工单变成一个只有它自己的假项目,代码仓/项目变量/记忆作用域全挂在一次性 id 上。
|
|
211361
211604
|
*
|
|
211362
|
-
*
|
|
211363
|
-
* 不需要预先存在(`ensureWorkorderProject` 后台建,也可能是本工单第一次被"解绑"——按需建)。
|
|
211605
|
+
* 幂等:没有绑定行时静默返回(store 的 delete 本身幂等)。
|
|
211364
211606
|
*/
|
|
211365
211607
|
async unlinkWorkorder(workOrderId) {
|
|
211366
211608
|
if (!workOrderId.trim()) throw new Error("work_order_id is required");
|
|
211367
|
-
|
|
211368
|
-
const existing = await this.artifacts.getWorkspaceBinding(workOrderId);
|
|
211369
|
-
if (!await this.projects.getProject(ephemeralId)) {
|
|
211370
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
211371
|
-
await this.projects.upsertProject({
|
|
211372
|
-
id: ephemeralId,
|
|
211373
|
-
name: `\u4E34\u65F6\u9879\u76EE \xB7 ${workOrderId}`,
|
|
211374
|
-
description: `\u5DE5\u5355 ${workOrderId} \u4ECE\u5176\u5B83\u9879\u76EE\u53D6\u6D88\u5173\u8054\u540E\u843D\u56DE\u7684\u4E34\u65F6\u9879\u76EE\u3002\u53EF\u7528 PATCH /api/work-orders/${workOrderId}/project \u6539\u6302\u5230\u6B63\u5F0F\u9879\u76EE\u3002`,
|
|
211375
|
-
createdAt: now,
|
|
211376
|
-
updatedAt: now
|
|
211377
|
-
});
|
|
211378
|
-
}
|
|
211379
|
-
if (existing?.projectId === ephemeralId) return existing;
|
|
211380
|
-
const binding = { workOrderId, projectId: ephemeralId };
|
|
211381
|
-
await this.artifacts.upsertWorkspaceBinding(binding);
|
|
211382
|
-
return binding;
|
|
211609
|
+
await this.artifacts.deleteWorkspaceBinding(workOrderId);
|
|
211383
211610
|
}
|
|
211384
211611
|
/**
|
|
211385
211612
|
* 删除项目(帧 `444:1792` 三点菜单第二项,口径由发起人 2026-09-09 拍板)。
|
|
211386
211613
|
*
|
|
211387
211614
|
* **只删项目这个壳**——项目主记录 + 成员名单 + 上传文件记录 + 内容位置。任务和会话一条都不删:
|
|
211388
211615
|
*
|
|
211389
|
-
* 1. 绑到本项目的工单**不管在跑还是没在跑**,一律 `unlinkWorkorder`
|
|
211390
|
-
*
|
|
211391
|
-
*
|
|
211392
|
-
*
|
|
211393
|
-
*
|
|
211394
|
-
*
|
|
211395
|
-
* 下一个节点就取不到代码仓——正是 `ephemeral-project.ts` 顶部那条五环事故链。
|
|
211616
|
+
* 1. 绑到本项目的工单**不管在跑还是没在跑**,一律 `unlinkWorkorder` 删掉绑定行 → 落进读侧的
|
|
211617
|
+
* 「无分类项目」。发起人明确不要「有任务在跑就不让删」这道闸,所以这里没有任何在跑判断。
|
|
211618
|
+
* 2. **代码仓不跟着搬**(发起人 2026-09-10 裁定)。`git/resolve.ts` 的 `resolveCodeRepo` 是
|
|
211619
|
+
* 派发时沿 `绑定 → 项目 → 内容位置` 实时解的,所以删完项目,这些任务下一个节点就取不到代码仓——
|
|
211620
|
+
* 这是**期望行为**:由执行中的数字员工发现并引导人给任务指定新项目,而不是由系统兜一个假容器
|
|
211621
|
+
* (旧版把 git 位置逐单复制到 `proj_tmp_*` 上,正是那套已退役的兜底)。
|
|
211396
211622
|
* 3. 会话的 `project_id` 置空、项目作用域的变量/密钥清理,走 {@link OnProjectDeleted} 钩子
|
|
211397
211623
|
* (本 service 对会话域与凭据域零耦合,同 `onProjectNameChanged` 的注入模式)。
|
|
211398
211624
|
*
|
|
211399
|
-
*
|
|
211400
|
-
* (工单的兜底归属,删掉等于把工单变成无绑定态——契约里 `Workspace.projectId` 必填)。
|
|
211625
|
+
* 拒绝 `proj:uncategorized`(读侧虚拟卡,存储里没有真行,删它没有意义)。
|
|
211401
211626
|
*/
|
|
211402
211627
|
async deleteProject(id) {
|
|
211403
211628
|
const projectId2 = id?.trim();
|
|
211404
211629
|
if (!projectId2) throw new Error("project id is required");
|
|
211405
211630
|
if (isUncategorizedProjectId(projectId2)) throw new Error(`project not deletable: ${projectId2}`);
|
|
211406
|
-
if (isEphemeralProject(projectId2)) throw new Error(`project not deletable: ${projectId2}`);
|
|
211407
211631
|
const project = await this.projects.getProject(projectId2);
|
|
211408
211632
|
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
211409
|
-
const gitLocations = (await this.projects.listContentLocations(projectId2)).filter((loc) => loc.kind === "external-home" && loc.externalHome?.system === "git");
|
|
211410
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
211411
211633
|
const bindings = await this.artifacts.listWorkspaceBindings();
|
|
211412
211634
|
const movedWorkOrderIds = [];
|
|
211413
211635
|
for (const binding of bindings) {
|
|
211414
211636
|
if (binding.projectId !== projectId2) continue;
|
|
211415
|
-
|
|
211637
|
+
await this.unlinkWorkorder(binding.workOrderId);
|
|
211416
211638
|
movedWorkOrderIds.push(binding.workOrderId);
|
|
211417
|
-
for (const loc of gitLocations) {
|
|
211418
|
-
await this.projects.addContentLocation({
|
|
211419
|
-
...loc,
|
|
211420
|
-
projectId: moved.projectId,
|
|
211421
|
-
// 派生 id:同一条位置搬到同一个临时项目永远算出同一个 id(addContentLocation 是
|
|
211422
|
-
// upsert),重跑不会堆出重复的仓库行。
|
|
211423
|
-
locationId: `pcl-${moved.projectId}-carried-${loc.locationId}`,
|
|
211424
|
-
updatedAt: now
|
|
211425
|
-
});
|
|
211426
|
-
}
|
|
211427
211639
|
}
|
|
211428
211640
|
await this.projects.deleteProject(projectId2);
|
|
211429
211641
|
if (this.onProjectDeleted) {
|
|
@@ -211433,7 +211645,7 @@ var init_service4 = __esm({
|
|
|
211433
211645
|
console.warn(`[projects] project-deleted follow-up failed for ${projectId2}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
211434
211646
|
}
|
|
211435
211647
|
}
|
|
211436
|
-
return { projectId: projectId2, movedWorkOrderIds
|
|
211648
|
+
return { projectId: projectId2, movedWorkOrderIds };
|
|
211437
211649
|
}
|
|
211438
211650
|
/**
|
|
211439
211651
|
* 项目关联工单(帧 `1355:4918`):把内部私有的 `listProjectWorkorders` 暴露给路由层。
|
|
@@ -211558,6 +211770,33 @@ var init_service4 = __esm({
|
|
|
211558
211770
|
});
|
|
211559
211771
|
}
|
|
211560
211772
|
}
|
|
211773
|
+
if (this.artifacts.listWorkOrderFiles) {
|
|
211774
|
+
for (const workOrderId of workorderSummaries.map((w2) => w2.id)) {
|
|
211775
|
+
const workOrderFiles = await this.artifacts.listWorkOrderFiles(workOrderId).catch(() => []);
|
|
211776
|
+
const taskTitle = workorderTitleById.get(workOrderId) ?? null;
|
|
211777
|
+
for (const file of workOrderFiles) {
|
|
211778
|
+
const creatorId = file.uploadedBy ?? null;
|
|
211779
|
+
items.push({
|
|
211780
|
+
kind: "file",
|
|
211781
|
+
id: file.fileId,
|
|
211782
|
+
name: file.name,
|
|
211783
|
+
path: `wo:${workOrderId}/file:${file.fileId}`,
|
|
211784
|
+
filePath: null,
|
|
211785
|
+
depth: 1,
|
|
211786
|
+
type: file.contentType ?? inferTypeFromName(file.name),
|
|
211787
|
+
blobRef: file.blobId,
|
|
211788
|
+
contentType: file.contentType ?? null,
|
|
211789
|
+
size: file.size,
|
|
211790
|
+
taskId: workOrderId,
|
|
211791
|
+
taskTitle,
|
|
211792
|
+
creatorKind: creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null,
|
|
211793
|
+
creatorId,
|
|
211794
|
+
creatorName: creatorId ? this.resolveActorName(creatorId) : null,
|
|
211795
|
+
updatedAt: file.linkedAt
|
|
211796
|
+
});
|
|
211797
|
+
}
|
|
211798
|
+
}
|
|
211799
|
+
}
|
|
211561
211800
|
const foldedTasks = foldSingleNodeTasks(items);
|
|
211562
211801
|
const taskFolders = /* @__PURE__ */ new Set();
|
|
211563
211802
|
for (const item of [...items]) {
|
|
@@ -211596,6 +211835,8 @@ var init_service4 = __esm({
|
|
|
211596
211835
|
filePath: null,
|
|
211597
211836
|
depth: path41 ? path41.replace(/^\/+/, "").split("/").length - 1 : 0,
|
|
211598
211837
|
type: file.contentType ?? inferTypeFromName(file.name),
|
|
211838
|
+
blobRef: file.blobId,
|
|
211839
|
+
contentType: file.contentType ?? null,
|
|
211599
211840
|
size: file.size,
|
|
211600
211841
|
taskId: null,
|
|
211601
211842
|
taskTitle: null,
|
|
@@ -212063,7 +212304,7 @@ var init_service4 = __esm({
|
|
|
212063
212304
|
if (!snap) return [];
|
|
212064
212305
|
const worksById = new Map(snap.works.map((w2) => [w2.id, w2]));
|
|
212065
212306
|
const binding = this.artifacts.getWorkspaceBinding ? await this.artifacts.getWorkspaceBinding(workOrderId).catch(() => null) : null;
|
|
212066
|
-
const projectId2 = binding?.projectId ??
|
|
212307
|
+
const projectId2 = binding?.projectId ?? UNCATEGORIZED_PROJECT_ID;
|
|
212067
212308
|
const out = [];
|
|
212068
212309
|
for (const node2 of snap.nodes) {
|
|
212069
212310
|
if (!node2.latestAcceptId) continue;
|
|
@@ -212193,20 +212434,8 @@ var init_service4 = __esm({
|
|
|
212193
212434
|
const filtered = artifacts.filter((artifact) => input.type === void 0 || artifact.type === input.type).filter((artifact) => input.status === void 0 || artifact.status === input.status).sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
|
|
212194
212435
|
return { artifacts: filtered, summary: summarizeArtifacts(filtered) };
|
|
212195
212436
|
}
|
|
212196
|
-
/**
|
|
212437
|
+
/** 内部:虚拟卡与真项目走同一条路——{@link listAllProjectArtifacts} 内部按「无绑定 / 绑到本项目」分流。 */
|
|
212197
212438
|
async collectProjectArtifacts(projectId2) {
|
|
212198
|
-
if (isUncategorizedProjectId(projectId2)) {
|
|
212199
|
-
const stored = await this.projects.listProjects();
|
|
212200
|
-
const ephemeralIds = new Set(
|
|
212201
|
-
stored.filter((project) => isEphemeralProject(project.id)).map((project) => project.id)
|
|
212202
|
-
);
|
|
212203
|
-
const artifacts = [];
|
|
212204
|
-
for (const id of ephemeralIds) {
|
|
212205
|
-
const list2 = await this.listAllProjectArtifacts(id);
|
|
212206
|
-
artifacts.push(...list2.map((a) => ({ ...a, projectId: UNCATEGORIZED_PROJECT_ID })));
|
|
212207
|
-
}
|
|
212208
|
-
return artifacts;
|
|
212209
|
-
}
|
|
212210
212439
|
return this.listAllProjectArtifacts(projectId2);
|
|
212211
212440
|
}
|
|
212212
212441
|
async listProjectWorkorders(projectId2) {
|
|
@@ -212215,17 +212444,19 @@ var init_service4 = __esm({
|
|
|
212215
212444
|
const uncategorized = isUncategorizedProjectId(projectId2);
|
|
212216
212445
|
return buildWorkorderSummaries(this.kernel.model, void 0, (workOrderId) => projectByWorkorder.get(workOrderId) ?? null, void 0, void 0, void 0, (role) => this.kernel.actorForRole(role)).filter((workorder) => {
|
|
212217
212446
|
const bound = workorder.projectId;
|
|
212218
|
-
if (uncategorized) return
|
|
212447
|
+
if (uncategorized) return bound == null;
|
|
212219
212448
|
return bound === projectId2;
|
|
212220
212449
|
});
|
|
212221
212450
|
}
|
|
212222
212451
|
async listAllProjectArtifacts(projectId2) {
|
|
212223
|
-
const
|
|
212452
|
+
const uncategorized = isUncategorizedProjectId(projectId2);
|
|
212453
|
+
const stored = uncategorized ? [] : await this.artifacts.listArtifacts({ projectId: projectId2 });
|
|
212224
212454
|
const byArtifact = new Map(stored.map((artifact) => [artifact.artifactId, artifact]));
|
|
212225
212455
|
const bindings = await this.artifacts.listWorkspaceBindings();
|
|
212226
212456
|
const projectByWorkorder = new Map(bindings.map((binding) => [binding.workOrderId, binding.projectId]));
|
|
212227
212457
|
for (const artifact of this.kernel.model.artifacts.values()) {
|
|
212228
|
-
|
|
212458
|
+
const bound = projectByWorkorder.get(artifact.workspace);
|
|
212459
|
+
if (uncategorized ? bound !== void 0 : bound !== projectId2) continue;
|
|
212229
212460
|
const descriptor = decodeDocumentDescription(artifact.description);
|
|
212230
212461
|
const info = nodeInfo(this.kernel.model, artifact);
|
|
212231
212462
|
const revisions = revisionsOf(this.kernel.model, artifact.id);
|
|
@@ -216890,8 +217121,18 @@ function wireRecoveredChatTurn(deps) {
|
|
|
216890
217121
|
clearInterval(ckptTimer);
|
|
216891
217122
|
const failed = exitFailed(info);
|
|
216892
217123
|
if (failed) {
|
|
216893
|
-
|
|
216894
|
-
|
|
217124
|
+
const errText2 = exitErrorText(info);
|
|
217125
|
+
live.emit({ type: "error", text: errText2 });
|
|
217126
|
+
const errorOutcome = itemLedger.recordError(errText2, { ...info.reason ? { reason: info.reason } : {} });
|
|
217127
|
+
live.publishServerItem({
|
|
217128
|
+
itemId: errorOutcome.itemId,
|
|
217129
|
+
itemType: "control",
|
|
217130
|
+
startedVersion: errorOutcome.startedVersion,
|
|
217131
|
+
version: errorOutcome.version,
|
|
217132
|
+
ord: errorOutcome.ord,
|
|
217133
|
+
status: "failed",
|
|
217134
|
+
payload: { code: "error", note: errText2, ...info.reason ? { reason: info.reason } : {} }
|
|
217135
|
+
});
|
|
216895
217136
|
}
|
|
216896
217137
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
216897
217138
|
const parts = collectedParts();
|
|
@@ -216918,6 +217159,11 @@ function wireRecoveredChatTurn(deps) {
|
|
|
216918
217159
|
...nextRuntimeSessionId ? { runtimeSessionId: nextRuntimeSessionId } : {},
|
|
216919
217160
|
touchedAt: finishedAt
|
|
216920
217161
|
}).catch(() => void 0);
|
|
217162
|
+
if (deps.onTurnSettled) {
|
|
217163
|
+
await deps.onTurnSettled(failed ? "failed" : "succeeded", finishedAt).catch((err) => {
|
|
217164
|
+
log3(`[chat-recovery] \u4F1A\u8BDD ${plan.chatSessionId} \u6536\u53E3\u8BB0\u8D26\u5931\u8D25\uFF08\u9000\u56DE\u6E05\u626B\u62CD\u515C\u5E95\uFF09: ${String(err)}`);
|
|
217165
|
+
});
|
|
217166
|
+
}
|
|
216921
217167
|
if (!liveTerminalEmitted) {
|
|
216922
217168
|
live.emitLive({
|
|
216923
217169
|
protocolVersion: 2,
|
|
@@ -217241,6 +217487,22 @@ async function recoverChatTurnsAcrossCompanies(deps) {
|
|
|
217241
217487
|
continue;
|
|
217242
217488
|
}
|
|
217243
217489
|
const handle = deps.recover(plan);
|
|
217490
|
+
const settleTurnId = turn?.id;
|
|
217491
|
+
const onTurnSettled = settleTurnId && stores.settleTurn ? async (status, completedAt) => {
|
|
217492
|
+
await stores.settleTurn(settleTurnId, {
|
|
217493
|
+
status,
|
|
217494
|
+
completedAt,
|
|
217495
|
+
...status === "failed" ? { lastError: `\u91CD\u6302\u7684\u8F6E\u4EE5 ${status} \u6536\u5C3E\uFF08run ${plan.runId}\uFF09` } : {}
|
|
217496
|
+
});
|
|
217497
|
+
log3(`[chat-recovery] \u8F6E\u6B21 ${settleTurnId}\uFF08\u4F1A\u8BDD ${plan.chatSessionId}\uFF09\u968F\u91CD\u6302\u6536\u53E3\u4E3A ${status}\uFF0C\u4F1A\u8BDD\u69FD\u5DF2\u91CA\u653E`);
|
|
217498
|
+
await deps.onTurnSettled?.({
|
|
217499
|
+
chatSessionId: plan.chatSessionId,
|
|
217500
|
+
turnId: settleTurnId,
|
|
217501
|
+
status,
|
|
217502
|
+
reason: `\u91CD\u6302\u7684\u8F6E\u4EE5 ${status} \u6536\u5C3E`,
|
|
217503
|
+
...companyId ? { companyId } : {}
|
|
217504
|
+
});
|
|
217505
|
+
} : void 0;
|
|
217244
217506
|
const { done } = wireRecoveredChatTurn({
|
|
217245
217507
|
plan,
|
|
217246
217508
|
handle,
|
|
@@ -217250,6 +217512,7 @@ async function recoverChatTurnsAcrossCompanies(deps) {
|
|
|
217250
217512
|
register: () => deps.register(plan, handle),
|
|
217251
217513
|
unregister: () => deps.unregister(plan),
|
|
217252
217514
|
...deps.traceHealth ? { traceHealth: deps.traceHealth } : {},
|
|
217515
|
+
...onTurnSettled ? { onTurnSettled } : {},
|
|
217253
217516
|
...stores.items ? { items: stores.items, itemsTurnId } : {},
|
|
217254
217517
|
log: log3
|
|
217255
217518
|
});
|
|
@@ -219317,7 +219580,14 @@ ${input.description}
|
|
|
219317
219580
|
vars.filter((v2) => v2.scope === "personal" && v2.actorId === actorId && v2.connectorId).map((v2) => v2.connectorId)
|
|
219318
219581
|
)];
|
|
219319
219582
|
const effective = await this.effectiveConnectorEntries(actorId, config2, connectors);
|
|
219320
|
-
|
|
219583
|
+
const flat = {};
|
|
219584
|
+
for (const v2 of vars) if (v2.scope === "global" && !v2.actorId) flat[v2.key] = this.decodeCiphertext(v2.valueEncrypted);
|
|
219585
|
+
for (const v2 of vars) if (v2.scope === "personal" && (!v2.actorId || v2.actorId === actorId)) flat[v2.key] = this.decodeCiphertext(v2.valueEncrypted);
|
|
219586
|
+
const credentialReady = collectAllConnectorCredentials({
|
|
219587
|
+
vars: flat,
|
|
219588
|
+
actorName: flat["GIT_AUTHOR_NAME"] ?? actorId.split(":").pop() ?? "oasis-agent"
|
|
219589
|
+
}).map((c) => c.slug);
|
|
219590
|
+
return { connections, globalConnected, actorConnected, effective, credentialReady };
|
|
219321
219591
|
}
|
|
219322
219592
|
/**
|
|
219323
219593
|
* 该员工每个连接器的「有效启用」集合(变量/技能注入闸门):
|
|
@@ -219430,6 +219700,11 @@ ${input.description}
|
|
|
219430
219700
|
deliveryMode: v2.deliveryMode ?? "env",
|
|
219431
219701
|
valueEncrypted: v2.encrypted === false ? `plain:${v2.value}` : this.encrypt(v2.value),
|
|
219432
219702
|
updatedAt: this.now(),
|
|
219703
|
+
/* 创建者 / 创建时间 / 更新者:**由服务端落,不接受调用方自报**。
|
|
219704
|
+
createdBy/createdAt 两格存储层按「第一次落行才写」处理(COALESCE 旧值),所以这里每次都传,
|
|
219705
|
+
写不写得进去由存储层决定;updatedBy 每次覆盖。
|
|
219706
|
+
`by` 缺省(连接器授权流程等系统写入)时三格都不传——**不拿一个假身份填上去**。 */
|
|
219707
|
+
...v2.by ? { createdBy: v2.by, createdAt: this.now(), updatedBy: v2.by } : {},
|
|
219433
219708
|
// ADR 凭据保险库补章 §D2.2:expiresAt 写侧穿透——**显式带 key**(含 undefined),使清空(传空→undefined)
|
|
219434
219709
|
// 也落到底(store 两实现都按此覆盖)。lastUsedAt 从不由写侧传,取用时另刷(§D2.3)。
|
|
219435
219710
|
expiresAt: v2.expiresAt
|
|
@@ -219502,7 +219777,12 @@ ${input.description}
|
|
|
219502
219777
|
updatedAt: r.updatedAt,
|
|
219503
219778
|
// ADR 凭据保险库补章 §D2.1:到期/取用时间戳透传(展示层据此染色;补章 §D2.4)。
|
|
219504
219779
|
...r.expiresAt !== void 0 ? { expiresAt: r.expiresAt } : {},
|
|
219505
|
-
...r.lastUsedAt !== void 0 ? { lastUsedAt: r.lastUsedAt } : {}
|
|
219780
|
+
...r.lastUsedAt !== void 0 ? { lastUsedAt: r.lastUsedAt } : {},
|
|
219781
|
+
// 创建者 / 创建时间 / 更新者(子 PRD《密钥管理》§2 + 2026-09-10 追加)。
|
|
219782
|
+
// 存量行没有这三格,原样不带出去——展示侧出「—」,不拿 updatedAt 冒充 createdAt。
|
|
219783
|
+
...r.createdBy !== void 0 ? { createdBy: r.createdBy } : {},
|
|
219784
|
+
...r.createdAt !== void 0 ? { createdAt: r.createdAt } : {},
|
|
219785
|
+
...r.updatedBy !== void 0 ? { updatedBy: r.updatedBy } : {}
|
|
219506
219786
|
};
|
|
219507
219787
|
});
|
|
219508
219788
|
}
|
|
@@ -219789,13 +220069,6 @@ var init_memory = __esm({
|
|
|
219789
220069
|
async write(ctx, input) {
|
|
219790
220070
|
this.assertWritable(input, "agent");
|
|
219791
220071
|
const projectId2 = await this.projectOf(ctx);
|
|
219792
|
-
if (input.scope === "project" && projectId2 === null) {
|
|
219793
|
-
throw new ApiError(
|
|
219794
|
-
400,
|
|
219795
|
-
"MEMORY_NO_PROJECT",
|
|
219796
|
-
"\u672C\u8F6E\u89E3\u6790\u4E0D\u51FA\u6240\u5C5E\u9879\u76EE\uFF08\u975E\u6D3E\u53D1\u4F1A\u8BDD\u6216\u4EA7\u7269\u672A\u7ED1\u5B9A\u5DE5\u5355\uFF09\uFF0C\u65E0\u6CD5\u5199\u9879\u76EE\u7EA7\u8BB0\u5FC6\u2014\u2014\u6539\u7528 --scope actor \u5199\u8DE8\u9879\u76EE\u901A\u7528\u7684\u7ECF\u9A8C\u3002"
|
|
219797
|
-
);
|
|
219798
|
-
}
|
|
219799
220072
|
return this.writeResolved(ctx, input, input.scope === "project" ? projectId2 : null, "agent");
|
|
219800
220073
|
}
|
|
219801
220074
|
/** 控制台管理面写入:actorId/projectId 由受保护的管理路由明确给出,不复用 agent 自用入口。 */
|
|
@@ -220905,6 +221178,21 @@ var init_connector_skills = __esm({
|
|
|
220905
221178
|
}
|
|
220906
221179
|
});
|
|
220907
221180
|
|
|
221181
|
+
// ../server/src/domains/nodes/runtime-tenancy.ts
|
|
221182
|
+
function foreignNodeBlock(node2, expectedCompanyId) {
|
|
221183
|
+
if (!expectedCompanyId) return null;
|
|
221184
|
+
if (!node2) return null;
|
|
221185
|
+
const owner = node2.companyId;
|
|
221186
|
+
if (!owner) return null;
|
|
221187
|
+
if (owner === expectedCompanyId) return null;
|
|
221188
|
+
return `\u8FD0\u884C\u65F6\u8282\u70B9 ${node2.id} \u5C5E\u4E8E\u516C\u53F8 ${owner}\uFF0C\u4E0D\u5C5E\u4E8E ${expectedCompanyId}\u2014\u2014\u5458\u5DE5\u53EA\u80FD\u7ED1\u5B9A/\u6D3E\u53D1\u5230\u672C\u516C\u53F8\u7684\u8282\u70B9\uFF08ADR-0164 \xA75\uFF1A\u8DE8\u516C\u53F8\u5171\u4EAB\u540C\u4E00\u53F0\u7269\u7406\u673A\u7684\u6B63\u89E3\u662F\u5404\u81EA\u767B\u8BB0\u4E0D\u540C node id\uFF09\u3002`;
|
|
221189
|
+
}
|
|
221190
|
+
var init_runtime_tenancy = __esm({
|
|
221191
|
+
"../server/src/domains/nodes/runtime-tenancy.ts"() {
|
|
221192
|
+
"use strict";
|
|
221193
|
+
}
|
|
221194
|
+
});
|
|
221195
|
+
|
|
220908
221196
|
// ../server/src/domains/actors/routes.ts
|
|
220909
221197
|
async function asApiError(run) {
|
|
220910
221198
|
try {
|
|
@@ -221227,6 +221515,9 @@ function actorsDomain(opts) {
|
|
|
221227
221515
|
const { service } = await resolveCtx(req.auth.companyId);
|
|
221228
221516
|
const b2 = req.body;
|
|
221229
221517
|
if (!b2?.actorId || !b2.nodeId || !b2.runtimeKind) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 actorId / nodeId / runtimeKind");
|
|
221518
|
+
const targetNode = await opts.resolveNodeTenancy?.(b2.nodeId).catch(() => null) ?? null;
|
|
221519
|
+
const foreign = foreignNodeBlock(targetNode, req.auth.companyId);
|
|
221520
|
+
if (foreign) throw new ApiError(400, "NODE_NOT_IN_COMPANY", foreign);
|
|
221230
221521
|
const { clearedModel } = await service.bind({ actorId: b2.actorId, nodeId: b2.nodeId, runtimeKind: b2.runtimeKind, status: b2.status ?? "active" }, req.auth.actor);
|
|
221231
221522
|
return { status: 201, body: { ok: true, ...clearedModel ? { clearedModel } : {} } };
|
|
221232
221523
|
});
|
|
@@ -221345,6 +221636,8 @@ function actorsDomain(opts) {
|
|
|
221345
221636
|
...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
|
|
221346
221637
|
...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
|
|
221347
221638
|
...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {},
|
|
221639
|
+
// 创建者/更新者取**当前登录人**,不接受 body 传入——自报的「谁建的」等于没有这一格。
|
|
221640
|
+
by: req.auth.actor,
|
|
221348
221641
|
expiresAt: expiresAt2
|
|
221349
221642
|
});
|
|
221350
221643
|
await auditVariableChange(
|
|
@@ -221417,6 +221710,7 @@ function actorsDomain(opts) {
|
|
|
221417
221710
|
...b2.connectorId !== void 0 ? { connectorId: b2.connectorId } : {},
|
|
221418
221711
|
...b2.overrides !== void 0 ? { overrides: b2.overrides } : {},
|
|
221419
221712
|
...b2.encrypted !== void 0 ? { encrypted: b2.encrypted } : {},
|
|
221713
|
+
by: req.auth.actor,
|
|
221420
221714
|
expiresAt: expiresAt2
|
|
221421
221715
|
});
|
|
221422
221716
|
await auditVariableChange(req, "credential_write", b2.key, `personal:${actorId}`);
|
|
@@ -222036,6 +222330,7 @@ var init_routes3 = __esm({
|
|
|
222036
222330
|
init_image_upload();
|
|
222037
222331
|
init_skill_materializer();
|
|
222038
222332
|
init_connector_skills();
|
|
222333
|
+
init_runtime_tenancy();
|
|
222039
222334
|
CREDENTIAL_REVEAL_SOURCES = /* @__PURE__ */ new Set(["agent-cli", "agent-exec", "smoke-test", "oasis-internal", "console"]);
|
|
222040
222335
|
RevealRateLimiter = class {
|
|
222041
222336
|
constructor(perSession, perSessionPerKey, ttlMs = 2 * 60 * 6e4) {
|
|
@@ -222232,7 +222527,7 @@ function createActorsDomain(opts) {
|
|
|
222232
222527
|
return {
|
|
222233
222528
|
service: defaultCtx.service,
|
|
222234
222529
|
resolveCtx,
|
|
222235
|
-
register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {} })
|
|
222530
|
+
register: actorsDomain({ resolveCtx, ...opts.trace ? { trace: opts.trace } : {}, ...opts.listBuiltinSkills ? { listBuiltinSkills: opts.listBuiltinSkills } : {}, ...opts.getBuiltinSkills ? { getBuiltinSkills: opts.getBuiltinSkills } : {}, ...opts.getConnectorSkills ? { getConnectorSkills: opts.getConnectorSkills } : {}, ...opts.refreshConnectorSkills ? { refreshConnectorSkills: opts.refreshConnectorSkills } : {}, ...opts.memory ? { memory: opts.memory } : {}, ...opts.resolveDispatchScope ? { resolveDispatchScope: opts.resolveDispatchScope } : {}, ...opts.projectExists ? { projectExists: opts.projectExists } : {}, ...opts.credentialAudit ? { credentialAudit: opts.credentialAudit } : {}, ...opts.readCredentialAudit ? { readCredentialAudit: opts.readCredentialAudit } : {}, ...opts.credentialRevealLimits ? { credentialRevealLimits: opts.credentialRevealLimits } : {}, ...opts.skillMarket ? { skillMarket: opts.skillMarket } : {}, ...opts.resolveNodeTenancy ? { resolveNodeTenancy: opts.resolveNodeTenancy } : {} })
|
|
222236
222531
|
};
|
|
222237
222532
|
}
|
|
222238
222533
|
var import_node_crypto49;
|
|
@@ -222434,12 +222729,9 @@ function projectsDomain(opts) {
|
|
|
222434
222729
|
}
|
|
222435
222730
|
});
|
|
222436
222731
|
router.get("/api/projects", async (req) => {
|
|
222437
|
-
const inc = req.query?.get("includeEphemeral");
|
|
222438
|
-
const includeEphemeral = inc === "1" || inc === "true";
|
|
222439
222732
|
const uncatRaw = req.query?.get("includeUncategorized");
|
|
222440
222733
|
const includeUncategorized = uncatRaw === null || uncatRaw === void 0 ? void 0 : !(uncatRaw === "0" || uncatRaw === "false");
|
|
222441
222734
|
const projects = await (await svc(req)).listProjects({
|
|
222442
|
-
includeEphemeral,
|
|
222443
222735
|
...includeUncategorized !== void 0 ? { includeUncategorized } : {}
|
|
222444
222736
|
});
|
|
222445
222737
|
const page = paginate(projects.map(toApiProject), req.query);
|
|
@@ -222477,8 +222769,7 @@ function projectsDomain(opts) {
|
|
|
222477
222769
|
body: {
|
|
222478
222770
|
ok: true,
|
|
222479
222771
|
project_id: result.projectId,
|
|
222480
|
-
moved_work_order_ids: result.movedWorkOrderIds
|
|
222481
|
-
carried_git_location_count: result.carriedGitLocationCount
|
|
222772
|
+
moved_work_order_ids: result.movedWorkOrderIds
|
|
222482
222773
|
}
|
|
222483
222774
|
};
|
|
222484
222775
|
} catch (err) {
|
|
@@ -222720,8 +223011,8 @@ ${errs.join("\n")}`);
|
|
|
222720
223011
|
const nulledPatch = "project_id" in b2 && b2.project_id === null || "projectId" in b2 && b2.projectId === null || b2.project_id === "null" || b2.project_id === "" || b2.projectId === "null" || b2.projectId === "";
|
|
222721
223012
|
if (nulledPatch) {
|
|
222722
223013
|
try {
|
|
222723
|
-
|
|
222724
|
-
return { status: 200, body: { work_order_id:
|
|
223014
|
+
await (await svc(req)).unlinkWorkorder(req.params.workOrderId);
|
|
223015
|
+
return { status: 200, body: { work_order_id: req.params.workOrderId, project_id: null } };
|
|
222725
223016
|
} catch (err) {
|
|
222726
223017
|
throw mapProjectError(err);
|
|
222727
223018
|
}
|
|
@@ -222737,8 +223028,8 @@ ${errs.join("\n")}`);
|
|
|
222737
223028
|
});
|
|
222738
223029
|
router.delete("/api/work-orders/:workOrderId/project", async (req) => {
|
|
222739
223030
|
try {
|
|
222740
|
-
|
|
222741
|
-
return { status: 200, body: { work_order_id:
|
|
223031
|
+
await (await svc(req)).unlinkWorkorder(req.params.workOrderId);
|
|
223032
|
+
return { status: 200, body: { work_order_id: req.params.workOrderId, project_id: null } };
|
|
222742
223033
|
} catch (err) {
|
|
222743
223034
|
throw mapProjectError(err);
|
|
222744
223035
|
}
|
|
@@ -223222,6 +223513,9 @@ function toApiProjectFilesView(view) {
|
|
|
223222
223513
|
// 其它外部引用→链接)。交付物整份产物行才有,上传文件与文件夹为 null。
|
|
223223
223514
|
content_kind: item.contentKind ?? null,
|
|
223224
223515
|
content_ref: item.contentRef ?? null,
|
|
223516
|
+
// 上传文件行(项目级上传 / 工单级文件)的 blob 引用 + MIME:前端据此直读 blob 预览。交付物与文件夹为 null。
|
|
223517
|
+
blob_ref: item.blobRef ?? null,
|
|
223518
|
+
content_type: item.contentType ?? null,
|
|
223225
223519
|
size: item.size,
|
|
223226
223520
|
task_id: item.taskId,
|
|
223227
223521
|
task_title: item.taskTitle,
|
|
@@ -223256,7 +223550,7 @@ var init_routes5 = __esm({
|
|
|
223256
223550
|
"use strict";
|
|
223257
223551
|
init_router();
|
|
223258
223552
|
init_src();
|
|
223259
|
-
|
|
223553
|
+
init_uncategorized_project();
|
|
223260
223554
|
}
|
|
223261
223555
|
});
|
|
223262
223556
|
|
|
@@ -228322,13 +228616,15 @@ async function buildChatPreview(chatStore, sessionId, who) {
|
|
|
228322
228616
|
if (lastAsst) lines.push({ label: who, text: clip2(lastAsst.content) });
|
|
228323
228617
|
return lines.length ? { lines } : void 0;
|
|
228324
228618
|
}
|
|
228325
|
-
async function buildChatInbox(chatStore, markers, me, resolveActor) {
|
|
228619
|
+
async function buildChatInbox(chatStore, markers, me, resolveActor, excludeChildSessions) {
|
|
228326
228620
|
const sessions = await chatStore.listSessions(me);
|
|
228327
228621
|
const byScope = new Map(markers.map((m2) => [m2.scope, m2]));
|
|
228328
228622
|
const unread = sessions.filter((s2) => s2.lastTurnQuality !== void 0 && isUnread(s2.touchedAt, byScope.get(chatScope(s2.id)))).sort((a, b2) => a.touchedAt < b2.touchedAt ? 1 : a.touchedAt > b2.touchedAt ? -1 : 0);
|
|
228623
|
+
const excluded = excludeChildSessions ? new Set(await excludeChildSessions(unread.map((s2) => s2.id)).catch(() => [])) : /* @__PURE__ */ new Set();
|
|
228329
228624
|
const out = [];
|
|
228330
228625
|
let previewsFetched = 0;
|
|
228331
228626
|
for (const s2 of unread) {
|
|
228627
|
+
if (excluded.has(s2.id)) continue;
|
|
228332
228628
|
const who = resolveActor?.(s2.aiActorId)?.name ?? "\u52A9\u7406";
|
|
228333
228629
|
const abnormal = s2.lastTurnQuality === "abnormal";
|
|
228334
228630
|
const preview2 = previewsFetched < PREVIEW_FETCH_CAP ? await buildChatPreview(chatStore, s2.id, who) : void 0;
|
|
@@ -228537,6 +228833,9 @@ function toAuditEntry(op) {
|
|
|
228537
228833
|
actor: op.actor,
|
|
228538
228834
|
op: op.kind,
|
|
228539
228835
|
artifactId: op.artifactId,
|
|
228836
|
+
/* 动作名单独发一份(见契约 `AuditEntry.actionLabel`):`summary` 拼着裸 artifactId,
|
|
228837
|
+
不能直接给人看,而「已处理」那一行需要「干了什么 + 在哪个节点」两截并排。 */
|
|
228838
|
+
actionLabel: KIND_LABEL2[op.kind] ?? op.kind,
|
|
228540
228839
|
summary: `${KIND_LABEL2[op.kind] ?? op.kind} \xB7 ${op.artifactId}`
|
|
228541
228840
|
};
|
|
228542
228841
|
}
|
|
@@ -228573,6 +228872,7 @@ function eventToAuditEntry(rec) {
|
|
|
228573
228872
|
actor: rec.actorId,
|
|
228574
228873
|
op: ev.kind,
|
|
228575
228874
|
artifactId,
|
|
228875
|
+
actionLabel: KIND_LABEL2[ev.kind] ?? ev.kind,
|
|
228576
228876
|
summary: `${KIND_LABEL2[ev.kind] ?? ev.kind}${artifactId ? ` \xB7 ${artifactId}` : ""}`
|
|
228577
228877
|
};
|
|
228578
228878
|
}
|
|
@@ -229523,13 +229823,32 @@ async function buildLeadResolver(registry2) {
|
|
|
229523
229823
|
return null;
|
|
229524
229824
|
};
|
|
229525
229825
|
}
|
|
229526
|
-
async function
|
|
229826
|
+
async function memberNameResolver(listMembers, companyId) {
|
|
229827
|
+
if (!listMembers) return void 0;
|
|
229828
|
+
let members;
|
|
229829
|
+
try {
|
|
229830
|
+
members = await listMembers(companyId);
|
|
229831
|
+
} catch (err) {
|
|
229832
|
+
console.warn(`[collab] \u6210\u5458\u6635\u79F0\u8BFB\u53D6\u5931\u8D25\uFF0C\u672C\u6B21\u56DE\u843D\u5458\u5DE5\u6863\u6848\u59D3\u540D: ${String(err)}`);
|
|
229833
|
+
return void 0;
|
|
229834
|
+
}
|
|
229835
|
+
const byActor2 = /* @__PURE__ */ new Map();
|
|
229836
|
+
for (const m2 of members) {
|
|
229837
|
+
const name = m2.displayName?.trim();
|
|
229838
|
+
if (name) byActor2.set(m2.actorId, name);
|
|
229839
|
+
}
|
|
229840
|
+
return (id) => byActor2.get(id);
|
|
229841
|
+
}
|
|
229842
|
+
async function buildResolver(registry2, memberName) {
|
|
229527
229843
|
if (!registry2) return void 0;
|
|
229528
229844
|
const actors = await registry2.listActors();
|
|
229529
229845
|
const byId = new Map(actors.map((a) => [a.id, a]));
|
|
229530
229846
|
return (id) => {
|
|
229531
229847
|
const a = byId.get(id);
|
|
229532
|
-
|
|
229848
|
+
if (!a) return actorRefFallbackOf(id);
|
|
229849
|
+
const fields = actorRefFieldsOf(a);
|
|
229850
|
+
const nickname = memberName?.(id);
|
|
229851
|
+
return nickname ? { ...fields, name: nickname } : fields;
|
|
229533
229852
|
};
|
|
229534
229853
|
}
|
|
229535
229854
|
async function buildProjectResolver(artifacts) {
|
|
@@ -229651,6 +229970,7 @@ function collabDomain(opts) {
|
|
|
229651
229970
|
cache.set(companyId, ctx);
|
|
229652
229971
|
return ctx;
|
|
229653
229972
|
}
|
|
229973
|
+
const resolverFor = async (companyId, registry2) => buildResolver(registry2, await memberNameResolver(opts.listMembers, companyId));
|
|
229654
229974
|
return (router) => {
|
|
229655
229975
|
router.get("/api/organization/usage-summary", async (req) => {
|
|
229656
229976
|
if (!opts.trace) {
|
|
@@ -229695,7 +230015,7 @@ function collabDomain(opts) {
|
|
|
229695
230015
|
});
|
|
229696
230016
|
router.get("/api/workorders", async (req) => {
|
|
229697
230017
|
const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
|
|
229698
|
-
const resolve10 = await
|
|
230018
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
229699
230019
|
const resolveProject = await buildProjectResolver(artifacts);
|
|
229700
230020
|
const dispatchPausedOf = await buildDispatchPausedResolver(kernel.model, artifacts);
|
|
229701
230021
|
const ref2 = (id) => {
|
|
@@ -229718,7 +230038,7 @@ function collabDomain(opts) {
|
|
|
229718
230038
|
if (projectFilter) {
|
|
229719
230039
|
items = items.filter((wo) => {
|
|
229720
230040
|
const bound = wo.projectId;
|
|
229721
|
-
if (isUncategorizedProjectId(projectFilter)) return
|
|
230041
|
+
if (isUncategorizedProjectId(projectFilter)) return bound == null;
|
|
229722
230042
|
return bound === projectFilter;
|
|
229723
230043
|
});
|
|
229724
230044
|
}
|
|
@@ -229726,7 +230046,7 @@ function collabDomain(opts) {
|
|
|
229726
230046
|
});
|
|
229727
230047
|
router.get("/api/workorder-metrics", async (req) => {
|
|
229728
230048
|
const { kernel, registry: registry2, artifacts, engineStore } = await resolveCtx(req.auth.companyId);
|
|
229729
|
-
const resolve10 = await
|
|
230049
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
229730
230050
|
const resolveProject = await buildProjectResolver(artifacts);
|
|
229731
230051
|
const dispatchPausedOf = await buildDispatchPausedResolver(kernel.model, artifacts);
|
|
229732
230052
|
const workorders = buildWorkorderSummaries(kernel.model, resolve10, resolveProject, dispatchPausedOf);
|
|
@@ -229773,7 +230093,7 @@ function collabDomain(opts) {
|
|
|
229773
230093
|
});
|
|
229774
230094
|
router.get("/api/workorders/:id", async (req) => {
|
|
229775
230095
|
const { kernel, oplog, blobs, registry: registry2, artifacts, engineStore } = await resolveCtx(req.auth.companyId);
|
|
229776
|
-
const resolve10 = await
|
|
230096
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
229777
230097
|
const resolveProject = await buildProjectResolver(artifacts);
|
|
229778
230098
|
const dispatchPausedOf = await buildDispatchPausedResolver(kernel.model, artifacts);
|
|
229779
230099
|
const wsArts = [...kernel.model.artifacts.values()].filter((a) => a.workspace === req.params.id);
|
|
@@ -229848,7 +230168,7 @@ function collabDomain(opts) {
|
|
|
229848
230168
|
if (![...kernel.model.artifacts.values()].some((a) => a.workspace === workorderId)) {
|
|
229849
230169
|
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${workorderId}` } } };
|
|
229850
230170
|
}
|
|
229851
|
-
const resolve10 = await
|
|
230171
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
229852
230172
|
const ref2 = (id) => {
|
|
229853
230173
|
const extra = resolve10?.(id);
|
|
229854
230174
|
return {
|
|
@@ -229891,7 +230211,7 @@ function collabDomain(opts) {
|
|
|
229891
230211
|
if (!cardId) {
|
|
229892
230212
|
return { status: 400, body: { error: { code: "bad_request", message: "\u7F3A\u5C11 card \u53C2\u6570\uFF08\u52A8\u6001\u5361 id\uFF09" } } };
|
|
229893
230213
|
}
|
|
229894
|
-
const resolve10 = await
|
|
230214
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
229895
230215
|
const ref2 = (id) => {
|
|
229896
230216
|
const extra = resolve10?.(id);
|
|
229897
230217
|
return {
|
|
@@ -229931,7 +230251,7 @@ function collabDomain(opts) {
|
|
|
229931
230251
|
if (!ledger) {
|
|
229932
230252
|
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${req.params.id}` } } };
|
|
229933
230253
|
}
|
|
229934
|
-
const resolve10 = await
|
|
230254
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
229935
230255
|
const ids2 = /* @__PURE__ */ new Set();
|
|
229936
230256
|
if (ledger.owner) ids2.add(ledger.owner);
|
|
229937
230257
|
if (ledger.manager) ids2.add(ledger.manager);
|
|
@@ -229950,13 +230270,14 @@ function collabDomain(opts) {
|
|
|
229950
230270
|
return { status: 200, body: { ...ledger, actors } };
|
|
229951
230271
|
});
|
|
229952
230272
|
router.get("/api/workorders/:id/files", async (req) => {
|
|
229953
|
-
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
230273
|
+
const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
|
|
229954
230274
|
const ws = req.params.id;
|
|
229955
230275
|
const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
|
|
229956
230276
|
if (!exists) {
|
|
229957
230277
|
return { status: 404, body: { error: { code: "not_found", message: `\u5DE5\u5355\u4E0D\u5B58\u5728: ${ws}` } } };
|
|
229958
230278
|
}
|
|
229959
230279
|
const files = artifacts?.listWorkOrderFiles ? await artifacts.listWorkOrderFiles(ws) : [];
|
|
230280
|
+
const resolve10 = await buildResolver(registry2);
|
|
229960
230281
|
const items = files.map((f2) => ({
|
|
229961
230282
|
id: f2.fileId,
|
|
229962
230283
|
name: f2.name,
|
|
@@ -229966,12 +230287,14 @@ function collabDomain(opts) {
|
|
|
229966
230287
|
updated_at: f2.linkedAt,
|
|
229967
230288
|
blob_ref: f2.blobId,
|
|
229968
230289
|
source_session_id: f2.sourceSessionId ?? null,
|
|
229969
|
-
source_message_id: f2.sourceMessageId ?? null
|
|
230290
|
+
source_message_id: f2.sourceMessageId ?? null,
|
|
230291
|
+
uploaded_by: f2.uploadedBy ?? null,
|
|
230292
|
+
uploader_name: f2.uploadedBy ? resolve10?.(f2.uploadedBy)?.name ?? null : null
|
|
229970
230293
|
}));
|
|
229971
230294
|
return { status: 200, body: { total: items.length, items } };
|
|
229972
230295
|
});
|
|
229973
230296
|
router.post("/api/workorders/:id/files", async (req) => {
|
|
229974
|
-
const { kernel, artifacts } = await resolveCtx(req.auth.companyId);
|
|
230297
|
+
const { kernel, registry: registry2, artifacts } = await resolveCtx(req.auth.companyId);
|
|
229975
230298
|
const ws = req.params.id;
|
|
229976
230299
|
const exists = [...kernel.model.artifacts.values()].some((a) => a.workspace === ws);
|
|
229977
230300
|
if (!exists) {
|
|
@@ -230005,7 +230328,9 @@ function collabDomain(opts) {
|
|
|
230005
230328
|
blobId,
|
|
230006
230329
|
size: bytes2.byteLength,
|
|
230007
230330
|
...contentType ? { contentType } : {},
|
|
230008
|
-
linkedAt
|
|
230331
|
+
linkedAt,
|
|
230332
|
+
// 上传者:项目页「创建人」列与预览副标据此署名(建单带入的那条路记的是建单发起人)。
|
|
230333
|
+
uploadedBy: req.auth.actor
|
|
230009
230334
|
};
|
|
230010
230335
|
try {
|
|
230011
230336
|
await artifacts.addWorkOrderFile(file);
|
|
@@ -230015,6 +230340,7 @@ function collabDomain(opts) {
|
|
|
230015
230340
|
body: { error: { code: "write_failed", message: `\u5199 work_order_files \u5931\u8D25\uFF1A${err instanceof Error ? err.message : String(err)}` } }
|
|
230016
230341
|
};
|
|
230017
230342
|
}
|
|
230343
|
+
const resolve10 = await buildResolver(registry2);
|
|
230018
230344
|
const item = {
|
|
230019
230345
|
id: fileId,
|
|
230020
230346
|
name,
|
|
@@ -230024,7 +230350,9 @@ function collabDomain(opts) {
|
|
|
230024
230350
|
updated_at: linkedAt,
|
|
230025
230351
|
blob_ref: blobId,
|
|
230026
230352
|
source_session_id: null,
|
|
230027
|
-
source_message_id: null
|
|
230353
|
+
source_message_id: null,
|
|
230354
|
+
uploaded_by: req.auth.actor,
|
|
230355
|
+
uploader_name: resolve10?.(req.auth.actor)?.name ?? null
|
|
230028
230356
|
};
|
|
230029
230357
|
return { status: 201, body: { item } };
|
|
230030
230358
|
});
|
|
@@ -230098,7 +230426,7 @@ function collabDomain(opts) {
|
|
|
230098
230426
|
const [leadOf, resolveActor, initialMarkers] = await Promise.all([
|
|
230099
230427
|
buildLeadResolver(registry2),
|
|
230100
230428
|
// 决策 0026 (c):gap 升级也发卡住节点 owner 的人类上级
|
|
230101
|
-
|
|
230429
|
+
resolverFor(req.auth.companyId, registry2),
|
|
230102
230430
|
// 决策 0042:gap reporter 名册 join
|
|
230103
230431
|
myReadMarkers ? myReadMarkers.listMarkers(me) : Promise.resolve([])
|
|
230104
230432
|
// ADR-0086:知会型条目的已读水位
|
|
@@ -230124,7 +230452,8 @@ function collabDomain(opts) {
|
|
|
230124
230452
|
return void 0;
|
|
230125
230453
|
}
|
|
230126
230454
|
})();
|
|
230127
|
-
const
|
|
230455
|
+
const excludeChildSessions = opts.resolveDelegationChildIds ? (ids2) => opts.resolveDelegationChildIds(req.auth.companyId, ids2) : void 0;
|
|
230456
|
+
const chatItems = chatStore && myReadMarkers ? await buildChatInbox(chatStore, markers, me, resolveActor, excludeChildSessions) : [];
|
|
230128
230457
|
let waitingItems = [];
|
|
230129
230458
|
if (engineStore) {
|
|
230130
230459
|
const snaps = (await Promise.all(
|
|
@@ -230370,7 +230699,7 @@ function collabDomain(opts) {
|
|
|
230370
230699
|
let teamMembers;
|
|
230371
230700
|
let lead;
|
|
230372
230701
|
if (registry2) {
|
|
230373
|
-
const resolve10 = await
|
|
230702
|
+
const resolve10 = await resolverFor(req.auth.companyId, registry2);
|
|
230374
230703
|
const meRec = await registry2.getActor(me);
|
|
230375
230704
|
if (meRec?.teamId) {
|
|
230376
230705
|
teamMembers = new Set((await registry2.listActors({ teamId: meRec.teamId })).map((m2) => m2.id));
|
|
@@ -230419,7 +230748,7 @@ var init_collab = __esm({
|
|
|
230419
230748
|
init_workorder_metrics();
|
|
230420
230749
|
init_chat_workorder_files();
|
|
230421
230750
|
init_organization_usage();
|
|
230422
|
-
|
|
230751
|
+
init_uncategorized_project();
|
|
230423
230752
|
init_tags();
|
|
230424
230753
|
init_activity2();
|
|
230425
230754
|
init_node_state();
|
|
@@ -230706,24 +231035,16 @@ function makeSeededWorkorderCreator(deps) {
|
|
|
230706
231035
|
const blobs = company?.blobs ?? deps.blobs;
|
|
230707
231036
|
const registry2 = company?.registry ?? deps.registry;
|
|
230708
231037
|
const artifactState = company ? company.artifactState : deps.artifactState;
|
|
230709
|
-
const
|
|
231038
|
+
const workorderDrafts = company ? company.workorderDrafts : deps.workorderDrafts;
|
|
230710
231039
|
const workspace = input.idempotencyKey ? workspaceForKey(input.idempotencyKey) : genWorkspace();
|
|
230711
231040
|
if (input.idempotencyKey) {
|
|
230712
231041
|
const existing = existingWorkorder(kernel, workspace);
|
|
230713
231042
|
if (existing) return existing;
|
|
230714
231043
|
}
|
|
230715
|
-
if (artifactState &&
|
|
230716
|
-
await
|
|
231044
|
+
if (artifactState && input.projectId) {
|
|
231045
|
+
await bindWorkorderProject({
|
|
230717
231046
|
workOrderId: workspace,
|
|
230718
|
-
|
|
230719
|
-
...input.title ? { title: input.title } : {},
|
|
230720
|
-
// ADR 0207:临时项目的创建人 = 建这张单的**真人**。`humanActorId` 是 B 路径 handler
|
|
230721
|
-
// 从 `x-chat-session-id` 反查填进来的「陪发起人聊出此单的人」,与 brief.owner 同源;
|
|
230722
|
-
// 没有它时只在 `input.actor` 本身是人时才用——剧本由 agent 触发时落 NULL,
|
|
230723
|
-
// 不把执行者写成负责人。
|
|
230724
|
-
...input.humanActorId ? { createdBy: input.humanActorId } : input.actor.startsWith("actor:human:") ? { createdBy: input.actor } : {},
|
|
230725
|
-
createProject: createProject ?? (async ({ id }) => ({ id })),
|
|
230726
|
-
// 上面的守卫保证走不到这个分支
|
|
231047
|
+
projectId: input.projectId,
|
|
230727
231048
|
upsertBinding: (b2) => artifactState.upsertWorkspaceBinding(b2),
|
|
230728
231049
|
...deps.onWarn ? { onWarn: deps.onWarn } : {}
|
|
230729
231050
|
});
|
|
@@ -230755,8 +231076,8 @@ function makeSeededWorkorderCreator(deps) {
|
|
|
230755
231076
|
if (!preview2.endState.ok) {
|
|
230756
231077
|
throw new Error(`\u5267\u672C\u5EFA\u5355\u7EC8\u6001\u6821\u9A8C\u4E0D\u8FC7\uFF1A${preview2.endState.violations.join("\uFF1B")}`);
|
|
230757
231078
|
}
|
|
230758
|
-
if (input.stage === true &&
|
|
230759
|
-
const draft = await
|
|
231079
|
+
if (input.stage === true && workorderDrafts) {
|
|
231080
|
+
const draft = await workorderDrafts.submit({
|
|
230760
231081
|
workspace,
|
|
230761
231082
|
plan: planned.plan,
|
|
230762
231083
|
rootArtifactId: planned.rootArtifactId,
|
|
@@ -230818,7 +231139,7 @@ var init_create_seeded_workorder = __esm({
|
|
|
230818
231139
|
"../server/src/domains/collab/create-seeded-workorder.ts"() {
|
|
230819
231140
|
"use strict";
|
|
230820
231141
|
import_node_crypto57 = require("node:crypto");
|
|
230821
|
-
|
|
231142
|
+
init_uncategorized_project();
|
|
230822
231143
|
init_planner();
|
|
230823
231144
|
enc2 = (s2) => new TextEncoder().encode(s2);
|
|
230824
231145
|
}
|
|
@@ -233630,7 +233951,10 @@ function createChatSessionsDomain(opts) {
|
|
|
233630
233951
|
const kernel = await kernelFor(req);
|
|
233631
233952
|
const engineStore = kernel?.getStore?.();
|
|
233632
233953
|
const snap = engineStore ? await engineStore.transaction((tx) => tx.loadWorkorder(workorderId)) : null;
|
|
233633
|
-
const resolve10 = await buildResolver(
|
|
233954
|
+
const resolve10 = await buildResolver(
|
|
233955
|
+
await registryFor(req),
|
|
233956
|
+
await memberNameResolver(opts.listMembers, req.auth.companyId)
|
|
233957
|
+
);
|
|
233634
233958
|
const ref2 = (id) => {
|
|
233635
233959
|
const extra = resolve10?.(id);
|
|
233636
233960
|
return { id, ...extra?.name ? { name: extra.name } : {}, ...extra?.role ? { role: extra.role } : {}, ...extra?.avatar ? { avatar: extra.avatar } : {} };
|
|
@@ -234058,6 +234382,9 @@ function createChatSessionsDomain(opts) {
|
|
|
234058
234382
|
childSessionId: record8.childSessionId,
|
|
234059
234383
|
expertActorId: record8.expertActorId,
|
|
234060
234384
|
...expert?.name ? { expertName: expert.name } : {},
|
|
234385
|
+
/* 头像与名字同一本名册、同一次查询取出来。缺了它,委派卡与输入框上方状态条只能退回
|
|
234386
|
+
姓名首字的灰圆(发起人 2026-09-10 报的「灰底 D」)——那两处画的是「[头像] 名字 进行中」。 */
|
|
234387
|
+
...expert?.avatar ? { expertAvatar: expert.avatar } : {},
|
|
234061
234388
|
state: record8.state,
|
|
234062
234389
|
...record8.failureKind ? { failureKind: record8.failureKind } : {},
|
|
234063
234390
|
...record8.label ? { label: record8.label } : {},
|
|
@@ -237750,7 +238077,7 @@ function playbooksDomain(opts) {
|
|
|
237750
238077
|
const drafts = opts.resolveWorkorderDrafts ? await opts.resolveWorkorderDrafts(req.auth?.companyId) : opts.workorderDrafts;
|
|
237751
238078
|
if (result.draftId && chatSid && drafts) {
|
|
237752
238079
|
try {
|
|
237753
|
-
await drafts.tagSession(result.draftId, chatSid, opts.liveChat?.runFor(chatSid));
|
|
238080
|
+
await drafts.tagSession(result.draftId, chatSid, opts.liveChat?.runFor(chatSid), result.workspace);
|
|
237754
238081
|
} catch (err) {
|
|
237755
238082
|
console.warn(`[playbooks] tagSession(${result.draftId}) \u5931\u8D25\uFF0C\u4E0D\u963B\u585E\u5EFA\u5355\u54CD\u5E94:`, err);
|
|
237756
238083
|
}
|
|
@@ -238497,7 +238824,10 @@ var init_daemon_adapter = __esm({
|
|
|
238497
238824
|
} else if (msg.type === "session_normalized_event") {
|
|
238498
238825
|
this.confirmDelivery(msg.dispatchId, "started");
|
|
238499
238826
|
const entry = this.pending.get(msg.dispatchId);
|
|
238500
|
-
if (!entry)
|
|
238827
|
+
if (!entry) {
|
|
238828
|
+
if (this.stashEnabled) this.stashFrame(msg.dispatchId, { type: "normalized", event: msg.event });
|
|
238829
|
+
return;
|
|
238830
|
+
}
|
|
238501
238831
|
if (entry.normalizedCbs.length === 0) entry.normalizedBacklog.push(msg.event);
|
|
238502
238832
|
else for (const cb of entry.normalizedCbs) cb(msg.event);
|
|
238503
238833
|
} else if (msg.type === "session_output") {
|
|
@@ -238630,6 +238960,7 @@ var init_daemon_adapter = __esm({
|
|
|
238630
238960
|
let replayOutput;
|
|
238631
238961
|
let replayTelemetry;
|
|
238632
238962
|
let replayLiveEvent;
|
|
238963
|
+
let replayNormalized;
|
|
238633
238964
|
const flushReplay = () => {
|
|
238634
238965
|
while (stashed.frames.length > 0) {
|
|
238635
238966
|
const frame = stashed.frames[0];
|
|
@@ -238641,6 +238972,10 @@ var init_daemon_adapter = __esm({
|
|
|
238641
238972
|
if (!replayTelemetry) return;
|
|
238642
238973
|
stashed.frames.shift();
|
|
238643
238974
|
replayTelemetry(frame.event);
|
|
238975
|
+
} else if (frame.type === "normalized") {
|
|
238976
|
+
if (!replayNormalized) return;
|
|
238977
|
+
stashed.frames.shift();
|
|
238978
|
+
replayNormalized(frame.event);
|
|
238644
238979
|
} else {
|
|
238645
238980
|
if (!replayLiveEvent) return;
|
|
238646
238981
|
stashed.frames.shift();
|
|
@@ -238679,9 +239014,11 @@ var init_daemon_adapter = __esm({
|
|
|
238679
239014
|
},
|
|
238680
239015
|
...supportsNormalizedEvents ? {
|
|
238681
239016
|
onNormalizedProviderEvent(cb) {
|
|
238682
|
-
|
|
239017
|
+
replayNormalized ??= cb;
|
|
239018
|
+
flushReplay();
|
|
238683
239019
|
const backlog = entry.normalizedBacklog.splice(0, entry.normalizedBacklog.length);
|
|
238684
239020
|
for (const e of backlog) cb(e);
|
|
239021
|
+
entry.normalizedCbs.push(cb);
|
|
238685
239022
|
}
|
|
238686
239023
|
} : {},
|
|
238687
239024
|
// ADR-0093 对话追加(重挂句柄同 spawn 句柄)。
|
|
@@ -240228,7 +240565,11 @@ function rowToVariable(row) {
|
|
|
240228
240565
|
...row.expires_at ? { expiresAt: row.expires_at } : {},
|
|
240229
240566
|
...row.last_used_at ? { lastUsedAt: row.last_used_at } : {},
|
|
240230
240567
|
// 展示名(存量行没有 → 不带这一格,展示侧回落成 key)。
|
|
240231
|
-
...row.name ? { name: row.name } : {}
|
|
240568
|
+
...row.name ? { name: row.name } : {},
|
|
240569
|
+
// 创建者 / 创建时间 / 更新者:2026-09-10 之前的行没有 → 不带这一格,展示侧出「—」。
|
|
240570
|
+
...row.created_by ? { createdBy: row.created_by } : {},
|
|
240571
|
+
...row.created_at ? { createdAt: row.created_at } : {},
|
|
240572
|
+
...row.updated_by ? { updatedBy: row.updated_by } : {}
|
|
240232
240573
|
};
|
|
240233
240574
|
}
|
|
240234
240575
|
var ident4, PostgresRegistryStore, rowToActor, rowToConfig, rowToAgentPrefs;
|
|
@@ -240399,6 +240740,9 @@ var init_postgres_registry = __esm({
|
|
|
240399
240740
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS name text`);
|
|
240400
240741
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS expires_at text`);
|
|
240401
240742
|
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS last_used_at text`);
|
|
240743
|
+
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS created_by text`);
|
|
240744
|
+
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS created_at text`);
|
|
240745
|
+
await pool.query(`ALTER TABLE IF EXISTS "${s2}".variables ADD COLUMN IF NOT EXISTS updated_by text`);
|
|
240402
240746
|
await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_key_actor_uq"`);
|
|
240403
240747
|
await pool.query(`DROP INDEX IF EXISTS "${s2}"."${s2}_variables_scope_uq"`);
|
|
240404
240748
|
await pool.query(`
|
|
@@ -240900,11 +241244,17 @@ var init_postgres_registry = __esm({
|
|
|
240900
241244
|
await this.pool.query(
|
|
240901
241245
|
/* `name` 走 COALESCE:**没传 = 不动**。连接器授权流程写变量时手上没有「名称」,
|
|
240902
241246
|
普通赋值会把人在密钥表单里填的名字抹掉——同 connectors.connected_by 那条。 */
|
|
240903
|
-
|
|
240904
|
-
|
|
241247
|
+
/* created_by / created_at 走 COALESCE(**旧值**, 新值):**第一次落行才写**,之后每次 upsert
|
|
241248
|
+
都保留原值——否则「谁建的」会被最后一次改的人顶掉,这一格就没有意义了。
|
|
241249
|
+
updated_by 反过来:每次都覆盖。三者与 name 的 COALESCE 方向相反,别照抄。 */
|
|
241250
|
+
`INSERT INTO ${this.s}.variables (key, scope, actor_id, project_id, connector_id, overrides, delivery_mode, value_encrypted, updated_at, expires_at, name, created_by, created_at, updated_by)
|
|
241251
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
|
|
240905
241252
|
ON CONFLICT (key, COALESCE(actor_id, ''), COALESCE(project_id, '')) DO UPDATE
|
|
240906
241253
|
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,
|
|
240907
|
-
name=COALESCE($11, ${this.s}.variables.name)
|
|
241254
|
+
name=COALESCE($11, ${this.s}.variables.name),
|
|
241255
|
+
created_by=COALESCE(${this.s}.variables.created_by, $12),
|
|
241256
|
+
created_at=COALESCE(${this.s}.variables.created_at, $13),
|
|
241257
|
+
updated_by=$14`,
|
|
240908
241258
|
[
|
|
240909
241259
|
v2.key,
|
|
240910
241260
|
v2.scope,
|
|
@@ -240916,7 +241266,10 @@ var init_postgres_registry = __esm({
|
|
|
240916
241266
|
v2.valueEncrypted,
|
|
240917
241267
|
v2.updatedAt,
|
|
240918
241268
|
v2.expiresAt ?? null,
|
|
240919
|
-
v2.name ?? null
|
|
241269
|
+
v2.name ?? null,
|
|
241270
|
+
v2.createdBy ?? null,
|
|
241271
|
+
v2.createdAt ?? null,
|
|
241272
|
+
v2.updatedBy ?? null
|
|
240920
241273
|
]
|
|
240921
241274
|
);
|
|
240922
241275
|
}
|
|
@@ -241887,8 +242240,10 @@ var init_postgres_projects = __esm({
|
|
|
241887
242240
|
content_type text,
|
|
241888
242241
|
source_session_id text,
|
|
241889
242242
|
source_message_id text,
|
|
241890
|
-
linked_at timestamptz NOT NULL
|
|
242243
|
+
linked_at timestamptz NOT NULL,
|
|
242244
|
+
uploaded_by text
|
|
241891
242245
|
)`);
|
|
242246
|
+
await pool.query(`ALTER TABLE "${s2}".work_order_files ADD COLUMN IF NOT EXISTS uploaded_by text`);
|
|
241892
242247
|
await pool.query(`CREATE INDEX IF NOT EXISTS work_order_files_wo_idx ON "${s2}".work_order_files (work_order_id)`);
|
|
241893
242248
|
return new _PostgresArtifactStateStore(pool, schema);
|
|
241894
242249
|
}
|
|
@@ -242164,15 +242519,19 @@ var init_postgres_projects = __esm({
|
|
|
242164
242519
|
const r = await this.pool.query(`SELECT * FROM ${this.s}.workspace_bindings`);
|
|
242165
242520
|
return r.rows.map(rowToWorkspaceBinding);
|
|
242166
242521
|
}
|
|
242522
|
+
async deleteWorkspaceBinding(workOrderId) {
|
|
242523
|
+
await this.pool.query(`DELETE FROM ${this.s}.workspace_bindings WHERE work_order_id = $1`, [workOrderId]);
|
|
242524
|
+
}
|
|
242167
242525
|
/* ---------- 任务输入文件(PRD §2.6.3 建单交接;薄旁存,非 oplog) ---------- */
|
|
242168
242526
|
async addWorkOrderFile(file) {
|
|
242169
242527
|
await this.pool.query(
|
|
242170
242528
|
`INSERT INTO ${this.s}.work_order_files
|
|
242171
|
-
(file_id, work_order_id, name, blob_id, size, content_type, source_session_id, source_message_id, linked_at)
|
|
242172
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)
|
|
242529
|
+
(file_id, work_order_id, name, blob_id, size, content_type, source_session_id, source_message_id, linked_at, uploaded_by)
|
|
242530
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)
|
|
242173
242531
|
ON CONFLICT (file_id) DO UPDATE SET
|
|
242174
242532
|
work_order_id=$2, name=$3, blob_id=$4, size=$5, content_type=$6,
|
|
242175
|
-
source_session_id=$7, source_message_id=$8, linked_at=$9
|
|
242533
|
+
source_session_id=$7, source_message_id=$8, linked_at=$9,
|
|
242534
|
+
uploaded_by=COALESCE($10, ${this.s}.work_order_files.uploaded_by)`,
|
|
242176
242535
|
[
|
|
242177
242536
|
file.fileId,
|
|
242178
242537
|
file.workOrderId,
|
|
@@ -242182,7 +242541,8 @@ var init_postgres_projects = __esm({
|
|
|
242182
242541
|
file.contentType ?? null,
|
|
242183
242542
|
file.sourceSessionId ?? null,
|
|
242184
242543
|
file.sourceMessageId ?? null,
|
|
242185
|
-
file.linkedAt
|
|
242544
|
+
file.linkedAt,
|
|
242545
|
+
file.uploadedBy ?? null
|
|
242186
242546
|
]
|
|
242187
242547
|
);
|
|
242188
242548
|
}
|
|
@@ -242374,6 +242734,7 @@ var init_postgres_projects = __esm({
|
|
|
242374
242734
|
...row.content_type !== null ? { contentType: row.content_type } : {},
|
|
242375
242735
|
...row.source_session_id !== null ? { sourceSessionId: row.source_session_id } : {},
|
|
242376
242736
|
...row.source_message_id !== null ? { sourceMessageId: row.source_message_id } : {},
|
|
242737
|
+
...row.uploaded_by !== null && row.uploaded_by !== void 0 ? { uploadedBy: row.uploaded_by } : {},
|
|
242377
242738
|
linkedAt: iso2(row.linked_at)
|
|
242378
242739
|
});
|
|
242379
242740
|
}
|
|
@@ -255641,8 +256002,8 @@ var init_cutover_aware_workorder_drafts = __esm({
|
|
|
255641
256002
|
async snapshotForNewRun(d, runId) {
|
|
255642
256003
|
return (await this.pickWritable()).snapshotForNewRun(d, runId);
|
|
255643
256004
|
}
|
|
255644
|
-
async tagSession(id, s2, runId) {
|
|
255645
|
-
return (await this.pickWritable()).tagSession(id, s2, runId);
|
|
256005
|
+
async tagSession(id, s2, runId, expectedWorkspace) {
|
|
256006
|
+
return (await this.pickWritable()).tagSession(id, s2, runId, expectedWorkspace);
|
|
255646
256007
|
}
|
|
255647
256008
|
async markEdited(id) {
|
|
255648
256009
|
return (await this.pickWritable()).markEdited(id);
|
|
@@ -256613,6 +256974,7 @@ var init_src11 = __esm({
|
|
|
256613
256974
|
init_sender();
|
|
256614
256975
|
init_routes7();
|
|
256615
256976
|
init_runtime_drift();
|
|
256977
|
+
init_runtime_tenancy();
|
|
256616
256978
|
init_types4();
|
|
256617
256979
|
init_roles();
|
|
256618
256980
|
init_node_store();
|
|
@@ -256664,7 +257026,7 @@ var init_src11 = __esm({
|
|
|
256664
257026
|
init_connector_skills();
|
|
256665
257027
|
init_connector_tool_versions();
|
|
256666
257028
|
init_board_watch();
|
|
256667
|
-
|
|
257029
|
+
init_uncategorized_project();
|
|
256668
257030
|
init_run_settlement();
|
|
256669
257031
|
init_reconcile_terminal_runs();
|
|
256670
257032
|
init_run_settlement_wiring();
|
|
@@ -258176,6 +258538,9 @@ async function startServe(opts) {
|
|
|
258176
258538
|
let memStoreShared;
|
|
258177
258539
|
let isKnowledgeQueryAvailable = async (_actorId) => false;
|
|
258178
258540
|
const actors = createActorsDomain({
|
|
258541
|
+
// 租户闸(ADR-0164 §5):`POST /api/bindings` 据它拦「把本公司员工绑到别家公司的机器上」。
|
|
258542
|
+
// 闭包读当前 nodeStore(下方 PG 分支会重赋值),与 `listBindingModelIds` 同一写法。
|
|
258543
|
+
resolveNodeTenancy: async (nodeId) => nodeStore.getNode(nodeId),
|
|
258179
258544
|
store: registryStore,
|
|
258180
258545
|
oplog,
|
|
258181
258546
|
kernel: defaultCompanyKernel,
|
|
@@ -258244,7 +258609,10 @@ async function startServe(opts) {
|
|
|
258244
258609
|
prefsStore: humanPrefsStore,
|
|
258245
258610
|
registry: registryStore,
|
|
258246
258611
|
actors: actors.service,
|
|
258247
|
-
|
|
258612
|
+
// 默认公司那一份。**闭包求值**:`defaultCompanyId` 是 let,startup 后才被库里的真实 id 覆盖
|
|
258613
|
+
// (同 :1575 那句「必须是调用时求值」)。
|
|
258614
|
+
companyId: () => defaultCompanyId,
|
|
258615
|
+
listRuntimes: (nodeId, scope) => nodeStore.listRuntimes(nodeId, scope),
|
|
258248
258616
|
isPlatformAdmin: async () => false
|
|
258249
258617
|
// 占位;controlPlaneStore 就绪后由下方 setPlatformAdminChecker 注入真判据
|
|
258250
258618
|
});
|
|
@@ -258536,8 +258904,6 @@ async function startServe(opts) {
|
|
|
258536
258904
|
getSchema: () => schema,
|
|
258537
258905
|
registry: registryStore,
|
|
258538
258906
|
artifactState: artifactStateStore,
|
|
258539
|
-
// 决策 C:剧本建单没给 projectId 时,兜底建一个由工单派生的临时项目并绑上。
|
|
258540
|
-
createProject: makeEnsureProjectFromStore(projectStateStore),
|
|
258541
258907
|
onWarn: (m2) => console.warn(m2),
|
|
258542
258908
|
workorderDrafts,
|
|
258543
258909
|
buildDraftGraph: async (draft) => buildWorkorderDraftDetail(draft, await buildResolver(registryStore), buildTypeReviewerResolver(schema, await registryStore.listActors())).graph,
|
|
@@ -258559,8 +258925,12 @@ async function startServe(opts) {
|
|
|
258559
258925
|
blobs: engine2.blobs,
|
|
258560
258926
|
registry: engine2.registry ?? registryStore,
|
|
258561
258927
|
artifactState: stores.artifacts,
|
|
258562
|
-
//
|
|
258563
|
-
|
|
258928
|
+
// 草案 store 必须按公司拿(2026-09-10 事故):此前它是装配期捕获的共享库实例,
|
|
258929
|
+
// `stage: true` 建出来的草案落大通铺、读侧却按公司解析,本公司永远查不到自己的草案。
|
|
258930
|
+
// ⚠ 这个 helper 的 const 声明在本处之后,只能在**请求时**才求值的闭包里引用
|
|
258931
|
+
// (同上方 resolveChatSession 的写法),直接在装配期引用会撞 TDZ。
|
|
258932
|
+
// planner 阻断项旁账**不在本次范围**,理由见 create-seeded-workorder.ts 里那段注释。
|
|
258933
|
+
workorderDrafts: await workorderDraftsFor(companyId)
|
|
258564
258934
|
};
|
|
258565
258935
|
}
|
|
258566
258936
|
} : {}
|
|
@@ -259716,6 +260086,7 @@ async function startServe(opts) {
|
|
|
259716
260086
|
}
|
|
259717
260087
|
return resolved;
|
|
259718
260088
|
};
|
|
260089
|
+
const listCompanyMembers = (companyId) => controlPlaneStore.listMembers(companyId ?? defaultCompanyId);
|
|
259719
260090
|
const server = await startOasisServer({
|
|
259720
260091
|
deployTargets,
|
|
259721
260092
|
kernel: engine.kernel,
|
|
@@ -259871,7 +260242,9 @@ async function startServe(opts) {
|
|
|
259871
260242
|
prefsStore: plane?.humanPrefs ?? humanPrefsStore,
|
|
259872
260243
|
registry: engine2.registry,
|
|
259873
260244
|
actors: ctx.service,
|
|
259874
|
-
|
|
260245
|
+
// 这一份服务的是**请求方的公司**——助理只在这家的在线 runtime 里挑机器(ADR-0164 §5)。
|
|
260246
|
+
companyId: () => cid,
|
|
260247
|
+
listRuntimes: (nodeId, scope) => nodeStore.listRuntimes(nodeId, scope),
|
|
259875
260248
|
isPlatformAdmin: async (caller) => {
|
|
259876
260249
|
const member = await controlPlaneStore.getMember(cid, caller);
|
|
259877
260250
|
return member?.role === "owner";
|
|
@@ -259961,10 +260334,20 @@ async function startServe(opts) {
|
|
|
259961
260334
|
// 草案图算「类型默认审核人」用(与 /api/cmd 同源类型目录)
|
|
259962
260335
|
roles: roleStore,
|
|
259963
260336
|
// 诊断 f1641dd3-abdaa869:概览角色桶补「默认承接人」(读 RoleDef.defaultOwner)
|
|
260337
|
+
// 真人展示名的第一顺位:本公司成员昵称压过员工档案 name(档案上那份缺省是邮箱)。
|
|
260338
|
+
listMembers: listCompanyMembers,
|
|
259964
260339
|
chatSession: chatSessionStore,
|
|
259965
260340
|
// ADR-0086:chat 知会条目(与 readMarkers 成对)
|
|
259966
260341
|
// ADR「多租户数据面收口」§D2:知会条目按**当前公司**取。装了路由才给(dev 文件模式沿用单实例)。
|
|
259967
260342
|
...chatStoreRouter ? { resolveChatSession: chatStoreFor } : {},
|
|
260343
|
+
/* 委派子会话不进铃铛(发起人 2026-09-10):专家跑完回流到父会话,父会话那条已经通知过一次。
|
|
260344
|
+
判据与左侧会话列表那条路同一个读口(`listChildSessionIds`,见下面 chat-sessions 域的
|
|
260345
|
+
`delegations` 接线),不另造。灰度关着 → 回空集合 = 不滤,与接入前逐字一致。 */
|
|
260346
|
+
resolveDelegationChildIds: async (companyId, sessionIds) => {
|
|
260347
|
+
if (!chatDelegationEnabledFromEnv() || sessionIds.length === 0) return [];
|
|
260348
|
+
const ledger = await delegationStoreFor(companyId).catch(() => null);
|
|
260349
|
+
return ledger ? ledger.listChildSessionIds(sessionIds) : [];
|
|
260350
|
+
},
|
|
259968
260351
|
readMarkers: readMarkerStore,
|
|
259969
260352
|
// bundle=`inbox`:水位按当前公司取(与上面 resolveChatSession 成对)。
|
|
259970
260353
|
...pgPool ? { resolveReadMarkers: readMarkerStoreFor } : {},
|
|
@@ -259991,6 +260374,8 @@ async function startServe(opts) {
|
|
|
259991
260374
|
// 装了路由(有 PG)就只走路由,解析不出公司一律 403,不回落默认公司。
|
|
259992
260375
|
...chatStoreRouter ? { resolveStore: chatStoreFor } : {},
|
|
259993
260376
|
registry: registryStore,
|
|
260377
|
+
// 缺口线署名的真人名字与 collab 读面同一口径:本公司成员昵称压过员工档案 name。
|
|
260378
|
+
listMembers: listCompanyMembers,
|
|
259994
260379
|
// ★ wo:acfee725 评审 blocker ①:两个 `/workdir/*` 读口与 files-view 段按**当前公司**解 binding,
|
|
259995
260380
|
// 与派发、与委派回流搬产物同一处(`resolveCompanyBinding`)。上面那个 `registry` 是全局
|
|
259996
260381
|
// store(`public.actor_bindings`),org-registry cutover 之后它是冻结旧镜像——只留给
|
|
@@ -260176,7 +260561,9 @@ async function startServe(opts) {
|
|
|
260176
260561
|
listProjects: (companyId) => companyId === defaultCompanyId ? projectStateStore.listProjects() : Promise.resolve([]),
|
|
260177
260562
|
listProjectMembers: async (companyId, projectId2) => companyId === defaultCompanyId ? (await projectStateStore.listProjectMembers(projectId2)).flatMap((member) => member.kind === "system" ? [] : [{ actorId: member.actorId, kind: member.kind, status: member.status === "active" ? "active" : "inactive" }]) : [],
|
|
260178
260563
|
getWorkspaceBinding: (companyId, workorderId) => companyId === defaultCompanyId ? artifactStateStore.getWorkspaceBinding(workorderId) : Promise.resolve(null),
|
|
260179
|
-
|
|
260564
|
+
// 非默认公司暂不支持项目级知识作用域(这里只有默认公司的 projectStateStore/名册可读),
|
|
260565
|
+
// 一律降级到组织作用域。旧版这个谓词还兼着"是不是 proj_tmp_ 临时项目",临时项目退役后只剩这一半。
|
|
260566
|
+
isProjectScopeUnsupported: (companyId) => companyId !== defaultCompanyId
|
|
260180
260567
|
},
|
|
260181
260568
|
actor: async (actorId, organizationId) => {
|
|
260182
260569
|
if (actorId === SYSTEM_ACTOR2) return { id: actorId, kind: "system", status: "active" };
|
|
@@ -260681,7 +261068,9 @@ async function startServe(opts) {
|
|
|
260681
261068
|
}
|
|
260682
261069
|
binding = currentBinding;
|
|
260683
261070
|
}
|
|
260684
|
-
const requireDispatchNode = (target) => {
|
|
261071
|
+
const requireDispatchNode = async (target) => {
|
|
261072
|
+
const foreign = foreignNodeBlock(await nodeStore.getNode(target.nodeId).catch(() => null), runCompanyId);
|
|
261073
|
+
if (foreign) throw new ApiError(409, "NODE_NOT_IN_COMPANY", foreign);
|
|
260685
261074
|
const connected = hub?.connectedDaemons().find((d) => d.daemonId === target.nodeId);
|
|
260686
261075
|
if (!connected) {
|
|
260687
261076
|
throw new ApiError(409, "NODE_OFFLINE", `\u7ED1\u5B9A\u8282\u70B9 ${target.nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF`);
|
|
@@ -260691,7 +261080,7 @@ async function startServe(opts) {
|
|
|
260691
261080
|
}
|
|
260692
261081
|
return connected;
|
|
260693
261082
|
};
|
|
260694
|
-
let node2 = requireDispatchNode(binding);
|
|
261083
|
+
let node2 = await requireDispatchNode(binding);
|
|
260695
261084
|
if (!chatRemoteAdapter) {
|
|
260696
261085
|
throw new ApiError(503, "NODE_GATEWAY_NOT_READY", "node-gateway \u5C1A\u672A\u5C31\u7EEA");
|
|
260697
261086
|
}
|
|
@@ -260771,7 +261160,7 @@ async function startServe(opts) {
|
|
|
260771
261160
|
}
|
|
260772
261161
|
if (stableDispatch.target.nodeId !== binding.nodeId || stableDispatch.target.runtimeKind !== binding.runtimeKind) {
|
|
260773
261162
|
binding = { actorId, ...stableDispatch.target, status: "active" };
|
|
260774
|
-
node2 = requireDispatchNode(binding);
|
|
261163
|
+
node2 = await requireDispatchNode(binding);
|
|
260775
261164
|
requireNodeConnectors();
|
|
260776
261165
|
}
|
|
260777
261166
|
}
|
|
@@ -261132,6 +261521,14 @@ async function startServe(opts) {
|
|
|
261132
261521
|
requestId: `project-created:${project.id}`
|
|
261133
261522
|
}, project.id);
|
|
261134
261523
|
});
|
|
261524
|
+
const notifyChatTurnSettled = async (info) => {
|
|
261525
|
+
await server.settleDelegationFromSweep(
|
|
261526
|
+
info.chatSessionId,
|
|
261527
|
+
`\u5B50\u4F1A\u8BDD\u8FD9\u4E00\u8F6E\u88AB\u6062\u590D\u5668\u6536\u53E3\u4E3A ${info.status}\uFF08${info.reason}\uFF09`,
|
|
261528
|
+
info.companyId,
|
|
261529
|
+
{ outcome: info.status === "succeeded" ? "succeeded" : "failed" }
|
|
261530
|
+
);
|
|
261531
|
+
};
|
|
261135
261532
|
const handshakeThrottleOpts = {};
|
|
261136
261533
|
{
|
|
261137
261534
|
const num3 = (name) => {
|
|
@@ -261194,10 +261591,12 @@ async function startServe(opts) {
|
|
|
261194
261591
|
if (hit) return hit;
|
|
261195
261592
|
const sessions = id === defaultCompanyId ? platformChatRecovery : PlatformChatRecoveryStore.open(pgPool, engineSchemaFor(id));
|
|
261196
261593
|
const items = await chatItemsFor(id);
|
|
261594
|
+
const turnsForRecovery = await chatTurnsFor(id).catch(() => void 0);
|
|
261197
261595
|
const plane = {
|
|
261198
261596
|
listRunningAssistantMessages: () => sessions.listRunningAssistantMessages(),
|
|
261199
261597
|
chatStore: sessions,
|
|
261200
261598
|
getTurnForRun: (sid, rid) => sessions.getTurnForRun(sid, rid),
|
|
261599
|
+
...turnsForRecovery ? { settleTurn: (turnId, patch) => turnsForRecovery.settleTurn(turnId, patch) } : {},
|
|
261201
261600
|
...items ? { items } : {}
|
|
261202
261601
|
};
|
|
261203
261602
|
chatRecoveryPlanes.set(id, plane);
|
|
@@ -261223,6 +261622,8 @@ async function startServe(opts) {
|
|
|
261223
261622
|
isWired: (artifactId) => chatLiveSessions.has(`chat::${artifactId}`),
|
|
261224
261623
|
...opts2.stopOnFirstHit ? { stopOnFirstHit: true } : {},
|
|
261225
261624
|
onCompanyError: chatRecoveryCompanyWarn(opts2.label),
|
|
261625
|
+
// 重挂的那一轮收口时当场外报,与清扫拍同一个接收方(见 `notifyChatTurnSettled`)。
|
|
261626
|
+
onTurnSettled: (info) => notifyChatTurnSettled(info),
|
|
261226
261627
|
liveChat,
|
|
261227
261628
|
trace: runRoutedTrace,
|
|
261228
261629
|
traceHealth,
|
|
@@ -261493,14 +261894,12 @@ async function startServe(opts) {
|
|
|
261493
261894
|
本拍是从轮次账本走的(`assistant_run_id` 一直有值),是"这一轮死没死"的权威来源。
|
|
261494
261895
|
`companyId` 由扇出那层填好 —— 台账按公司分 store,少了它会查到默认公司的库。
|
|
261495
261896
|
非委派会话调进去是空转:`settleFromSweep` 按 childSessionId 查不到记录就原样返回。 */
|
|
261496
|
-
|
|
261497
|
-
|
|
261498
|
-
|
|
261499
|
-
|
|
261500
|
-
|
|
261501
|
-
|
|
261502
|
-
);
|
|
261503
|
-
},
|
|
261897
|
+
/* `status` 必须带下去分流。跨重启完成的那一轮走的正是 `succeeded`:
|
|
261898
|
+
`settleRound` 是派发那一刻建的闭包,serve 一重启就随老进程没了。
|
|
261899
|
+
不分流的话,一条**干完活**的委派会被按时钟闭合拍成 failed。
|
|
261900
|
+
接收方与重挂路**同一个**(`notifyChatTurnSettled`)——两条路一处收敛,
|
|
261901
|
+
不会出现「清扫拍收的能通知、重挂收的不能」这种只在重启后显形的差异。 */
|
|
261902
|
+
onTurnSettled: (info) => notifyChatTurnSettled(info),
|
|
261504
261903
|
log: (m2) => console.log(m2),
|
|
261505
261904
|
onCompanyError: (companyId, err) => {
|
|
261506
261905
|
const key = companyId ?? "<default>";
|
|
@@ -262124,6 +262523,8 @@ async function startServe(opts) {
|
|
|
262124
262523
|
checkDispatchable: async ({ actor, binding }) => {
|
|
262125
262524
|
try {
|
|
262126
262525
|
if (!binding.nodeId) return null;
|
|
262526
|
+
const foreign = foreignNodeBlock(await nodeStore.getNode(binding.nodeId).catch(() => null), companyId);
|
|
262527
|
+
if (foreign) return { reason: "node-not-in-company", errorMessage: foreign };
|
|
262127
262528
|
const drift = runtimeDriftBlock(await nodeStore.listRuntimes(binding.nodeId), binding.nodeId, binding.runtimeKind);
|
|
262128
262529
|
if (drift) return drift;
|
|
262129
262530
|
const health = healthOfNode(binding.nodeId);
|
|
@@ -269282,6 +269683,9 @@ ${res.warning}`);
|
|
|
269282
269683
|
...expect !== void 0 ? { expectedVersion: Number(expect) } : {}
|
|
269283
269684
|
});
|
|
269284
269685
|
println(`\u5DF2\u8BB0\u4E0B ${rec.memId}\uFF08v${rec.version}\uFF0C${rec.projectId === null ? "actor \u7EA7\xB7\u8DE8\u9879\u76EE\u901A\u7528" : `\u9879\u76EE ${rec.projectId}`}\uFF09`);
|
|
269686
|
+
if ((flags.get("scope") ?? "actor") === "project" && rec.projectId === null) {
|
|
269687
|
+
println(" \u24D8 \u672C\u8F6E\u89E3\u6790\u4E0D\u51FA\u6240\u5C5E\u9879\u76EE\uFF08\u8FD9\u5F20\u4EFB\u52A1\u6CA1\u6302\u9879\u76EE\uFF09\uFF0C\u5DF2\u6309 actor \u7EA7\u8BB0\u4E0B\u2014\u2014\u60F3\u8BA9\u5B83\u843D\u5230\u9879\u76EE\u540D\u4E0B\uFF0C\u5148\u7ED9\u4EFB\u52A1\u6307\u5B9A\u9879\u76EE\u3002");
|
|
269688
|
+
}
|
|
269285
269689
|
} catch (err) {
|
|
269286
269690
|
const body2 = err instanceof ApiRequestError ? err.body : void 0;
|
|
269287
269691
|
if (body2?.current) {
|
|
@@ -270840,7 +271244,7 @@ function shimScript() {
|
|
|
270840
271244
|
}
|
|
270841
271245
|
|
|
270842
271246
|
// src/index.ts
|
|
270843
|
-
var PKG_VERSION = true ? "2.2.
|
|
271247
|
+
var PKG_VERSION = true ? "2.2.12" : "dev";
|
|
270844
271248
|
var LOCAL_BIN = localBin();
|
|
270845
271249
|
var NPM_PREFIX = npmPrefix();
|
|
270846
271250
|
var INSTANCE = DEFAULT_INSTANCE;
|