oasis_test_v2 2.2.10 → 2.2.11
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 +516 -322
- 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;
|
|
@@ -29324,6 +29386,8 @@ function runBackgroundAssistantChatTurn(deps) {
|
|
|
29324
29386
|
...assistantMsgId ? { messageId: assistantMsgId } : {},
|
|
29325
29387
|
log: log3
|
|
29326
29388
|
});
|
|
29389
|
+
const liveTurn = deps.liveChat && deps.liveHandle ? deps.liveChat.start(deps.chatSessionId, deps.liveHandle, { items: itemLedger }) : void 0;
|
|
29390
|
+
if (assistantMsgId) liveTurn?.setAssistantMessageId(assistantMsgId);
|
|
29327
29391
|
const normalizedSource = typeof deps.session.onNormalizedProviderEvent === "function" ? {
|
|
29328
29392
|
onNormalizedProviderEvent: deps.session.onNormalizedProviderEvent.bind(deps.session),
|
|
29329
29393
|
finishTurn: (signal) => deps.session.finishNormalizedTurn?.(signal)
|
|
@@ -29349,7 +29413,8 @@ function runBackgroundAssistantChatTurn(deps) {
|
|
|
29349
29413
|
let ckptDirty = false;
|
|
29350
29414
|
normalizedSource.onNormalizedProviderEvent((event) => {
|
|
29351
29415
|
try {
|
|
29352
|
-
|
|
29416
|
+
if (liveTurn) liveTurn.applyNormalizedEvent(event);
|
|
29417
|
+
else itemLedger.apply(event);
|
|
29353
29418
|
} catch {
|
|
29354
29419
|
}
|
|
29355
29420
|
ckptDirty = true;
|
|
@@ -29387,34 +29452,59 @@ function runBackgroundAssistantChatTurn(deps) {
|
|
|
29387
29452
|
}
|
|
29388
29453
|
const finalize = async (status) => {
|
|
29389
29454
|
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
|
-
|
|
29455
|
+
try {
|
|
29456
|
+
await checkpointChain.catch(() => void 0);
|
|
29457
|
+
normalizedSource.finishTurn(status === "done" ? "completed" : "failed");
|
|
29458
|
+
itemLedger.finish(status === "done" ? "completed" : "failed");
|
|
29459
|
+
await itemLedger.drain();
|
|
29460
|
+
const projectedContent = itemLedger.projectContent();
|
|
29461
|
+
const parts = collectedParts();
|
|
29462
|
+
await finalizeAssistantRow({
|
|
29463
|
+
chatStore: deps.chatStore,
|
|
29464
|
+
chatSessionId: deps.chatSessionId,
|
|
29465
|
+
assistantMsgId,
|
|
29466
|
+
content: projectedContent,
|
|
29467
|
+
parts,
|
|
29468
|
+
status,
|
|
29469
|
+
...runId ? { runId } : {},
|
|
29470
|
+
now
|
|
29471
|
+
});
|
|
29472
|
+
if (typeof deps.chatStore.updateSession === "function") {
|
|
29473
|
+
try {
|
|
29474
|
+
await deps.chatStore.updateSession(deps.chatSessionId, {
|
|
29475
|
+
runtimeSessionId: deps.session.nativeSessionId ?? null,
|
|
29476
|
+
touchedAt: now()
|
|
29477
|
+
});
|
|
29478
|
+
} catch {
|
|
29479
|
+
}
|
|
29480
|
+
}
|
|
29481
|
+
if (liveTurn) {
|
|
29482
|
+
if (projectedContent) liveTurn.emit({ type: "text", text: projectedContent });
|
|
29483
|
+
liveTurn.emitLive({
|
|
29484
|
+
protocolVersion: 2,
|
|
29485
|
+
streamId: `background:${deps.chatSessionId}`,
|
|
29486
|
+
turnId: deps.turnId ?? fallbackTurnId(runId),
|
|
29487
|
+
itemId: `background:${assistantMsgId ?? runId}`,
|
|
29488
|
+
itemType: "message",
|
|
29489
|
+
operation: "completed",
|
|
29490
|
+
payload: { role: "assistant", text: projectedContent }
|
|
29411
29491
|
});
|
|
29492
|
+
}
|
|
29493
|
+
try {
|
|
29494
|
+
deps.onSettled?.();
|
|
29412
29495
|
} catch {
|
|
29413
29496
|
}
|
|
29414
|
-
}
|
|
29415
|
-
|
|
29416
|
-
|
|
29417
|
-
|
|
29497
|
+
} finally {
|
|
29498
|
+
liveTurn?.emit({ type: "done" });
|
|
29499
|
+
liveTurn?.emitLive({
|
|
29500
|
+
protocolVersion: 2,
|
|
29501
|
+
streamId: `background:${deps.chatSessionId}`,
|
|
29502
|
+
turnId: deps.turnId ?? fallbackTurnId(runId),
|
|
29503
|
+
itemId: "background:terminal",
|
|
29504
|
+
itemType: "control",
|
|
29505
|
+
operation: status === "done" ? "turn_completed" : "turn_failed"
|
|
29506
|
+
});
|
|
29507
|
+
liveTurn?.finish(status);
|
|
29418
29508
|
}
|
|
29419
29509
|
};
|
|
29420
29510
|
void Promise.resolve(deps.session.done).then(() => finalize("done").catch(() => void 0)).catch((err) => {
|
|
@@ -142333,71 +142423,6 @@ var init_collect = __esm({
|
|
|
142333
142423
|
}
|
|
142334
142424
|
});
|
|
142335
142425
|
|
|
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
142426
|
// ../server/src/domains/collab/timeline.ts
|
|
142402
142427
|
function buildNodeTimeline(artifactId, snap, runIdByTarget) {
|
|
142403
142428
|
if (!snap) return { artifactId, items: [] };
|
|
@@ -159818,8 +159843,9 @@ var init_live_chat = __esm({
|
|
|
159818
159843
|
DEFAULT_LIVE_COALESCE_MS = 24;
|
|
159819
159844
|
DEFAULT_LIVE_COALESCE_MAX_CHUNKS = 32;
|
|
159820
159845
|
DEFAULT_LIVE_COALESCE_MAX_CHARS = 64 * 1024;
|
|
159821
|
-
LiveChatRegistry = class {
|
|
159846
|
+
LiveChatRegistry = class _LiveChatRegistry {
|
|
159822
159847
|
turns = /* @__PURE__ */ new Map();
|
|
159848
|
+
turnStartWaiters = /* @__PURE__ */ new Map();
|
|
159823
159849
|
epochs = /* @__PURE__ */ new Map();
|
|
159824
159850
|
bufferMax;
|
|
159825
159851
|
graceMs;
|
|
@@ -159865,6 +159891,15 @@ var init_live_chat = __esm({
|
|
|
159865
159891
|
v3Items: /* @__PURE__ */ new Map()
|
|
159866
159892
|
};
|
|
159867
159893
|
this.turns.set(chatSessionId, turn);
|
|
159894
|
+
this.flushPendingUserItems(chatSessionId);
|
|
159895
|
+
const waiters = this.turnStartWaiters.get(chatSessionId);
|
|
159896
|
+
this.turnStartWaiters.delete(chatSessionId);
|
|
159897
|
+
for (const wake of waiters ?? []) {
|
|
159898
|
+
try {
|
|
159899
|
+
wake();
|
|
159900
|
+
} catch {
|
|
159901
|
+
}
|
|
159902
|
+
}
|
|
159868
159903
|
return {
|
|
159869
159904
|
runtimeSessionId: turn.runtimeSessionId,
|
|
159870
159905
|
epoch: turn.liveEpoch,
|
|
@@ -159937,6 +159972,26 @@ var init_live_chat = __esm({
|
|
|
159937
159972
|
...opts.items ? { items: opts.items } : {}
|
|
159938
159973
|
};
|
|
159939
159974
|
}
|
|
159975
|
+
/** 只通知开轮,不承载内容。先检查再登记均为同步,避免空闲检查与订阅之间漏掉开轮。 */
|
|
159976
|
+
onNextTurn(chatSessionId, wake) {
|
|
159977
|
+
if (this.isRunning(chatSessionId)) {
|
|
159978
|
+
wake();
|
|
159979
|
+
return () => {
|
|
159980
|
+
};
|
|
159981
|
+
}
|
|
159982
|
+
let waiters = this.turnStartWaiters.get(chatSessionId);
|
|
159983
|
+
if (!waiters) {
|
|
159984
|
+
waiters = /* @__PURE__ */ new Set();
|
|
159985
|
+
this.turnStartWaiters.set(chatSessionId, waiters);
|
|
159986
|
+
}
|
|
159987
|
+
waiters.add(wake);
|
|
159988
|
+
return () => {
|
|
159989
|
+
waiters.delete(wake);
|
|
159990
|
+
if (waiters.size === 0 && this.turnStartWaiters.get(chatSessionId) === waiters) {
|
|
159991
|
+
this.turnStartWaiters.delete(chatSessionId);
|
|
159992
|
+
}
|
|
159993
|
+
};
|
|
159994
|
+
}
|
|
159940
159995
|
/**
|
|
159941
159996
|
* 归一层中间事件 → 带 `itemId` 的 item op(ADR D4 第 2 跳)+ v3 帧 dual-emit(S3)。
|
|
159942
159997
|
*
|
|
@@ -160164,9 +160219,34 @@ var init_live_chat = __esm({
|
|
|
160164
160219
|
* (`server.ts` 里 `recordUserSubmission` 在 `liveChat.start` 之前)。拿不到就不发——
|
|
160165
160220
|
* 前端退回「等下一次快照」,与修复前一致,不会更坏。
|
|
160166
160221
|
*/
|
|
160222
|
+
/**
|
|
160223
|
+
* 「user item 已落库、但这条会话的 live 轮还没 start」时的暂存位(见 {@link publishUserItem})。
|
|
160224
|
+
*
|
|
160225
|
+
* 每会话只留最近几条:它的唯一用途是补发**当前这一次提交**,不是可靠队列。留太多等于把
|
|
160226
|
+
* 一条早就过期的 user 帧在下一轮开头推出去,那比丢了更糟(用户会看到上一轮的话冒出来)。
|
|
160227
|
+
*/
|
|
160228
|
+
pendingUserItems = /* @__PURE__ */ new Map();
|
|
160229
|
+
/** 单会话暂存上限。正常只会有 1 条(一次提交一条);给到 4 是为了并发提交时不至于丢。 */
|
|
160230
|
+
static PENDING_USER_ITEM_MAX = 4;
|
|
160231
|
+
bufferUserItem(chatSessionId, item) {
|
|
160232
|
+
const list2 = this.pendingUserItems.get(chatSessionId) ?? [];
|
|
160233
|
+
list2.push(item);
|
|
160234
|
+
while (list2.length > _LiveChatRegistry.PENDING_USER_ITEM_MAX) list2.shift();
|
|
160235
|
+
this.pendingUserItems.set(chatSessionId, list2);
|
|
160236
|
+
}
|
|
160237
|
+
/** `start` 之后立刻调:把暂存的 user item 补发出去。发不出去就丢掉,不再往下一轮带。 */
|
|
160238
|
+
flushPendingUserItems(chatSessionId) {
|
|
160239
|
+
const list2 = this.pendingUserItems.get(chatSessionId);
|
|
160240
|
+
if (!list2?.length) return;
|
|
160241
|
+
this.pendingUserItems.delete(chatSessionId);
|
|
160242
|
+
for (const item of list2) this.publishUserItem(chatSessionId, item);
|
|
160243
|
+
}
|
|
160167
160244
|
publishUserItem(chatSessionId, item) {
|
|
160168
160245
|
const turn = this.turns.get(chatSessionId);
|
|
160169
|
-
if (!turn)
|
|
160246
|
+
if (!turn) {
|
|
160247
|
+
this.bufferUserItem(chatSessionId, item);
|
|
160248
|
+
return false;
|
|
160249
|
+
}
|
|
160170
160250
|
const payload = { role: "user", text: item.text };
|
|
160171
160251
|
if (item.clientSubmitId) payload.clientSubmitId = item.clientSubmitId;
|
|
160172
160252
|
if (item.attachments?.length) payload.attachments = item.attachments;
|
|
@@ -160587,6 +160667,40 @@ var init_live_chat = __esm({
|
|
|
160587
160667
|
}
|
|
160588
160668
|
});
|
|
160589
160669
|
|
|
160670
|
+
// ../server/src/chat-turn-wait.ts
|
|
160671
|
+
function waitForChatTurn(res, liveChat, sessionId, startKeepalive, ready = false) {
|
|
160672
|
+
res.writeHead(200, {
|
|
160673
|
+
"content-type": "application/x-oasis-live-v3+ndjson; charset=utf-8",
|
|
160674
|
+
"cache-control": "no-cache, no-transform",
|
|
160675
|
+
"x-accel-buffering": "no",
|
|
160676
|
+
"access-control-expose-headers": "X-Chat-Session-Watch, X-Chat-Waiting, X-Can-Append",
|
|
160677
|
+
"x-chat-session-watch": "1",
|
|
160678
|
+
"x-chat-waiting": "1",
|
|
160679
|
+
"x-can-append": "0"
|
|
160680
|
+
});
|
|
160681
|
+
res.flushHeaders();
|
|
160682
|
+
const stopKeepalive = startKeepalive(res);
|
|
160683
|
+
let detach = () => {
|
|
160684
|
+
};
|
|
160685
|
+
const cleanup = () => {
|
|
160686
|
+
stopKeepalive();
|
|
160687
|
+
detach();
|
|
160688
|
+
};
|
|
160689
|
+
res.once("close", cleanup);
|
|
160690
|
+
const wake = () => {
|
|
160691
|
+
cleanup();
|
|
160692
|
+
if (!res.destroyed) res.end('{"type":"turn_available"}\n');
|
|
160693
|
+
};
|
|
160694
|
+
if (ready) wake();
|
|
160695
|
+
else detach = liveChat.onNextTurn(sessionId, wake);
|
|
160696
|
+
if (res.destroyed) cleanup();
|
|
160697
|
+
}
|
|
160698
|
+
var init_chat_turn_wait = __esm({
|
|
160699
|
+
"../server/src/chat-turn-wait.ts"() {
|
|
160700
|
+
"use strict";
|
|
160701
|
+
}
|
|
160702
|
+
});
|
|
160703
|
+
|
|
160590
160704
|
// ../server/src/chat-attachment-files.ts
|
|
160591
160705
|
function safeAttachmentName(raw) {
|
|
160592
160706
|
return (raw || "attachment").replace(/[^\w.\-]+/g, "_").replace(/^\.+/, "").slice(0, 80) || "attachment";
|
|
@@ -160784,13 +160898,24 @@ var init_closure_report = __esm({
|
|
|
160784
160898
|
});
|
|
160785
160899
|
|
|
160786
160900
|
// ../server/src/governance/workorder-drafts.ts
|
|
160787
|
-
var import_node_fs5, DRAFT_EDIT_LOCK_TTL_MS, WorkorderDraftStore, ident, seqNoOf, PostgresWorkorderDraftStore;
|
|
160901
|
+
var import_node_fs5, DRAFT_EDIT_LOCK_TTL_MS, WorkorderDraftTagMismatchError, WorkorderDraftStore, ident, seqNoOf, PostgresWorkorderDraftStore;
|
|
160788
160902
|
var init_workorder_drafts = __esm({
|
|
160789
160903
|
"../server/src/governance/workorder-drafts.ts"() {
|
|
160790
160904
|
"use strict";
|
|
160791
160905
|
import_node_fs5 = __toESM(require("node:fs"), 1);
|
|
160792
160906
|
init_closure_report();
|
|
160793
160907
|
DRAFT_EDIT_LOCK_TTL_MS = 12e4;
|
|
160908
|
+
WorkorderDraftTagMismatchError = class extends Error {
|
|
160909
|
+
constructor(draftId, expectedWorkspace, actualWorkspace) {
|
|
160910
|
+
super(
|
|
160911
|
+
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`
|
|
160912
|
+
);
|
|
160913
|
+
this.draftId = draftId;
|
|
160914
|
+
this.expectedWorkspace = expectedWorkspace;
|
|
160915
|
+
this.actualWorkspace = actualWorkspace;
|
|
160916
|
+
this.name = "WorkorderDraftTagMismatchError";
|
|
160917
|
+
}
|
|
160918
|
+
};
|
|
160794
160919
|
WorkorderDraftStore = class {
|
|
160795
160920
|
constructor(file) {
|
|
160796
160921
|
this.file = file;
|
|
@@ -160894,14 +161019,28 @@ var init_workorder_drafts = __esm({
|
|
|
160894
161019
|
}
|
|
160895
161020
|
/** 给草案打上 chat 会话标(建单经 chat 路时,/api/cmd 拿到 X-Chat-Session-Id 后调)。
|
|
160896
161021
|
* 同时(若拿得到)盖上产出它的那一轮 runId,供 friday 重进对话页把该版草案归位到那条助手消息。
|
|
160897
|
-
* sourceRunId 一旦盖上不再改(同一版草案就属于那一轮)。
|
|
160898
|
-
|
|
161022
|
+
* sourceRunId 一旦盖上不再改(同一版草案就属于那一轮)。
|
|
161023
|
+
*
|
|
161024
|
+
* **`expectedWorkspace` 是防跨库改绑的那道闸(2026-09-10 线上事故)**:草案 id 形如
|
|
161025
|
+
* `wodraft:<n>`,而**每个 schema 各有一台发号器**(共享库一台、每家公司各一台,见
|
|
161026
|
+
* `PostgresWorkorderDraftStore.open` 建的 `workorder_draft_seq`)。于是「共享库的 70 号」和
|
|
161027
|
+
* 「didi 库的 70 号」是两份毫不相干的草案。一旦建草案与打标落在**不同的 store**上
|
|
161028
|
+
* (公司上下文解析不出来 → 建落共享库;打标按公司解析 → 打进公司库),这里就会拿着同一个
|
|
161029
|
+
* 号在另一个库里翻出**别人的草案**,把它的 chatSessionId / sourceRunId 覆盖成本次会话——
|
|
161030
|
+
* 别人的建单卡就这样出现在你的对话里,而原归属被覆盖后再也找不回来。
|
|
161031
|
+
*
|
|
161032
|
+
* 所以调用方**必须**把「我刚建的那张单的 workspace」一起传进来:对不上就抛,绝不写。
|
|
161033
|
+
* 查不到 id 同理——旧实现在这里是 `if (d) {}` 的静默 no-op,那正是这次事故连一行日志都没留下的原因。
|
|
161034
|
+
* 抛出的是 {@link WorkorderDraftTagMismatchError},调用方该**记账后继续**(草案本身已经建好,
|
|
161035
|
+
* 不该因为打标失败把整个建单回执变成失败),见 `/api/cmd` 与 playbooks 两处调用点。 */
|
|
161036
|
+
async tagSession(draftId, chatSessionId, sourceRunId, expectedWorkspace) {
|
|
160899
161037
|
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 });
|
|
161038
|
+
if (!d || expectedWorkspace !== void 0 && d.workspace !== expectedWorkspace) {
|
|
161039
|
+
throw new WorkorderDraftTagMismatchError(draftId, expectedWorkspace, d?.workspace);
|
|
160904
161040
|
}
|
|
161041
|
+
d.chatSessionId = chatSessionId;
|
|
161042
|
+
if (sourceRunId && !d.sourceRunId) d.sourceRunId = sourceRunId;
|
|
161043
|
+
await this.persist({ kind: "upsert", draft: d });
|
|
160905
161044
|
}
|
|
160906
161045
|
/** 草案被就地编辑后(editDraftPlan 改了 plan.ops)调用:bump `at` 让 friday 重载据此察觉,
|
|
160907
161046
|
* 并**落盘**——否则编辑过的草案在 apply 前进程重启就丢改动(草案落盘的初衷正是扛重启)。 */
|
|
@@ -161694,9 +161833,14 @@ var init_assistants = __esm({
|
|
|
161694
161833
|
await this.opts.actors.disableActor(agentId, by).catch(() => {
|
|
161695
161834
|
});
|
|
161696
161835
|
}
|
|
161836
|
+
/** 本服务的公司 scope;未启用公司维度(单租户装配/单测)时 undefined = 不过滤,行为不变。 */
|
|
161837
|
+
scope() {
|
|
161838
|
+
const companyId = this.opts.companyId();
|
|
161839
|
+
return companyId ? { companyId } : void 0;
|
|
161840
|
+
}
|
|
161697
161841
|
/* ---------- runtime 挑选(ADR §3.3.a):负载均衡 + 幂等 tie-break ---------- */
|
|
161698
161842
|
async pickAssistantRuntime() {
|
|
161699
|
-
const runtimes = (await this.opts.listRuntimes()).filter((r) => r.status === "online");
|
|
161843
|
+
const runtimes = (await this.opts.listRuntimes(void 0, this.scope())).filter((r) => r.status === "online");
|
|
161700
161844
|
if (runtimes.length === 0) return null;
|
|
161701
161845
|
let defaultKind = DEFAULT_ASSISTANT_RUNTIME_KIND;
|
|
161702
161846
|
for (const r of runtimes) {
|
|
@@ -161723,7 +161867,7 @@ var init_assistants = __esm({
|
|
|
161723
161867
|
if (!actor.roles.includes("assistant")) return false;
|
|
161724
161868
|
const binding = await this.opts.registry.getBinding(agentId);
|
|
161725
161869
|
if (!binding || binding.status !== "active") return false;
|
|
161726
|
-
const runtime = (await this.opts.listRuntimes(binding.nodeId)).find((r) => r.kind === binding.runtimeKind);
|
|
161870
|
+
const runtime = (await this.opts.listRuntimes(binding.nodeId, this.scope())).find((r) => r.kind === binding.runtimeKind);
|
|
161727
161871
|
if (!runtime || runtime.status !== "online") return false;
|
|
161728
161872
|
return true;
|
|
161729
161873
|
}
|
|
@@ -186756,12 +186900,13 @@ var init_governance_service = __esm({
|
|
|
186756
186900
|
const binding = await this.options.projects.getWorkspaceBinding(ctx.organizationId, workorderId);
|
|
186757
186901
|
if (!binding) {
|
|
186758
186902
|
if (claimedProjectId) throw new KnowledgeGovernanceError(409, "KNOWLEDGE_PROJECT_BINDING_MISSING", "\u5DE5\u5355\u6CA1\u6709\u53EF\u4FE1\u9879\u76EE\u7ED1\u5B9A");
|
|
186903
|
+
await this.requireOrganizationActor(ctx);
|
|
186759
186904
|
return void 0;
|
|
186760
186905
|
}
|
|
186761
186906
|
if (claimedProjectId && binding.projectId !== claimedProjectId) {
|
|
186762
186907
|
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_PROJECT_MISMATCH", "\u8BF7\u6C42\u9879\u76EE\u4E0E\u5DE5\u5355\u7ED1\u5B9A\u4E0D\u4E00\u81F4");
|
|
186763
186908
|
}
|
|
186764
|
-
if (this.options.projects.
|
|
186909
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, binding.projectId)) {
|
|
186765
186910
|
await this.requireOrganizationActor(ctx);
|
|
186766
186911
|
return void 0;
|
|
186767
186912
|
}
|
|
@@ -186969,8 +187114,8 @@ var init_governance_service = __esm({
|
|
|
186969
187114
|
const projectId2 = input.scopeType === "project" ? input.scopeId : void 0;
|
|
186970
187115
|
if (input.scopeType === "project") {
|
|
186971
187116
|
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", "\
|
|
187117
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2)) {
|
|
187118
|
+
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_EPHEMERAL_PROJECT", "\u8BE5\u9879\u76EE\u4E0D\u652F\u6301\u72EC\u7ACB\u77E5\u8BC6 Space");
|
|
186974
187119
|
}
|
|
186975
187120
|
await this.authorizeProjectResource(ctx, projectId2, "configure");
|
|
186976
187121
|
} else if (input.scopeId) {
|
|
@@ -187096,7 +187241,7 @@ var init_governance_service = __esm({
|
|
|
187096
187241
|
async reconfigureKnowledgeTree(ctx, store, organizationSpace, connection, _config) {
|
|
187097
187242
|
const projectEntries = [];
|
|
187098
187243
|
for (const project of await this.options.projects.listProjects(ctx.organizationId)) {
|
|
187099
|
-
if (this.options.projects.
|
|
187244
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, project.id)) continue;
|
|
187100
187245
|
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
187246
|
if (projectSpace) projectEntries.push({ project, space: projectSpace });
|
|
187102
187247
|
}
|
|
@@ -187315,8 +187460,8 @@ var init_governance_service = __esm({
|
|
|
187315
187460
|
async resolveRunTarget(ctx, projectId2) {
|
|
187316
187461
|
await this.requireOrganizationActor(ctx);
|
|
187317
187462
|
if (projectId2) {
|
|
187318
|
-
if (this.options.projects.
|
|
187319
|
-
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_EPHEMERAL_PROJECT", "\
|
|
187463
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2)) {
|
|
187464
|
+
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
187465
|
}
|
|
187321
187466
|
await this.authorizeProjectResource(ctx, projectId2, "publish");
|
|
187322
187467
|
}
|
|
@@ -187532,7 +187677,7 @@ var init_governance_service = __esm({
|
|
|
187532
187677
|
if (actor.kind !== "system") await this.requireOrganizationAdmin(ctx);
|
|
187533
187678
|
const project = await this.options.projects.getProject(ctx.organizationId, projectId2);
|
|
187534
187679
|
if (!project) throw new KnowledgeGovernanceError(404, "KNOWLEDGE_PROJECT_NOT_FOUND", "\u9879\u76EE\u4E0D\u5B58\u5728");
|
|
187535
|
-
if (this.options.projects.
|
|
187680
|
+
if (this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2)) return null;
|
|
187536
187681
|
const store = await this.options.storeFor(ctx.organizationId);
|
|
187537
187682
|
const scope = this.projectScope(ctx, projectId2);
|
|
187538
187683
|
const existingSpaces = await store.list(scope, "space");
|
|
@@ -190232,7 +190377,7 @@ var init_governance_service = __esm({
|
|
|
190232
190377
|
if (event.claimedProjectId !== trusted.projectId) {
|
|
190233
190378
|
throw new KnowledgeGovernanceError(409, "KNOWLEDGE_PROJECT_MISMATCH", "\u8BF7\u6C42\u9879\u76EE\u4E0E\u53EF\u4FE1\u6765\u6E90\u5F52\u5C5E\u4E0D\u4E00\u81F4");
|
|
190234
190379
|
}
|
|
190235
|
-
const targetProjectId = trusted.projectId && !this.options.projects.
|
|
190380
|
+
const targetProjectId = trusted.projectId && !this.options.projects.isProjectScopeUnsupported(ctx.organizationId, trusted.projectId) ? trusted.projectId : void 0;
|
|
190236
190381
|
const discoveryActor = await this.requireActiveActor(ctx.actorId, ctx.organizationId);
|
|
190237
190382
|
if (discoveryActor.kind !== "system") await this.requireOrganizationActor(ctx);
|
|
190238
190383
|
if (targetProjectId) {
|
|
@@ -190498,7 +190643,7 @@ var init_governance_service = __esm({
|
|
|
190498
190643
|
runs.push(...await this.enqueueDiscoveryEvent(ctx, event, runtime ?? void 0));
|
|
190499
190644
|
} catch (error2) {
|
|
190500
190645
|
const store = await this.options.storeFor(ctx.organizationId);
|
|
190501
|
-
const taskProjectId = projectId2 && !this.options.projects.
|
|
190646
|
+
const taskProjectId = projectId2 && !this.options.projects.isProjectScopeUnsupported(ctx.organizationId, projectId2) ? projectId2 : void 0;
|
|
190502
190647
|
const scope = this.scopeFor(ctx, taskProjectId);
|
|
190503
190648
|
const task = this.governanceTask(ctx, "discovery_failed", event.sourceType, `${event.sourceId}:${event.sourceVersionId}`, {
|
|
190504
190649
|
eventType: event.eventType,
|
|
@@ -202522,20 +202667,6 @@ async function runCommand(kernel, blobs, oplog, engineStore, actor, command, arg
|
|
|
202522
202667
|
const project = await ctx.projectState.getProject(projectId2);
|
|
202523
202668
|
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
202524
202669
|
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
202670
|
}
|
|
202540
202671
|
if (schemaForSeed) {
|
|
202541
202672
|
const title = optStr(args, "title") ?? optStr(args, "description") ?? workspace;
|
|
@@ -204027,6 +204158,18 @@ async function startOasisServer(opts) {
|
|
|
204027
204158
|
chatStore: store,
|
|
204028
204159
|
chatSessionId: sessionId,
|
|
204029
204160
|
session: bgSession,
|
|
204161
|
+
liveChat,
|
|
204162
|
+
liveHandle: {
|
|
204163
|
+
runtimeSessionId: session.id,
|
|
204164
|
+
...session.runId ? { runId: session.runId } : {},
|
|
204165
|
+
kill: () => {
|
|
204166
|
+
void session.kill?.();
|
|
204167
|
+
},
|
|
204168
|
+
...session.appendInput ? { appendInput: (input) => session.appendInput(input) } : {},
|
|
204169
|
+
get canAppendInput() {
|
|
204170
|
+
return typeof session.appendInput === "function" && session.canAppendInput !== false;
|
|
204171
|
+
}
|
|
204172
|
+
},
|
|
204030
204173
|
...itemStore ? { itemStore } : {},
|
|
204031
204174
|
// 广播/委派回流带上 held turn id(B3);授权卡入口在别处,那条路本轮不接账本。
|
|
204032
204175
|
...turnId ? { turnId } : {}
|
|
@@ -204585,7 +204728,15 @@ async function startOasisServer(opts) {
|
|
|
204585
204728
|
if (body2.command === "spawn" && wodrafts) {
|
|
204586
204729
|
const draftId = result?.data?.draftId;
|
|
204587
204730
|
const chatSid = req.headers["x-chat-session-id"];
|
|
204588
|
-
if (draftId && typeof chatSid === "string")
|
|
204731
|
+
if (draftId && typeof chatSid === "string") {
|
|
204732
|
+
const draftWorkspace = result?.data?.workspace;
|
|
204733
|
+
await wodrafts.tagSession(draftId, chatSid, liveChat.runFor(chatSid), draftWorkspace).catch((err) => {
|
|
204734
|
+
console.error(
|
|
204735
|
+
`[workorder-draft] \u8349\u6848 ${draftId}\uFF08ws=${String(draftWorkspace)}\uFF09\u672A\u80FD\u5173\u8054\u4F1A\u8BDD ${chatSid}\uFF1A`,
|
|
204736
|
+
err
|
|
204737
|
+
);
|
|
204738
|
+
});
|
|
204739
|
+
}
|
|
204589
204740
|
}
|
|
204590
204741
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(result));
|
|
204591
204742
|
} catch (err) {
|
|
@@ -205592,6 +205743,13 @@ async function startOasisServer(opts) {
|
|
|
205592
205743
|
};
|
|
205593
205744
|
const wantsV3 = url.searchParams.get("protocol") === "v3";
|
|
205594
205745
|
if (wantsV3) {
|
|
205746
|
+
const watchSession = url.searchParams.get("watch") === "1";
|
|
205747
|
+
const fromEpoch = url.searchParams.has("epoch") ? Number(url.searchParams.get("epoch")) : void 0;
|
|
205748
|
+
const cursor = liveChat.cursorFor(sid);
|
|
205749
|
+
if (watchSession && fromEpoch !== void 0 && cursor && cursor.epoch !== fromEpoch) {
|
|
205750
|
+
waitForChatTurn(res, liveChat, sid, startStreamKeepalive, true);
|
|
205751
|
+
return;
|
|
205752
|
+
}
|
|
205595
205753
|
const sub2 = (frame) => {
|
|
205596
205754
|
if (res.destroyed) return;
|
|
205597
205755
|
res.write(`${JSON.stringify(frame)}
|
|
@@ -205603,6 +205761,11 @@ async function startOasisServer(opts) {
|
|
|
205603
205761
|
await reopenRecoveringTurn();
|
|
205604
205762
|
att2 = liveChat.attachLiveV3(sid, fromSeq, sub2);
|
|
205605
205763
|
}
|
|
205764
|
+
if (watchSession && (!att2 || att2.status !== "running" && att2.replay.length === 0)) {
|
|
205765
|
+
att2?.detach();
|
|
205766
|
+
waitForChatTurn(res, liveChat, sid, startStreamKeepalive);
|
|
205767
|
+
return;
|
|
205768
|
+
}
|
|
205606
205769
|
if (!att2) {
|
|
205607
205770
|
res.writeHead(204).end();
|
|
205608
205771
|
return;
|
|
@@ -205610,7 +205773,8 @@ async function startOasisServer(opts) {
|
|
|
205610
205773
|
res.writeHead(200, {
|
|
205611
205774
|
"content-type": "application/x-oasis-live-v3+ndjson; charset=utf-8",
|
|
205612
205775
|
"transfer-encoding": "chunked",
|
|
205613
|
-
"access-control-expose-headers": "X-Session-Id, X-Replay-Gap, X-Live-Epoch, X-Can-Append",
|
|
205776
|
+
"access-control-expose-headers": "X-Session-Id, X-Replay-Gap, X-Live-Epoch, X-Can-Append, X-Chat-Session-Watch",
|
|
205777
|
+
...watchSession ? { "x-chat-session-watch": "1" } : {},
|
|
205614
205778
|
"x-session-id": cs.runtimeSessionId ?? "",
|
|
205615
205779
|
"x-live-epoch": String(att2.epoch),
|
|
205616
205780
|
"x-can-append": liveChat.canAppend(sid) ? "1" : "0",
|
|
@@ -206993,14 +207157,23 @@ ${composed}`;
|
|
|
206993
207157
|
const installationId = await svc.revealResolvedVariable("GITHUB_APP_INSTALLATION_ID", actorId);
|
|
206994
207158
|
const effective = installationId ? await fetchInstallationPermissions({ appId, privateKeyPem, installationId }) : meta.permissions;
|
|
206995
207159
|
const missing = missingAppPermissions(effective);
|
|
207160
|
+
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));
|
|
207161
|
+
const grantedKeys = new Set(
|
|
207162
|
+
Object.entries(effective).filter(([, level]) => Boolean(level)).map(([k2]) => k2)
|
|
207163
|
+
);
|
|
206996
207164
|
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({
|
|
206997
207165
|
configured: true,
|
|
206998
207166
|
slug: meta.slug,
|
|
206999
207167
|
ownerLogin: meta.ownerLogin,
|
|
207168
|
+
/** App 申请的全集(英文原名 + 级别)。界面用它回答「我配了多少项」。 */
|
|
207169
|
+
requested: requestedAll,
|
|
207000
207170
|
/** true=读的是这次安装真正被授予的权限;false=只拿到 App 申请的,可能偏乐观。 */
|
|
207001
207171
|
fromInstallation: Boolean(installationId),
|
|
207002
|
-
/**
|
|
207003
|
-
|
|
207172
|
+
/**
|
|
207173
|
+
* App 申请了、但这次安装还没被授予——多半是权限升级还等着组织 owner 批准。
|
|
207174
|
+
* **在全集上做差**,不再只看本仓必需的那八项(否则人另外勾的永远报不出来)。
|
|
207175
|
+
*/
|
|
207176
|
+
awaitingApproval: requestedAll.filter((r) => !grantedKeys.has(r.key)).map((r) => r.key),
|
|
207004
207177
|
/**
|
|
207005
207178
|
* 这次安装**真正被授予**的全部权限,一律用 GitHub 的英文原名 + 级别
|
|
207006
207179
|
* (2026-09-10 发起人:「所有权限都用英文原文,不区分是不是八项权限」)。
|
|
@@ -207318,7 +207491,9 @@ ${composed}`;
|
|
|
207318
207491
|
const svc = await connectorActorsService();
|
|
207319
207492
|
if (svc) {
|
|
207320
207493
|
const connId = "github";
|
|
207321
|
-
|
|
207494
|
+
if (!actorIdParam) {
|
|
207495
|
+
await svc.upsertConnector({ id: connId, name: "GitHub", mode: "oauth", status: "connected", account: "github" });
|
|
207496
|
+
}
|
|
207322
207497
|
for (const [k2, v2] of [["GIT_AUTHOR_NAME", actorNameParam], ["GIT_AUTHOR_EMAIL", ""], ["GIT_COMMITTER_NAME", actorNameParam], ["GIT_COMMITTER_EMAIL", ""], ["EMAIL", ""]])
|
|
207323
207498
|
await svc.putVariable({ key: k2, value: v2, scope: "personal", actorId: actorIdParam, connectorId: connId, encrypted: false });
|
|
207324
207499
|
await svc.putVariable({ key: "GITHUB_TOKEN", value: p2.access_token, scope: "personal", actorId: actorIdParam, connectorId: connId, encrypted: true });
|
|
@@ -207453,7 +207628,6 @@ var init_server3 = __esm({
|
|
|
207453
207628
|
init_src7();
|
|
207454
207629
|
init_collect();
|
|
207455
207630
|
init_remote_util();
|
|
207456
|
-
init_ephemeral_project();
|
|
207457
207631
|
init_timeline();
|
|
207458
207632
|
init_node_state();
|
|
207459
207633
|
init_src5();
|
|
@@ -207462,6 +207636,7 @@ var init_server3 = __esm({
|
|
|
207462
207636
|
init_router();
|
|
207463
207637
|
init_infra_error();
|
|
207464
207638
|
init_live_chat();
|
|
207639
|
+
init_chat_turn_wait();
|
|
207465
207640
|
init_append_now();
|
|
207466
207641
|
init_workorder_drafts();
|
|
207467
207642
|
init_draft_edit();
|
|
@@ -210527,6 +210702,32 @@ var init_projection = __esm({
|
|
|
210527
210702
|
}
|
|
210528
210703
|
});
|
|
210529
210704
|
|
|
210705
|
+
// ../server/src/domains/projects/uncategorized-project.ts
|
|
210706
|
+
function isUncategorizedProjectId(projectId2) {
|
|
210707
|
+
return projectId2 === UNCATEGORIZED_PROJECT_ID;
|
|
210708
|
+
}
|
|
210709
|
+
async function bindWorkorderProject(deps) {
|
|
210710
|
+
const { workOrderId, projectId: projectId2 } = deps;
|
|
210711
|
+
if (!projectId2) return null;
|
|
210712
|
+
try {
|
|
210713
|
+
await deps.upsertBinding({ workOrderId, projectId: projectId2 });
|
|
210714
|
+
return projectId2;
|
|
210715
|
+
} catch (e) {
|
|
210716
|
+
deps.onWarn?.(`[project] \u5DE5\u5355 ${workOrderId} \u7ED1\u5B9A\u9879\u76EE\u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u5EFA\u5355\uFF09\uFF1A${String(e)}`);
|
|
210717
|
+
return null;
|
|
210718
|
+
}
|
|
210719
|
+
}
|
|
210720
|
+
var UNCATEGORIZED_PROJECT_ID, UNCATEGORIZED_PROJECT_NAME, UNCATEGORIZED_PROJECT_DESCRIPTION, UNCATEGORIZED_UNKNOWN_TIME;
|
|
210721
|
+
var init_uncategorized_project = __esm({
|
|
210722
|
+
"../server/src/domains/projects/uncategorized-project.ts"() {
|
|
210723
|
+
"use strict";
|
|
210724
|
+
UNCATEGORIZED_PROJECT_ID = "proj:uncategorized";
|
|
210725
|
+
UNCATEGORIZED_PROJECT_NAME = "\u65E0\u5206\u7C7B\u9879\u76EE";
|
|
210726
|
+
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";
|
|
210727
|
+
UNCATEGORIZED_UNKNOWN_TIME = "1970-01-01T00:00:00.000Z";
|
|
210728
|
+
}
|
|
210729
|
+
});
|
|
210730
|
+
|
|
210530
210731
|
// ../server/src/domains/projects/service.ts
|
|
210531
210732
|
function sanitizeCreatorActorId(raw) {
|
|
210532
210733
|
const id = raw?.trim();
|
|
@@ -210799,7 +211000,7 @@ var init_service4 = __esm({
|
|
|
210799
211000
|
init_src();
|
|
210800
211001
|
init_src5();
|
|
210801
211002
|
init_projection();
|
|
210802
|
-
|
|
211003
|
+
init_uncategorized_project();
|
|
210803
211004
|
init_workorders();
|
|
210804
211005
|
init_node_state();
|
|
210805
211006
|
DEFAULT_AGENT_ACTOR = "actor:agent:system";
|
|
@@ -210935,6 +211136,9 @@ var init_service4 = __esm({
|
|
|
210935
211136
|
async listWorkspaceBindings() {
|
|
210936
211137
|
return [...this.workspaceBindings.values()].map((binding) => ({ ...binding }));
|
|
210937
211138
|
}
|
|
211139
|
+
async deleteWorkspaceBinding(workOrderId) {
|
|
211140
|
+
this.workspaceBindings.delete(workOrderId);
|
|
211141
|
+
}
|
|
210938
211142
|
async setWorkspaceDispatchHold(workOrderId, held) {
|
|
210939
211143
|
if (held) this.dispatchHeld.add(workOrderId);
|
|
210940
211144
|
else this.dispatchHeld.delete(workOrderId);
|
|
@@ -211061,15 +211265,8 @@ var init_service4 = __esm({
|
|
|
211061
211265
|
return { project, artifacts, files };
|
|
211062
211266
|
}
|
|
211063
211267
|
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));
|
|
211268
|
+
if (isUncategorizedProjectId(projectId2)) return [];
|
|
211269
|
+
return this.projects.listProjectFiles(projectId2);
|
|
211073
211270
|
}
|
|
211074
211271
|
/**
|
|
211075
211272
|
* B2 投影同步:写命令落 oplog 后,从 oplog 重放重建 `this.artifacts` 文档层投影。
|
|
@@ -211126,23 +211323,18 @@ var init_service4 = __esm({
|
|
|
211126
211323
|
return project ? this.withProjectDerivedState(project) : null;
|
|
211127
211324
|
}
|
|
211128
211325
|
/**
|
|
211129
|
-
*
|
|
211326
|
+
* 「无分类项目」虚拟卡:把所有**没有项目绑定**的工单聚合成一张固定项目,供列表/详情/关联任务/相关文件消费。
|
|
211130
211327
|
* 存储里没有真行——这就是本卡与真项目最大的区别:
|
|
211131
211328
|
* - 写侧(成员、内容位置、上传文件)一律拒收(写到真项目 or 走真工单流程);
|
|
211132
|
-
* - 派生态(artifactSummary / workOrderCount
|
|
211329
|
+
* - 派生态(artifactSummary / workOrderCount)按「无绑定工单」算并集;
|
|
211133
211330
|
* - 帧上的「项目负责人:系统」由前端自选文案渲染,本层只给一个稳定的空 members 名单。
|
|
211331
|
+
*
|
|
211332
|
+
* ⚠ `workOrderCount` **走 listProjectWorkorders 同一条路**,不另写一份 bindings 过滤——
|
|
211333
|
+
* 两处各算各的正是「卡片计数与点进去的列表条数对不上」那类缺陷的来路。
|
|
211134
211334
|
*/
|
|
211135
211335
|
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();
|
|
211336
|
+
const workorders = await this.listProjectWorkorders(UNCATEGORIZED_PROJECT_ID);
|
|
211337
|
+
const artifactList = await this.aggregateUncategorizedArtifacts();
|
|
211146
211338
|
return {
|
|
211147
211339
|
id: UNCATEGORIZED_PROJECT_ID,
|
|
211148
211340
|
name: UNCATEGORIZED_PROJECT_NAME,
|
|
@@ -211150,46 +211342,33 @@ var init_service4 = __esm({
|
|
|
211150
211342
|
memberCount: 0,
|
|
211151
211343
|
members: [],
|
|
211152
211344
|
artifactSummary: artifactList.summary,
|
|
211153
|
-
workOrderCount,
|
|
211345
|
+
workOrderCount: workorders.length,
|
|
211154
211346
|
// ADR 0205:本卡的「项目负责人:系统」是**前端按帧自选的文案**,后端不出 `"system"` 哨兵——
|
|
211155
211347
|
// 出 null 保持「取不到就 null」的域内红线,也不把一个不存在的 actor id 塞进契约。
|
|
211156
211348
|
createdBy: null,
|
|
211157
|
-
createdAt:
|
|
211158
|
-
updatedAt:
|
|
211349
|
+
createdAt: UNCATEGORIZED_UNKNOWN_TIME,
|
|
211350
|
+
updatedAt: UNCATEGORIZED_UNKNOWN_TIME
|
|
211159
211351
|
};
|
|
211160
211352
|
}
|
|
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
|
-
}
|
|
211353
|
+
async aggregateUncategorizedArtifacts() {
|
|
211354
|
+
const artifacts = await this.listAllProjectArtifacts(UNCATEGORIZED_PROJECT_ID);
|
|
211168
211355
|
artifacts.sort((a, b2) => b2.updatedAt.localeCompare(a.updatedAt));
|
|
211169
211356
|
return { artifacts, summary: summarizeArtifacts(artifacts) };
|
|
211170
211357
|
}
|
|
211171
211358
|
/**
|
|
211172
211359
|
* 项目列表(带派生态)。
|
|
211173
211360
|
*
|
|
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`。
|
|
211361
|
+
* 库里的每一行都是**人建的真项目**,全部返回——临时项目(`proj_tmp_*`)退役后,这里不再有
|
|
211362
|
+
* "算完就扔"的过滤:旧版 dev 环境 294 个项目里 272 个是一单一个的临时项目,
|
|
211363
|
+
* `withProjectDerivedState` 对它们逐个跑 `buildWorkorderSummaries`(全量工单)+ 遍历全部 artifact,
|
|
211364
|
+
* 93% 的计算白算,还把单线程事件循环整块堵住 3~11 秒(并发时 /api/automations 从 0.24s 被拖到 7.1s,
|
|
211365
|
+
* 前端看到的是「项目列表加载失败:timeout」——像数据库挂了,其实是被自己堵死的)。
|
|
211184
211366
|
*/
|
|
211185
211367
|
async listProjects(opts) {
|
|
211186
211368
|
const stored = await this.projects.listProjects();
|
|
211187
|
-
const wanted = opts?.includeEphemeral === true ? stored : stored.filter((project) => !isEphemeralProject(project.id));
|
|
211188
211369
|
const creatorCache = /* @__PURE__ */ new Map();
|
|
211189
|
-
const derived = await Promise.all(
|
|
211370
|
+
const derived = await Promise.all(stored.map((project) => this.withProjectDerivedState(project, creatorCache)));
|
|
211190
211371
|
if (opts?.includeUncategorized === false) return derived;
|
|
211191
|
-
const wantUncategorized = opts?.includeUncategorized ?? !(opts?.includeEphemeral === true);
|
|
211192
|
-
if (!wantUncategorized) return derived;
|
|
211193
211372
|
const virtual = await this.buildUncategorizedProject();
|
|
211194
211373
|
return [virtual, ...derived];
|
|
211195
211374
|
}
|
|
@@ -211268,10 +211447,10 @@ var init_service4 = __esm({
|
|
|
211268
211447
|
* 项目创建人(ADR 0205)——**读时解析**,三条来路按优先级:
|
|
211269
211448
|
*
|
|
211270
211449
|
* 1. 库里落了 `created_by` → 按它解析 name/avatar;
|
|
211271
|
-
* 2.
|
|
211272
|
-
*
|
|
211273
|
-
*
|
|
211274
|
-
*
|
|
211450
|
+
* 2. 没落 → `null`(老的手建项目),前端诚实显示「—」。
|
|
211451
|
+
*
|
|
211452
|
+
* 旧版还有一档「临时项目从它绑定的那张工单 `brief.owner` 派生」——临时项目退役后没有这一档了
|
|
211453
|
+
* (见 uncategorized-project.ts 文件头)。
|
|
211275
211454
|
*
|
|
211276
211455
|
* **绝不拿别的字段顶替**(`members[0]`、项目名、updatedAt 都不行)——未指定成员时全组织都是成员,
|
|
211277
211456
|
* 按 actorId 排序等于随机点一个 agent 当负责人。这是本域红线,见 resolveActorName 的注释。
|
|
@@ -211279,9 +211458,7 @@ var init_service4 = __esm({
|
|
|
211279
211458
|
async resolveProjectCreator(project, workorders, cache) {
|
|
211280
211459
|
const stored = project.createdBy?.id;
|
|
211281
211460
|
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;
|
|
211461
|
+
return null;
|
|
211285
211462
|
}
|
|
211286
211463
|
/**
|
|
211287
211464
|
* actorId → {@link ProjectActorRef}:`kind` 按 id 前缀派生,`name` / `avatar` 走本公司名册现取。
|
|
@@ -211344,7 +211521,8 @@ var init_service4 = __esm({
|
|
|
211344
211521
|
*/
|
|
211345
211522
|
async reassignWorkorder(workOrderId, projectId2) {
|
|
211346
211523
|
if (isUncategorizedProjectId(projectId2)) {
|
|
211347
|
-
|
|
211524
|
+
await this.unlinkWorkorder(workOrderId);
|
|
211525
|
+
return { workOrderId, projectId: null };
|
|
211348
211526
|
}
|
|
211349
211527
|
const project = await this.projects.getProject(projectId2);
|
|
211350
211528
|
if (!project) throw new Error(`project not found: ${projectId2}`);
|
|
@@ -211353,77 +211531,46 @@ var init_service4 = __esm({
|
|
|
211353
211531
|
return binding;
|
|
211354
211532
|
}
|
|
211355
211533
|
/**
|
|
211356
|
-
*
|
|
211534
|
+
* 「取消任务关联」——**删掉工单的项目绑定行**,工单从此没有项目(读侧落进「无分类项目」桶)。
|
|
211357
211535
|
*
|
|
211358
|
-
*
|
|
211359
|
-
*
|
|
211360
|
-
*
|
|
211536
|
+
* 2026-09-10 之前这里是"回落到工单专属的临时项目 `proj_tmp_<slug>`",理由是「契约里每个工单
|
|
211537
|
+
* 都该属于某项目」。那套兜底已退役(见 uncategorized-project.ts 文件头):兜底容器让每张未挂靠
|
|
211538
|
+
* 工单变成一个只有它自己的假项目,代码仓/项目变量/记忆作用域全挂在一次性 id 上。
|
|
211361
211539
|
*
|
|
211362
|
-
*
|
|
211363
|
-
* 不需要预先存在(`ensureWorkorderProject` 后台建,也可能是本工单第一次被"解绑"——按需建)。
|
|
211540
|
+
* 幂等:没有绑定行时静默返回(store 的 delete 本身幂等)。
|
|
211364
211541
|
*/
|
|
211365
211542
|
async unlinkWorkorder(workOrderId) {
|
|
211366
211543
|
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;
|
|
211544
|
+
await this.artifacts.deleteWorkspaceBinding(workOrderId);
|
|
211383
211545
|
}
|
|
211384
211546
|
/**
|
|
211385
211547
|
* 删除项目(帧 `444:1792` 三点菜单第二项,口径由发起人 2026-09-09 拍板)。
|
|
211386
211548
|
*
|
|
211387
211549
|
* **只删项目这个壳**——项目主记录 + 成员名单 + 上传文件记录 + 内容位置。任务和会话一条都不删:
|
|
211388
211550
|
*
|
|
211389
|
-
* 1. 绑到本项目的工单**不管在跑还是没在跑**,一律 `unlinkWorkorder`
|
|
211390
|
-
*
|
|
211391
|
-
*
|
|
211392
|
-
*
|
|
211393
|
-
*
|
|
211394
|
-
*
|
|
211395
|
-
* 下一个节点就取不到代码仓——正是 `ephemeral-project.ts` 顶部那条五环事故链。
|
|
211551
|
+
* 1. 绑到本项目的工单**不管在跑还是没在跑**,一律 `unlinkWorkorder` 删掉绑定行 → 落进读侧的
|
|
211552
|
+
* 「无分类项目」。发起人明确不要「有任务在跑就不让删」这道闸,所以这里没有任何在跑判断。
|
|
211553
|
+
* 2. **代码仓不跟着搬**(发起人 2026-09-10 裁定)。`git/resolve.ts` 的 `resolveCodeRepo` 是
|
|
211554
|
+
* 派发时沿 `绑定 → 项目 → 内容位置` 实时解的,所以删完项目,这些任务下一个节点就取不到代码仓——
|
|
211555
|
+
* 这是**期望行为**:由执行中的数字员工发现并引导人给任务指定新项目,而不是由系统兜一个假容器
|
|
211556
|
+
* (旧版把 git 位置逐单复制到 `proj_tmp_*` 上,正是那套已退役的兜底)。
|
|
211396
211557
|
* 3. 会话的 `project_id` 置空、项目作用域的变量/密钥清理,走 {@link OnProjectDeleted} 钩子
|
|
211397
211558
|
* (本 service 对会话域与凭据域零耦合,同 `onProjectNameChanged` 的注入模式)。
|
|
211398
211559
|
*
|
|
211399
|
-
*
|
|
211400
|
-
* (工单的兜底归属,删掉等于把工单变成无绑定态——契约里 `Workspace.projectId` 必填)。
|
|
211560
|
+
* 拒绝 `proj:uncategorized`(读侧虚拟卡,存储里没有真行,删它没有意义)。
|
|
211401
211561
|
*/
|
|
211402
211562
|
async deleteProject(id) {
|
|
211403
211563
|
const projectId2 = id?.trim();
|
|
211404
211564
|
if (!projectId2) throw new Error("project id is required");
|
|
211405
211565
|
if (isUncategorizedProjectId(projectId2)) throw new Error(`project not deletable: ${projectId2}`);
|
|
211406
|
-
if (isEphemeralProject(projectId2)) throw new Error(`project not deletable: ${projectId2}`);
|
|
211407
211566
|
const project = await this.projects.getProject(projectId2);
|
|
211408
211567
|
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
211568
|
const bindings = await this.artifacts.listWorkspaceBindings();
|
|
211412
211569
|
const movedWorkOrderIds = [];
|
|
211413
211570
|
for (const binding of bindings) {
|
|
211414
211571
|
if (binding.projectId !== projectId2) continue;
|
|
211415
|
-
|
|
211572
|
+
await this.unlinkWorkorder(binding.workOrderId);
|
|
211416
211573
|
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
211574
|
}
|
|
211428
211575
|
await this.projects.deleteProject(projectId2);
|
|
211429
211576
|
if (this.onProjectDeleted) {
|
|
@@ -211433,7 +211580,7 @@ var init_service4 = __esm({
|
|
|
211433
211580
|
console.warn(`[projects] project-deleted follow-up failed for ${projectId2}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
211434
211581
|
}
|
|
211435
211582
|
}
|
|
211436
|
-
return { projectId: projectId2, movedWorkOrderIds
|
|
211583
|
+
return { projectId: projectId2, movedWorkOrderIds };
|
|
211437
211584
|
}
|
|
211438
211585
|
/**
|
|
211439
211586
|
* 项目关联工单(帧 `1355:4918`):把内部私有的 `listProjectWorkorders` 暴露给路由层。
|
|
@@ -212063,7 +212210,7 @@ var init_service4 = __esm({
|
|
|
212063
212210
|
if (!snap) return [];
|
|
212064
212211
|
const worksById = new Map(snap.works.map((w2) => [w2.id, w2]));
|
|
212065
212212
|
const binding = this.artifacts.getWorkspaceBinding ? await this.artifacts.getWorkspaceBinding(workOrderId).catch(() => null) : null;
|
|
212066
|
-
const projectId2 = binding?.projectId ??
|
|
212213
|
+
const projectId2 = binding?.projectId ?? UNCATEGORIZED_PROJECT_ID;
|
|
212067
212214
|
const out = [];
|
|
212068
212215
|
for (const node2 of snap.nodes) {
|
|
212069
212216
|
if (!node2.latestAcceptId) continue;
|
|
@@ -212193,20 +212340,8 @@ var init_service4 = __esm({
|
|
|
212193
212340
|
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
212341
|
return { artifacts: filtered, summary: summarizeArtifacts(filtered) };
|
|
212195
212342
|
}
|
|
212196
|
-
/**
|
|
212343
|
+
/** 内部:虚拟卡与真项目走同一条路——{@link listAllProjectArtifacts} 内部按「无绑定 / 绑到本项目」分流。 */
|
|
212197
212344
|
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
212345
|
return this.listAllProjectArtifacts(projectId2);
|
|
212211
212346
|
}
|
|
212212
212347
|
async listProjectWorkorders(projectId2) {
|
|
@@ -212215,17 +212350,19 @@ var init_service4 = __esm({
|
|
|
212215
212350
|
const uncategorized = isUncategorizedProjectId(projectId2);
|
|
212216
212351
|
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
212352
|
const bound = workorder.projectId;
|
|
212218
|
-
if (uncategorized) return
|
|
212353
|
+
if (uncategorized) return bound == null;
|
|
212219
212354
|
return bound === projectId2;
|
|
212220
212355
|
});
|
|
212221
212356
|
}
|
|
212222
212357
|
async listAllProjectArtifacts(projectId2) {
|
|
212223
|
-
const
|
|
212358
|
+
const uncategorized = isUncategorizedProjectId(projectId2);
|
|
212359
|
+
const stored = uncategorized ? [] : await this.artifacts.listArtifacts({ projectId: projectId2 });
|
|
212224
212360
|
const byArtifact = new Map(stored.map((artifact) => [artifact.artifactId, artifact]));
|
|
212225
212361
|
const bindings = await this.artifacts.listWorkspaceBindings();
|
|
212226
212362
|
const projectByWorkorder = new Map(bindings.map((binding) => [binding.workOrderId, binding.projectId]));
|
|
212227
212363
|
for (const artifact of this.kernel.model.artifacts.values()) {
|
|
212228
|
-
|
|
212364
|
+
const bound = projectByWorkorder.get(artifact.workspace);
|
|
212365
|
+
if (uncategorized ? bound !== void 0 : bound !== projectId2) continue;
|
|
212229
212366
|
const descriptor = decodeDocumentDescription(artifact.description);
|
|
212230
212367
|
const info = nodeInfo(this.kernel.model, artifact);
|
|
212231
212368
|
const revisions = revisionsOf(this.kernel.model, artifact.id);
|
|
@@ -216890,8 +217027,18 @@ function wireRecoveredChatTurn(deps) {
|
|
|
216890
217027
|
clearInterval(ckptTimer);
|
|
216891
217028
|
const failed = exitFailed(info);
|
|
216892
217029
|
if (failed) {
|
|
216893
|
-
|
|
216894
|
-
|
|
217030
|
+
const errText2 = exitErrorText(info);
|
|
217031
|
+
live.emit({ type: "error", text: errText2 });
|
|
217032
|
+
const errorOutcome = itemLedger.recordError(errText2, { ...info.reason ? { reason: info.reason } : {} });
|
|
217033
|
+
live.publishServerItem({
|
|
217034
|
+
itemId: errorOutcome.itemId,
|
|
217035
|
+
itemType: "control",
|
|
217036
|
+
startedVersion: errorOutcome.startedVersion,
|
|
217037
|
+
version: errorOutcome.version,
|
|
217038
|
+
ord: errorOutcome.ord,
|
|
217039
|
+
status: "failed",
|
|
217040
|
+
payload: { code: "error", note: errText2, ...info.reason ? { reason: info.reason } : {} }
|
|
217041
|
+
});
|
|
216895
217042
|
}
|
|
216896
217043
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
216897
217044
|
const parts = collectedParts();
|
|
@@ -219789,13 +219936,6 @@ var init_memory = __esm({
|
|
|
219789
219936
|
async write(ctx, input) {
|
|
219790
219937
|
this.assertWritable(input, "agent");
|
|
219791
219938
|
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
219939
|
return this.writeResolved(ctx, input, input.scope === "project" ? projectId2 : null, "agent");
|
|
219800
219940
|
}
|
|
219801
219941
|
/** 控制台管理面写入:actorId/projectId 由受保护的管理路由明确给出,不复用 agent 自用入口。 */
|
|
@@ -220905,6 +221045,21 @@ var init_connector_skills = __esm({
|
|
|
220905
221045
|
}
|
|
220906
221046
|
});
|
|
220907
221047
|
|
|
221048
|
+
// ../server/src/domains/nodes/runtime-tenancy.ts
|
|
221049
|
+
function foreignNodeBlock(node2, expectedCompanyId) {
|
|
221050
|
+
if (!expectedCompanyId) return null;
|
|
221051
|
+
if (!node2) return null;
|
|
221052
|
+
const owner = node2.companyId;
|
|
221053
|
+
if (!owner) return null;
|
|
221054
|
+
if (owner === expectedCompanyId) return null;
|
|
221055
|
+
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`;
|
|
221056
|
+
}
|
|
221057
|
+
var init_runtime_tenancy = __esm({
|
|
221058
|
+
"../server/src/domains/nodes/runtime-tenancy.ts"() {
|
|
221059
|
+
"use strict";
|
|
221060
|
+
}
|
|
221061
|
+
});
|
|
221062
|
+
|
|
220908
221063
|
// ../server/src/domains/actors/routes.ts
|
|
220909
221064
|
async function asApiError(run) {
|
|
220910
221065
|
try {
|
|
@@ -221227,6 +221382,9 @@ function actorsDomain(opts) {
|
|
|
221227
221382
|
const { service } = await resolveCtx(req.auth.companyId);
|
|
221228
221383
|
const b2 = req.body;
|
|
221229
221384
|
if (!b2?.actorId || !b2.nodeId || !b2.runtimeKind) throw new ApiError(400, "BAD_REQUEST", "\u7F3A\u5C11 actorId / nodeId / runtimeKind");
|
|
221385
|
+
const targetNode = await opts.resolveNodeTenancy?.(b2.nodeId).catch(() => null) ?? null;
|
|
221386
|
+
const foreign = foreignNodeBlock(targetNode, req.auth.companyId);
|
|
221387
|
+
if (foreign) throw new ApiError(400, "NODE_NOT_IN_COMPANY", foreign);
|
|
221230
221388
|
const { clearedModel } = await service.bind({ actorId: b2.actorId, nodeId: b2.nodeId, runtimeKind: b2.runtimeKind, status: b2.status ?? "active" }, req.auth.actor);
|
|
221231
221389
|
return { status: 201, body: { ok: true, ...clearedModel ? { clearedModel } : {} } };
|
|
221232
221390
|
});
|
|
@@ -222036,6 +222194,7 @@ var init_routes3 = __esm({
|
|
|
222036
222194
|
init_image_upload();
|
|
222037
222195
|
init_skill_materializer();
|
|
222038
222196
|
init_connector_skills();
|
|
222197
|
+
init_runtime_tenancy();
|
|
222039
222198
|
CREDENTIAL_REVEAL_SOURCES = /* @__PURE__ */ new Set(["agent-cli", "agent-exec", "smoke-test", "oasis-internal", "console"]);
|
|
222040
222199
|
RevealRateLimiter = class {
|
|
222041
222200
|
constructor(perSession, perSessionPerKey, ttlMs = 2 * 60 * 6e4) {
|
|
@@ -222232,7 +222391,7 @@ function createActorsDomain(opts) {
|
|
|
222232
222391
|
return {
|
|
222233
222392
|
service: defaultCtx.service,
|
|
222234
222393
|
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 } : {} })
|
|
222394
|
+
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
222395
|
};
|
|
222237
222396
|
}
|
|
222238
222397
|
var import_node_crypto49;
|
|
@@ -222434,12 +222593,9 @@ function projectsDomain(opts) {
|
|
|
222434
222593
|
}
|
|
222435
222594
|
});
|
|
222436
222595
|
router.get("/api/projects", async (req) => {
|
|
222437
|
-
const inc = req.query?.get("includeEphemeral");
|
|
222438
|
-
const includeEphemeral = inc === "1" || inc === "true";
|
|
222439
222596
|
const uncatRaw = req.query?.get("includeUncategorized");
|
|
222440
222597
|
const includeUncategorized = uncatRaw === null || uncatRaw === void 0 ? void 0 : !(uncatRaw === "0" || uncatRaw === "false");
|
|
222441
222598
|
const projects = await (await svc(req)).listProjects({
|
|
222442
|
-
includeEphemeral,
|
|
222443
222599
|
...includeUncategorized !== void 0 ? { includeUncategorized } : {}
|
|
222444
222600
|
});
|
|
222445
222601
|
const page = paginate(projects.map(toApiProject), req.query);
|
|
@@ -222477,8 +222633,7 @@ function projectsDomain(opts) {
|
|
|
222477
222633
|
body: {
|
|
222478
222634
|
ok: true,
|
|
222479
222635
|
project_id: result.projectId,
|
|
222480
|
-
moved_work_order_ids: result.movedWorkOrderIds
|
|
222481
|
-
carried_git_location_count: result.carriedGitLocationCount
|
|
222636
|
+
moved_work_order_ids: result.movedWorkOrderIds
|
|
222482
222637
|
}
|
|
222483
222638
|
};
|
|
222484
222639
|
} catch (err) {
|
|
@@ -222720,8 +222875,8 @@ ${errs.join("\n")}`);
|
|
|
222720
222875
|
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
222876
|
if (nulledPatch) {
|
|
222722
222877
|
try {
|
|
222723
|
-
|
|
222724
|
-
return { status: 200, body: { work_order_id:
|
|
222878
|
+
await (await svc(req)).unlinkWorkorder(req.params.workOrderId);
|
|
222879
|
+
return { status: 200, body: { work_order_id: req.params.workOrderId, project_id: null } };
|
|
222725
222880
|
} catch (err) {
|
|
222726
222881
|
throw mapProjectError(err);
|
|
222727
222882
|
}
|
|
@@ -222737,8 +222892,8 @@ ${errs.join("\n")}`);
|
|
|
222737
222892
|
});
|
|
222738
222893
|
router.delete("/api/work-orders/:workOrderId/project", async (req) => {
|
|
222739
222894
|
try {
|
|
222740
|
-
|
|
222741
|
-
return { status: 200, body: { work_order_id:
|
|
222895
|
+
await (await svc(req)).unlinkWorkorder(req.params.workOrderId);
|
|
222896
|
+
return { status: 200, body: { work_order_id: req.params.workOrderId, project_id: null } };
|
|
222742
222897
|
} catch (err) {
|
|
222743
222898
|
throw mapProjectError(err);
|
|
222744
222899
|
}
|
|
@@ -223256,7 +223411,7 @@ var init_routes5 = __esm({
|
|
|
223256
223411
|
"use strict";
|
|
223257
223412
|
init_router();
|
|
223258
223413
|
init_src();
|
|
223259
|
-
|
|
223414
|
+
init_uncategorized_project();
|
|
223260
223415
|
}
|
|
223261
223416
|
});
|
|
223262
223417
|
|
|
@@ -228322,13 +228477,15 @@ async function buildChatPreview(chatStore, sessionId, who) {
|
|
|
228322
228477
|
if (lastAsst) lines.push({ label: who, text: clip2(lastAsst.content) });
|
|
228323
228478
|
return lines.length ? { lines } : void 0;
|
|
228324
228479
|
}
|
|
228325
|
-
async function buildChatInbox(chatStore, markers, me, resolveActor) {
|
|
228480
|
+
async function buildChatInbox(chatStore, markers, me, resolveActor, excludeChildSessions) {
|
|
228326
228481
|
const sessions = await chatStore.listSessions(me);
|
|
228327
228482
|
const byScope = new Map(markers.map((m2) => [m2.scope, m2]));
|
|
228328
228483
|
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);
|
|
228484
|
+
const excluded = excludeChildSessions ? new Set(await excludeChildSessions(unread.map((s2) => s2.id)).catch(() => [])) : /* @__PURE__ */ new Set();
|
|
228329
228485
|
const out = [];
|
|
228330
228486
|
let previewsFetched = 0;
|
|
228331
228487
|
for (const s2 of unread) {
|
|
228488
|
+
if (excluded.has(s2.id)) continue;
|
|
228332
228489
|
const who = resolveActor?.(s2.aiActorId)?.name ?? "\u52A9\u7406";
|
|
228333
228490
|
const abnormal = s2.lastTurnQuality === "abnormal";
|
|
228334
228491
|
const preview2 = previewsFetched < PREVIEW_FETCH_CAP ? await buildChatPreview(chatStore, s2.id, who) : void 0;
|
|
@@ -229718,7 +229875,7 @@ function collabDomain(opts) {
|
|
|
229718
229875
|
if (projectFilter) {
|
|
229719
229876
|
items = items.filter((wo) => {
|
|
229720
229877
|
const bound = wo.projectId;
|
|
229721
|
-
if (isUncategorizedProjectId(projectFilter)) return
|
|
229878
|
+
if (isUncategorizedProjectId(projectFilter)) return bound == null;
|
|
229722
229879
|
return bound === projectFilter;
|
|
229723
229880
|
});
|
|
229724
229881
|
}
|
|
@@ -230124,7 +230281,8 @@ function collabDomain(opts) {
|
|
|
230124
230281
|
return void 0;
|
|
230125
230282
|
}
|
|
230126
230283
|
})();
|
|
230127
|
-
const
|
|
230284
|
+
const excludeChildSessions = opts.resolveDelegationChildIds ? (ids2) => opts.resolveDelegationChildIds(req.auth.companyId, ids2) : void 0;
|
|
230285
|
+
const chatItems = chatStore && myReadMarkers ? await buildChatInbox(chatStore, markers, me, resolveActor, excludeChildSessions) : [];
|
|
230128
230286
|
let waitingItems = [];
|
|
230129
230287
|
if (engineStore) {
|
|
230130
230288
|
const snaps = (await Promise.all(
|
|
@@ -230419,7 +230577,7 @@ var init_collab = __esm({
|
|
|
230419
230577
|
init_workorder_metrics();
|
|
230420
230578
|
init_chat_workorder_files();
|
|
230421
230579
|
init_organization_usage();
|
|
230422
|
-
|
|
230580
|
+
init_uncategorized_project();
|
|
230423
230581
|
init_tags();
|
|
230424
230582
|
init_activity2();
|
|
230425
230583
|
init_node_state();
|
|
@@ -230706,24 +230864,16 @@ function makeSeededWorkorderCreator(deps) {
|
|
|
230706
230864
|
const blobs = company?.blobs ?? deps.blobs;
|
|
230707
230865
|
const registry2 = company?.registry ?? deps.registry;
|
|
230708
230866
|
const artifactState = company ? company.artifactState : deps.artifactState;
|
|
230709
|
-
const
|
|
230867
|
+
const workorderDrafts = company ? company.workorderDrafts : deps.workorderDrafts;
|
|
230710
230868
|
const workspace = input.idempotencyKey ? workspaceForKey(input.idempotencyKey) : genWorkspace();
|
|
230711
230869
|
if (input.idempotencyKey) {
|
|
230712
230870
|
const existing = existingWorkorder(kernel, workspace);
|
|
230713
230871
|
if (existing) return existing;
|
|
230714
230872
|
}
|
|
230715
|
-
if (artifactState &&
|
|
230716
|
-
await
|
|
230873
|
+
if (artifactState && input.projectId) {
|
|
230874
|
+
await bindWorkorderProject({
|
|
230717
230875
|
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
|
-
// 上面的守卫保证走不到这个分支
|
|
230876
|
+
projectId: input.projectId,
|
|
230727
230877
|
upsertBinding: (b2) => artifactState.upsertWorkspaceBinding(b2),
|
|
230728
230878
|
...deps.onWarn ? { onWarn: deps.onWarn } : {}
|
|
230729
230879
|
});
|
|
@@ -230755,8 +230905,8 @@ function makeSeededWorkorderCreator(deps) {
|
|
|
230755
230905
|
if (!preview2.endState.ok) {
|
|
230756
230906
|
throw new Error(`\u5267\u672C\u5EFA\u5355\u7EC8\u6001\u6821\u9A8C\u4E0D\u8FC7\uFF1A${preview2.endState.violations.join("\uFF1B")}`);
|
|
230757
230907
|
}
|
|
230758
|
-
if (input.stage === true &&
|
|
230759
|
-
const draft = await
|
|
230908
|
+
if (input.stage === true && workorderDrafts) {
|
|
230909
|
+
const draft = await workorderDrafts.submit({
|
|
230760
230910
|
workspace,
|
|
230761
230911
|
plan: planned.plan,
|
|
230762
230912
|
rootArtifactId: planned.rootArtifactId,
|
|
@@ -230818,7 +230968,7 @@ var init_create_seeded_workorder = __esm({
|
|
|
230818
230968
|
"../server/src/domains/collab/create-seeded-workorder.ts"() {
|
|
230819
230969
|
"use strict";
|
|
230820
230970
|
import_node_crypto57 = require("node:crypto");
|
|
230821
|
-
|
|
230971
|
+
init_uncategorized_project();
|
|
230822
230972
|
init_planner();
|
|
230823
230973
|
enc2 = (s2) => new TextEncoder().encode(s2);
|
|
230824
230974
|
}
|
|
@@ -234058,6 +234208,9 @@ function createChatSessionsDomain(opts) {
|
|
|
234058
234208
|
childSessionId: record8.childSessionId,
|
|
234059
234209
|
expertActorId: record8.expertActorId,
|
|
234060
234210
|
...expert?.name ? { expertName: expert.name } : {},
|
|
234211
|
+
/* 头像与名字同一本名册、同一次查询取出来。缺了它,委派卡与输入框上方状态条只能退回
|
|
234212
|
+
姓名首字的灰圆(发起人 2026-09-10 报的「灰底 D」)——那两处画的是「[头像] 名字 进行中」。 */
|
|
234213
|
+
...expert?.avatar ? { expertAvatar: expert.avatar } : {},
|
|
234061
234214
|
state: record8.state,
|
|
234062
234215
|
...record8.failureKind ? { failureKind: record8.failureKind } : {},
|
|
234063
234216
|
...record8.label ? { label: record8.label } : {},
|
|
@@ -237750,7 +237903,7 @@ function playbooksDomain(opts) {
|
|
|
237750
237903
|
const drafts = opts.resolveWorkorderDrafts ? await opts.resolveWorkorderDrafts(req.auth?.companyId) : opts.workorderDrafts;
|
|
237751
237904
|
if (result.draftId && chatSid && drafts) {
|
|
237752
237905
|
try {
|
|
237753
|
-
await drafts.tagSession(result.draftId, chatSid, opts.liveChat?.runFor(chatSid));
|
|
237906
|
+
await drafts.tagSession(result.draftId, chatSid, opts.liveChat?.runFor(chatSid), result.workspace);
|
|
237754
237907
|
} catch (err) {
|
|
237755
237908
|
console.warn(`[playbooks] tagSession(${result.draftId}) \u5931\u8D25\uFF0C\u4E0D\u963B\u585E\u5EFA\u5355\u54CD\u5E94:`, err);
|
|
237756
237909
|
}
|
|
@@ -238497,7 +238650,10 @@ var init_daemon_adapter = __esm({
|
|
|
238497
238650
|
} else if (msg.type === "session_normalized_event") {
|
|
238498
238651
|
this.confirmDelivery(msg.dispatchId, "started");
|
|
238499
238652
|
const entry = this.pending.get(msg.dispatchId);
|
|
238500
|
-
if (!entry)
|
|
238653
|
+
if (!entry) {
|
|
238654
|
+
if (this.stashEnabled) this.stashFrame(msg.dispatchId, { type: "normalized", event: msg.event });
|
|
238655
|
+
return;
|
|
238656
|
+
}
|
|
238501
238657
|
if (entry.normalizedCbs.length === 0) entry.normalizedBacklog.push(msg.event);
|
|
238502
238658
|
else for (const cb of entry.normalizedCbs) cb(msg.event);
|
|
238503
238659
|
} else if (msg.type === "session_output") {
|
|
@@ -238630,6 +238786,7 @@ var init_daemon_adapter = __esm({
|
|
|
238630
238786
|
let replayOutput;
|
|
238631
238787
|
let replayTelemetry;
|
|
238632
238788
|
let replayLiveEvent;
|
|
238789
|
+
let replayNormalized;
|
|
238633
238790
|
const flushReplay = () => {
|
|
238634
238791
|
while (stashed.frames.length > 0) {
|
|
238635
238792
|
const frame = stashed.frames[0];
|
|
@@ -238641,6 +238798,10 @@ var init_daemon_adapter = __esm({
|
|
|
238641
238798
|
if (!replayTelemetry) return;
|
|
238642
238799
|
stashed.frames.shift();
|
|
238643
238800
|
replayTelemetry(frame.event);
|
|
238801
|
+
} else if (frame.type === "normalized") {
|
|
238802
|
+
if (!replayNormalized) return;
|
|
238803
|
+
stashed.frames.shift();
|
|
238804
|
+
replayNormalized(frame.event);
|
|
238644
238805
|
} else {
|
|
238645
238806
|
if (!replayLiveEvent) return;
|
|
238646
238807
|
stashed.frames.shift();
|
|
@@ -238679,9 +238840,11 @@ var init_daemon_adapter = __esm({
|
|
|
238679
238840
|
},
|
|
238680
238841
|
...supportsNormalizedEvents ? {
|
|
238681
238842
|
onNormalizedProviderEvent(cb) {
|
|
238682
|
-
|
|
238843
|
+
replayNormalized ??= cb;
|
|
238844
|
+
flushReplay();
|
|
238683
238845
|
const backlog = entry.normalizedBacklog.splice(0, entry.normalizedBacklog.length);
|
|
238684
238846
|
for (const e of backlog) cb(e);
|
|
238847
|
+
entry.normalizedCbs.push(cb);
|
|
238685
238848
|
}
|
|
238686
238849
|
} : {},
|
|
238687
238850
|
// ADR-0093 对话追加(重挂句柄同 spawn 句柄)。
|
|
@@ -242164,6 +242327,9 @@ var init_postgres_projects = __esm({
|
|
|
242164
242327
|
const r = await this.pool.query(`SELECT * FROM ${this.s}.workspace_bindings`);
|
|
242165
242328
|
return r.rows.map(rowToWorkspaceBinding);
|
|
242166
242329
|
}
|
|
242330
|
+
async deleteWorkspaceBinding(workOrderId) {
|
|
242331
|
+
await this.pool.query(`DELETE FROM ${this.s}.workspace_bindings WHERE work_order_id = $1`, [workOrderId]);
|
|
242332
|
+
}
|
|
242167
242333
|
/* ---------- 任务输入文件(PRD §2.6.3 建单交接;薄旁存,非 oplog) ---------- */
|
|
242168
242334
|
async addWorkOrderFile(file) {
|
|
242169
242335
|
await this.pool.query(
|
|
@@ -255641,8 +255807,8 @@ var init_cutover_aware_workorder_drafts = __esm({
|
|
|
255641
255807
|
async snapshotForNewRun(d, runId) {
|
|
255642
255808
|
return (await this.pickWritable()).snapshotForNewRun(d, runId);
|
|
255643
255809
|
}
|
|
255644
|
-
async tagSession(id, s2, runId) {
|
|
255645
|
-
return (await this.pickWritable()).tagSession(id, s2, runId);
|
|
255810
|
+
async tagSession(id, s2, runId, expectedWorkspace) {
|
|
255811
|
+
return (await this.pickWritable()).tagSession(id, s2, runId, expectedWorkspace);
|
|
255646
255812
|
}
|
|
255647
255813
|
async markEdited(id) {
|
|
255648
255814
|
return (await this.pickWritable()).markEdited(id);
|
|
@@ -256613,6 +256779,7 @@ var init_src11 = __esm({
|
|
|
256613
256779
|
init_sender();
|
|
256614
256780
|
init_routes7();
|
|
256615
256781
|
init_runtime_drift();
|
|
256782
|
+
init_runtime_tenancy();
|
|
256616
256783
|
init_types4();
|
|
256617
256784
|
init_roles();
|
|
256618
256785
|
init_node_store();
|
|
@@ -256664,7 +256831,7 @@ var init_src11 = __esm({
|
|
|
256664
256831
|
init_connector_skills();
|
|
256665
256832
|
init_connector_tool_versions();
|
|
256666
256833
|
init_board_watch();
|
|
256667
|
-
|
|
256834
|
+
init_uncategorized_project();
|
|
256668
256835
|
init_run_settlement();
|
|
256669
256836
|
init_reconcile_terminal_runs();
|
|
256670
256837
|
init_run_settlement_wiring();
|
|
@@ -258176,6 +258343,9 @@ async function startServe(opts) {
|
|
|
258176
258343
|
let memStoreShared;
|
|
258177
258344
|
let isKnowledgeQueryAvailable = async (_actorId) => false;
|
|
258178
258345
|
const actors = createActorsDomain({
|
|
258346
|
+
// 租户闸(ADR-0164 §5):`POST /api/bindings` 据它拦「把本公司员工绑到别家公司的机器上」。
|
|
258347
|
+
// 闭包读当前 nodeStore(下方 PG 分支会重赋值),与 `listBindingModelIds` 同一写法。
|
|
258348
|
+
resolveNodeTenancy: async (nodeId) => nodeStore.getNode(nodeId),
|
|
258179
258349
|
store: registryStore,
|
|
258180
258350
|
oplog,
|
|
258181
258351
|
kernel: defaultCompanyKernel,
|
|
@@ -258244,7 +258414,10 @@ async function startServe(opts) {
|
|
|
258244
258414
|
prefsStore: humanPrefsStore,
|
|
258245
258415
|
registry: registryStore,
|
|
258246
258416
|
actors: actors.service,
|
|
258247
|
-
|
|
258417
|
+
// 默认公司那一份。**闭包求值**:`defaultCompanyId` 是 let,startup 后才被库里的真实 id 覆盖
|
|
258418
|
+
// (同 :1575 那句「必须是调用时求值」)。
|
|
258419
|
+
companyId: () => defaultCompanyId,
|
|
258420
|
+
listRuntimes: (nodeId, scope) => nodeStore.listRuntimes(nodeId, scope),
|
|
258248
258421
|
isPlatformAdmin: async () => false
|
|
258249
258422
|
// 占位;controlPlaneStore 就绪后由下方 setPlatformAdminChecker 注入真判据
|
|
258250
258423
|
});
|
|
@@ -258536,8 +258709,6 @@ async function startServe(opts) {
|
|
|
258536
258709
|
getSchema: () => schema,
|
|
258537
258710
|
registry: registryStore,
|
|
258538
258711
|
artifactState: artifactStateStore,
|
|
258539
|
-
// 决策 C:剧本建单没给 projectId 时,兜底建一个由工单派生的临时项目并绑上。
|
|
258540
|
-
createProject: makeEnsureProjectFromStore(projectStateStore),
|
|
258541
258712
|
onWarn: (m2) => console.warn(m2),
|
|
258542
258713
|
workorderDrafts,
|
|
258543
258714
|
buildDraftGraph: async (draft) => buildWorkorderDraftDetail(draft, await buildResolver(registryStore), buildTypeReviewerResolver(schema, await registryStore.listActors())).graph,
|
|
@@ -258559,8 +258730,12 @@ async function startServe(opts) {
|
|
|
258559
258730
|
blobs: engine2.blobs,
|
|
258560
258731
|
registry: engine2.registry ?? registryStore,
|
|
258561
258732
|
artifactState: stores.artifacts,
|
|
258562
|
-
//
|
|
258563
|
-
|
|
258733
|
+
// 草案 store 必须按公司拿(2026-09-10 事故):此前它是装配期捕获的共享库实例,
|
|
258734
|
+
// `stage: true` 建出来的草案落大通铺、读侧却按公司解析,本公司永远查不到自己的草案。
|
|
258735
|
+
// ⚠ 这个 helper 的 const 声明在本处之后,只能在**请求时**才求值的闭包里引用
|
|
258736
|
+
// (同上方 resolveChatSession 的写法),直接在装配期引用会撞 TDZ。
|
|
258737
|
+
// planner 阻断项旁账**不在本次范围**,理由见 create-seeded-workorder.ts 里那段注释。
|
|
258738
|
+
workorderDrafts: await workorderDraftsFor(companyId)
|
|
258564
258739
|
};
|
|
258565
258740
|
}
|
|
258566
258741
|
} : {}
|
|
@@ -259871,7 +260046,9 @@ async function startServe(opts) {
|
|
|
259871
260046
|
prefsStore: plane?.humanPrefs ?? humanPrefsStore,
|
|
259872
260047
|
registry: engine2.registry,
|
|
259873
260048
|
actors: ctx.service,
|
|
259874
|
-
|
|
260049
|
+
// 这一份服务的是**请求方的公司**——助理只在这家的在线 runtime 里挑机器(ADR-0164 §5)。
|
|
260050
|
+
companyId: () => cid,
|
|
260051
|
+
listRuntimes: (nodeId, scope) => nodeStore.listRuntimes(nodeId, scope),
|
|
259875
260052
|
isPlatformAdmin: async (caller) => {
|
|
259876
260053
|
const member = await controlPlaneStore.getMember(cid, caller);
|
|
259877
260054
|
return member?.role === "owner";
|
|
@@ -259965,6 +260142,14 @@ async function startServe(opts) {
|
|
|
259965
260142
|
// ADR-0086:chat 知会条目(与 readMarkers 成对)
|
|
259966
260143
|
// ADR「多租户数据面收口」§D2:知会条目按**当前公司**取。装了路由才给(dev 文件模式沿用单实例)。
|
|
259967
260144
|
...chatStoreRouter ? { resolveChatSession: chatStoreFor } : {},
|
|
260145
|
+
/* 委派子会话不进铃铛(发起人 2026-09-10):专家跑完回流到父会话,父会话那条已经通知过一次。
|
|
260146
|
+
判据与左侧会话列表那条路同一个读口(`listChildSessionIds`,见下面 chat-sessions 域的
|
|
260147
|
+
`delegations` 接线),不另造。灰度关着 → 回空集合 = 不滤,与接入前逐字一致。 */
|
|
260148
|
+
resolveDelegationChildIds: async (companyId, sessionIds) => {
|
|
260149
|
+
if (!chatDelegationEnabledFromEnv() || sessionIds.length === 0) return [];
|
|
260150
|
+
const ledger = await delegationStoreFor(companyId).catch(() => null);
|
|
260151
|
+
return ledger ? ledger.listChildSessionIds(sessionIds) : [];
|
|
260152
|
+
},
|
|
259968
260153
|
readMarkers: readMarkerStore,
|
|
259969
260154
|
// bundle=`inbox`:水位按当前公司取(与上面 resolveChatSession 成对)。
|
|
259970
260155
|
...pgPool ? { resolveReadMarkers: readMarkerStoreFor } : {},
|
|
@@ -260176,7 +260361,9 @@ async function startServe(opts) {
|
|
|
260176
260361
|
listProjects: (companyId) => companyId === defaultCompanyId ? projectStateStore.listProjects() : Promise.resolve([]),
|
|
260177
260362
|
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
260363
|
getWorkspaceBinding: (companyId, workorderId) => companyId === defaultCompanyId ? artifactStateStore.getWorkspaceBinding(workorderId) : Promise.resolve(null),
|
|
260179
|
-
|
|
260364
|
+
// 非默认公司暂不支持项目级知识作用域(这里只有默认公司的 projectStateStore/名册可读),
|
|
260365
|
+
// 一律降级到组织作用域。旧版这个谓词还兼着"是不是 proj_tmp_ 临时项目",临时项目退役后只剩这一半。
|
|
260366
|
+
isProjectScopeUnsupported: (companyId) => companyId !== defaultCompanyId
|
|
260180
260367
|
},
|
|
260181
260368
|
actor: async (actorId, organizationId) => {
|
|
260182
260369
|
if (actorId === SYSTEM_ACTOR2) return { id: actorId, kind: "system", status: "active" };
|
|
@@ -260681,7 +260868,9 @@ async function startServe(opts) {
|
|
|
260681
260868
|
}
|
|
260682
260869
|
binding = currentBinding;
|
|
260683
260870
|
}
|
|
260684
|
-
const requireDispatchNode = (target) => {
|
|
260871
|
+
const requireDispatchNode = async (target) => {
|
|
260872
|
+
const foreign = foreignNodeBlock(await nodeStore.getNode(target.nodeId).catch(() => null), runCompanyId);
|
|
260873
|
+
if (foreign) throw new ApiError(409, "NODE_NOT_IN_COMPANY", foreign);
|
|
260685
260874
|
const connected = hub?.connectedDaemons().find((d) => d.daemonId === target.nodeId);
|
|
260686
260875
|
if (!connected) {
|
|
260687
260876
|
throw new ApiError(409, "NODE_OFFLINE", `\u7ED1\u5B9A\u8282\u70B9 ${target.nodeId} \u5F53\u524D\u4E0D\u5728\u7EBF`);
|
|
@@ -260691,7 +260880,7 @@ async function startServe(opts) {
|
|
|
260691
260880
|
}
|
|
260692
260881
|
return connected;
|
|
260693
260882
|
};
|
|
260694
|
-
let node2 = requireDispatchNode(binding);
|
|
260883
|
+
let node2 = await requireDispatchNode(binding);
|
|
260695
260884
|
if (!chatRemoteAdapter) {
|
|
260696
260885
|
throw new ApiError(503, "NODE_GATEWAY_NOT_READY", "node-gateway \u5C1A\u672A\u5C31\u7EEA");
|
|
260697
260886
|
}
|
|
@@ -260771,7 +260960,7 @@ async function startServe(opts) {
|
|
|
260771
260960
|
}
|
|
260772
260961
|
if (stableDispatch.target.nodeId !== binding.nodeId || stableDispatch.target.runtimeKind !== binding.runtimeKind) {
|
|
260773
260962
|
binding = { actorId, ...stableDispatch.target, status: "active" };
|
|
260774
|
-
node2 = requireDispatchNode(binding);
|
|
260963
|
+
node2 = await requireDispatchNode(binding);
|
|
260775
260964
|
requireNodeConnectors();
|
|
260776
260965
|
}
|
|
260777
260966
|
}
|
|
@@ -262124,6 +262313,8 @@ async function startServe(opts) {
|
|
|
262124
262313
|
checkDispatchable: async ({ actor, binding }) => {
|
|
262125
262314
|
try {
|
|
262126
262315
|
if (!binding.nodeId) return null;
|
|
262316
|
+
const foreign = foreignNodeBlock(await nodeStore.getNode(binding.nodeId).catch(() => null), companyId);
|
|
262317
|
+
if (foreign) return { reason: "node-not-in-company", errorMessage: foreign };
|
|
262127
262318
|
const drift = runtimeDriftBlock(await nodeStore.listRuntimes(binding.nodeId), binding.nodeId, binding.runtimeKind);
|
|
262128
262319
|
if (drift) return drift;
|
|
262129
262320
|
const health = healthOfNode(binding.nodeId);
|
|
@@ -269282,6 +269473,9 @@ ${res.warning}`);
|
|
|
269282
269473
|
...expect !== void 0 ? { expectedVersion: Number(expect) } : {}
|
|
269283
269474
|
});
|
|
269284
269475
|
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`);
|
|
269476
|
+
if ((flags.get("scope") ?? "actor") === "project" && rec.projectId === null) {
|
|
269477
|
+
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");
|
|
269478
|
+
}
|
|
269285
269479
|
} catch (err) {
|
|
269286
269480
|
const body2 = err instanceof ApiRequestError ? err.body : void 0;
|
|
269287
269481
|
if (body2?.current) {
|
|
@@ -270840,7 +271034,7 @@ function shimScript() {
|
|
|
270840
271034
|
}
|
|
270841
271035
|
|
|
270842
271036
|
// src/index.ts
|
|
270843
|
-
var PKG_VERSION = true ? "2.2.
|
|
271037
|
+
var PKG_VERSION = true ? "2.2.11" : "dev";
|
|
270844
271038
|
var LOCAL_BIN = localBin();
|
|
270845
271039
|
var NPM_PREFIX = npmPrefix();
|
|
270846
271040
|
var INSTANCE = DEFAULT_INSTANCE;
|