oasis_test_v2 2.2.8 → 2.2.10
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 +366 -77
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -665,7 +665,20 @@ function isAgentAvatarPresetRef(ref2) {
|
|
|
665
665
|
const id = ref2.slice(AGENT_AVATAR_PRESET_PREFIX.length);
|
|
666
666
|
return AGENT_AVATAR_PRESET_IDS.includes(id);
|
|
667
667
|
}
|
|
668
|
-
|
|
668
|
+
function humanAvatarToneOf(seed) {
|
|
669
|
+
let hash2 = 2166136261;
|
|
670
|
+
for (const ch of seed.trim()) {
|
|
671
|
+
hash2 = Math.imul(hash2 ^ (ch.codePointAt(0) ?? 0), 16777619) >>> 0;
|
|
672
|
+
}
|
|
673
|
+
return hash2 % HUMAN_AVATAR_TONE_COUNT;
|
|
674
|
+
}
|
|
675
|
+
function humanAvatarPresetRef(tone) {
|
|
676
|
+
return `${HUMAN_AVATAR_PRESET_PREFIX}${tone}`;
|
|
677
|
+
}
|
|
678
|
+
function defaultHumanAvatarRef(actorId) {
|
|
679
|
+
return humanAvatarPresetRef(humanAvatarToneOf(actorId));
|
|
680
|
+
}
|
|
681
|
+
var AGENT_AVATAR_PRESET_PREFIX, AGENT_AVATAR_LEGACY_PRESET_IDS, AGENT_AVATAR_V2_PRESET_IDS, AGENT_AVATAR_PRESET_IDS, SYSTEM_AVATAR_REF, HUMAN_AVATAR_PRESET_PREFIX, HUMAN_AVATAR_TONE_COUNT;
|
|
669
682
|
var init_avatar = __esm({
|
|
670
683
|
"../contract/src/avatar.ts"() {
|
|
671
684
|
"use strict";
|
|
@@ -695,6 +708,9 @@ var init_avatar = __esm({
|
|
|
695
708
|
...AGENT_AVATAR_LEGACY_PRESET_IDS,
|
|
696
709
|
...AGENT_AVATAR_V2_PRESET_IDS
|
|
697
710
|
];
|
|
711
|
+
SYSTEM_AVATAR_REF = "preset:system";
|
|
712
|
+
HUMAN_AVATAR_PRESET_PREFIX = "preset:human:";
|
|
713
|
+
HUMAN_AVATAR_TONE_COUNT = 8;
|
|
698
714
|
}
|
|
699
715
|
});
|
|
700
716
|
|
|
@@ -904,16 +920,35 @@ var init_link_windows = __esm({
|
|
|
904
920
|
/** orphan-sweep 判「账上在跑、四张认领表都不认」为孤儿的宽限(`serve.ts`)。 */
|
|
905
921
|
orphanGraceMs: 12e4,
|
|
906
922
|
/** orphan-sweep 在 serve 冷启动后推迟判孤儿的窗口(`serve.ts`)。 */
|
|
907
|
-
orphanColdStartGraceMs: 18e4
|
|
923
|
+
orphanColdStartGraceMs: 18e4,
|
|
924
|
+
/**
|
|
925
|
+
* `/items` 读时把「账上还 running、本进程 live 注册表里却没有」的一轮**显示**成 `interrupted`
|
|
926
|
+
* 的宽限(ADR-0510 D6 表格第二行)。
|
|
927
|
+
*
|
|
928
|
+
* 取值必须 **≥ {@link LINK_WINDOWS.orphanGraceMs}**:这条判定只写 wire、不写账本,它的作用是
|
|
929
|
+
* 让页面别在权威扫描还没开始看的时候就一直转圈;但它也**不该比权威更早下结论**——早于
|
|
930
|
+
* `orphanGraceMs` 就等于拿时间当判据抢在核实前面说话(ADR-0166)。两个数写在同一处,
|
|
931
|
+
* 谁单边调参 `tests/adr-0300-link-windows.test.ts` 当场变红。
|
|
932
|
+
*/
|
|
933
|
+
chatTurnPhaseStaleAfterMs: 12e4
|
|
908
934
|
};
|
|
909
935
|
}
|
|
910
936
|
});
|
|
911
937
|
|
|
912
938
|
// ../contract/src/registry.ts
|
|
913
|
-
|
|
939
|
+
function isSystemActorId(id) {
|
|
940
|
+
return typeof id === "string" && id.startsWith(SYSTEM_ACTOR_ID_PREFIX);
|
|
941
|
+
}
|
|
942
|
+
function isHumanActorId(id) {
|
|
943
|
+
return typeof id === "string" && id.startsWith(HUMAN_ACTOR_ID_PREFIX);
|
|
944
|
+
}
|
|
945
|
+
var SYSTEM_ACTOR_ID_PREFIX, SYSTEM_ACTOR_DISPLAY_NAME, HUMAN_ACTOR_ID_PREFIX, AGENT_PREFS_DEFAULTS, SkillOwnedByAnotherCompanyError;
|
|
914
946
|
var init_registry = __esm({
|
|
915
947
|
"../contract/src/registry.ts"() {
|
|
916
948
|
"use strict";
|
|
949
|
+
SYSTEM_ACTOR_ID_PREFIX = "actor:system";
|
|
950
|
+
SYSTEM_ACTOR_DISPLAY_NAME = "\u7CFB\u7EDF";
|
|
951
|
+
HUMAN_ACTOR_ID_PREFIX = "actor:human:";
|
|
917
952
|
AGENT_PREFS_DEFAULTS = { memoryEnabled: true };
|
|
918
953
|
SkillOwnedByAnotherCompanyError = class extends Error {
|
|
919
954
|
constructor(skillId, companyId) {
|
|
@@ -5554,6 +5589,7 @@ CREATE TABLE IF NOT EXISTS ${q2}.work_artifacts (
|
|
|
5554
5589
|
content_kind text NOT NULL CHECK (content_kind IN ('inline','external','manifest')),
|
|
5555
5590
|
ordinal integer,
|
|
5556
5591
|
created_at timestamptz NOT NULL,
|
|
5592
|
+
content_edited_at timestamptz,
|
|
5557
5593
|
UNIQUE (work_id, logical_path)
|
|
5558
5594
|
);
|
|
5559
5595
|
CREATE INDEX IF NOT EXISTS work_artifacts_node_idx ON ${q2}.work_artifacts (node_id);
|
|
@@ -5719,6 +5755,9 @@ ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS proposal_reason text;
|
|
|
5719
5755
|
-- \u2605 \u8FD0\u884C\u6D3B\u8DC3\u5FC3\u8DF3\uFF1Av2 submit_output / work.conclude \u5199\u5165\uFF1Bscan \u8D85\u65F6\u4EE5\u5B83\u8D77\u7B97\uFF08work \u751F\u547D\u5468\u671F\u5EF6\u957F\u540E
|
|
5720
5756
|
-- 30min \u949F\u4E0D\u80FD\u518D\u7F69\u6B7B\u6574\u4E2A\u4F1A\u8BDD\uFF09\u3002\u5B58\u91CF\u4E3A NULL \u2192 \u56DE\u9000 started_at/created_at\uFF0C\u884C\u4E3A\u4E0D\u53D8\u3002
|
|
5721
5757
|
ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS last_activity_at timestamptz;
|
|
5758
|
+
-- \u4EA7\u7269\u6B63\u6587\u5C31\u5730\u6539\u5199\uFF08work.content_edited\uFF09\u7684\u65F6\u523B\u3002\u5C31\u5730\u6539\u5199\u4E0D\u65B0\u5F00 work\u3001\u4E0D\u94F8\u65B0\u7248\uFF0C
|
|
5759
|
+
-- \u6CA1\u6709\u8FD9\u4E00\u5217\uFF0C\u300C\u76F8\u5173\u6587\u4EF6\u300D\u8868\u7684\u65F6\u95F4\u5217\u5C31\u53EA\u80FD\u505C\u5728\u9A8C\u6536\u65F6\u523B\u3002\u5B58\u91CF\u884C\u4E3A NULL = \u4ECE\u672A\u88AB\u6539\u5199\u8FC7\u3002
|
|
5760
|
+
ALTER TABLE ${q2}.work_artifacts ADD COLUMN IF NOT EXISTS content_edited_at timestamptz;
|
|
5722
5761
|
-- \u2605 \u51BB\u7ED3\u6765\u4FE1\u96C6\u5408\uFF08\u8BA1\u5212 \xA76.5\uFF0C\u9636\u6BB5 E\uFF09\uFF1Awork.create \u6309 reasons \u7684 issue \u9879\u51BB\u7ED3\u7684 issue id \u6279\u6B21\uFF08jsonb \u6570\u7EC4\uFF09\u3002
|
|
5723
5762
|
-- reply \u5F52\u5C5E/\u9500\u8D26/\u5BA1\u8BA1\u53EA\u8BA4\u5B83\uFF0C\u4E0D\u9760\u65F6\u95F4\u7A97\u731C\u6D4B\u3002\u5B58\u91CF\u4E3A NULL\uFF08\u65E0\u6279\u6B21\u8BED\u4E49\uFF09\u3002
|
|
5724
5763
|
ALTER TABLE ${q2}.works ADD COLUMN IF NOT EXISTS frozen_issue_ids jsonb;
|
|
@@ -11382,7 +11421,11 @@ var init_store_postgres = __esm({
|
|
|
11382
11421
|
contentRef: a.content_ref,
|
|
11383
11422
|
contentKind: a.content_kind,
|
|
11384
11423
|
ordinal: a.ordinal,
|
|
11385
|
-
createdAt: iso(a.created_at)
|
|
11424
|
+
createdAt: iso(a.created_at),
|
|
11425
|
+
// 只在真被改写过时才带这个键:从未改写的行在内存态里是**没有这个字段**的,
|
|
11426
|
+
// PG 回读时补一个 `null` 会让「事件重放出来的态」和「从库里读出来的态」深比不相等
|
|
11427
|
+
// (packages/engine/tests/store-conformance.postgres.test.ts 就是钉这条一致性的)。
|
|
11428
|
+
...iso(a.content_edited_at) ? { contentEditedAt: iso(a.content_edited_at) } : {}
|
|
11386
11429
|
})),
|
|
11387
11430
|
workInputs: inputs.rows.map((i) => ({
|
|
11388
11431
|
workId: i.work_id,
|
|
@@ -11617,9 +11660,9 @@ var init_store_postgres = __esm({
|
|
|
11617
11660
|
for (const a of m2.rows) {
|
|
11618
11661
|
await this.c.query(
|
|
11619
11662
|
`INSERT INTO ${this.s}.work_artifacts
|
|
11620
|
-
(id,work_id,node_id,type,name,logical_path,content_ref,content_kind,ordinal,created_at)
|
|
11621
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10) ON CONFLICT (id) DO NOTHING`,
|
|
11622
|
-
[a.id, a.workId, a.nodeId, a.type, a.name, a.logicalPath, a.contentRef, a.contentKind, a.ordinal, a.createdAt]
|
|
11663
|
+
(id,work_id,node_id,type,name,logical_path,content_ref,content_kind,ordinal,created_at,content_edited_at)
|
|
11664
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT (id) DO NOTHING`,
|
|
11665
|
+
[a.id, a.workId, a.nodeId, a.type, a.name, a.logicalPath, a.contentRef, a.contentKind, a.ordinal, a.createdAt, a.contentEditedAt ?? null]
|
|
11623
11666
|
);
|
|
11624
11667
|
}
|
|
11625
11668
|
break;
|
|
@@ -12620,7 +12663,7 @@ var init_work = __esm({
|
|
|
12620
12663
|
workContentEdited = {
|
|
12621
12664
|
name: "work/content-edited",
|
|
12622
12665
|
kind: "work.content_edited",
|
|
12623
|
-
apply(e, state) {
|
|
12666
|
+
apply(e, state, ctx) {
|
|
12624
12667
|
const work = state.work(e.workId);
|
|
12625
12668
|
if (!work) return;
|
|
12626
12669
|
const node2 = state.node(work.nodeId);
|
|
@@ -12628,7 +12671,7 @@ var init_work = __esm({
|
|
|
12628
12671
|
const artifacts = state.artifactsOf(e.workId);
|
|
12629
12672
|
const target = artifacts.find((a) => a.id === e.artifactId);
|
|
12630
12673
|
if (!target || target.contentRef !== e.previousRef || target.contentKind === "external") return;
|
|
12631
|
-
state.replaceArtifacts(e.workId, artifacts.map((a) => a.id === target.id ? { ...a, contentRef: e.contentRef } : a));
|
|
12674
|
+
state.replaceArtifacts(e.workId, artifacts.map((a) => a.id === target.id ? { ...a, contentRef: e.contentRef, contentEditedAt: ctx.at } : a));
|
|
12632
12675
|
}
|
|
12633
12676
|
};
|
|
12634
12677
|
workConclude = {
|
|
@@ -28733,9 +28776,22 @@ var init_chat_item_ledger = __esm({
|
|
|
28733
28776
|
...deterministicId ? { deterministicId } : {}
|
|
28734
28777
|
});
|
|
28735
28778
|
}
|
|
28736
|
-
/**
|
|
28779
|
+
/**
|
|
28780
|
+
* 服务端合成的错误帧(session.done 抛 / exit 判失败)。不入正文,归 control。
|
|
28781
|
+
*
|
|
28782
|
+
* **返回 outcome 是为了让调用方把它推上 v3 流**(ADR-0510 D5/D6,2026-09-10 修)。
|
|
28783
|
+
* 在此之前这条行只落库、不发帧:客户端在这一轮里唯一收到的终态信号是 `emitV3TurnTerminal`
|
|
28784
|
+
* 发的 `turn_failed`,而那一帧不带任何正文、客户端的 `applyFrame` 又明说「不建 item、
|
|
28785
|
+
* 仅推进 frameSeq」——于是失败原因整条飞不到还开着的页面上,只有下一次 `/items` 快照
|
|
28786
|
+
* 才补得上。生产现场(v2.openfriday.ai,2026-09-10 05:39 那一轮):runtime 报
|
|
28787
|
+
* 「401 Authentication Fails」,库里三处都记着,页面上从头到尾是空的。
|
|
28788
|
+
*
|
|
28789
|
+
* 号取的是**建行时那两格钟**(`startedVersion` 给 wire 的 `started` 帧、`version` 给这一行
|
|
28790
|
+
* 本身的写),与 provider item 走 {@link ChatItemLedger.apply} 那条路逐字同构——
|
|
28791
|
+
* 快照与增量流因此还是同一套版本空间。
|
|
28792
|
+
*/
|
|
28737
28793
|
recordError(text5, extra) {
|
|
28738
|
-
this.insert({
|
|
28794
|
+
const id = this.insert({
|
|
28739
28795
|
kind: "control",
|
|
28740
28796
|
role: "assistant",
|
|
28741
28797
|
status: "failed",
|
|
@@ -28751,6 +28807,8 @@ var init_chat_item_ledger = __esm({
|
|
|
28751
28807
|
...extra?.detail ? { detail: extra.detail } : {}
|
|
28752
28808
|
}
|
|
28753
28809
|
});
|
|
28810
|
+
const row = this.rows.get(id);
|
|
28811
|
+
return { itemId: id, version: row.wireVersion, startedVersion: row.startedVersion, ord: row.ord };
|
|
28754
28812
|
}
|
|
28755
28813
|
/**
|
|
28756
28814
|
* 回灌重启前已经落库的那段正文(`chat-recovery` 重挂 / 迟到对账用)。
|
|
@@ -29523,7 +29581,7 @@ async function sweepStaleChatTurns(deps) {
|
|
|
29523
29581
|
`[chat-turn-sweep] \u8F6E\u6B21 ${turn.id}\uFF08\u4F1A\u8BDD ${turn.chatSessionId}\uFF09\u6536\u53E3\u4E3A ${outcome.status}\uFF08${outcome.reason}\uFF09\uFF0C\u4F1A\u8BDD\u69FD\u5DF2\u91CA\u653E`
|
|
29524
29582
|
);
|
|
29525
29583
|
const settledStatus = outcome.settled ? outcome.status : void 0;
|
|
29526
|
-
if (deps.onTurnSettled && settledStatus
|
|
29584
|
+
if (deps.onTurnSettled && settledStatus) {
|
|
29527
29585
|
await deps.onTurnSettled({
|
|
29528
29586
|
chatSessionId: turn.chatSessionId,
|
|
29529
29587
|
turnId: turn.id,
|
|
@@ -159829,6 +159887,7 @@ var init_live_chat = __esm({
|
|
|
159829
159887
|
},
|
|
159830
159888
|
emitLive: (event) => this.emitLive(turn, event),
|
|
159831
159889
|
applyNormalizedEvent: (event) => this.applyNormalizedEvent(turn, event),
|
|
159890
|
+
publishServerItem: (item) => this.publishServerItem(turn, item),
|
|
159832
159891
|
subscribe: (sub) => {
|
|
159833
159892
|
turn.subscribers.add(sub);
|
|
159834
159893
|
return () => {
|
|
@@ -159975,6 +160034,45 @@ var init_live_chat = __esm({
|
|
|
159975
160034
|
};
|
|
159976
160035
|
this.publishV3Coalesced(turn, frame);
|
|
159977
160036
|
}
|
|
160037
|
+
/**
|
|
160038
|
+
* 服务端合成 item → 两帧(`started` + 终态),与 {@link emitV3} 给 provider item 发的那两帧
|
|
160039
|
+
* **逐字同构**:先用建行预留的那一格发 `started`(客户端以它为该 item 首次亮相、payload 首次成形),
|
|
160040
|
+
* 再用这一行本身的写号发 `completed` / `failed`。
|
|
160041
|
+
*
|
|
160042
|
+
* 为什么不学 {@link publishUserItem} 用一帧 `set_text`:那条路是给「一次性成形的**正文**」用的,
|
|
160043
|
+
* 落在客户端桶里 `status` 恒为 `streaming`——用户那条消息无所谓,失败收尾的 control 行却必须是
|
|
160044
|
+
* 终态,否则这一轮在客户端看来永远没关。
|
|
160045
|
+
*/
|
|
160046
|
+
publishServerItem(turn, item) {
|
|
160047
|
+
if (turn.status !== "running") return;
|
|
160048
|
+
const perItem = turn.v3Items.get(item.itemId) ?? { textOffset: 0, started: false };
|
|
160049
|
+
const ord = turn.items?.ordFor(item.itemId) ?? item.ord ?? void 0;
|
|
160050
|
+
const base = {
|
|
160051
|
+
protocolVersion: 3,
|
|
160052
|
+
streamId: turn.liveStreamId,
|
|
160053
|
+
turnId: this.wireTurnId(turn),
|
|
160054
|
+
itemId: item.itemId,
|
|
160055
|
+
itemType: item.itemType,
|
|
160056
|
+
...ord !== void 0 ? { ord } : {},
|
|
160057
|
+
offset: 0
|
|
160058
|
+
};
|
|
160059
|
+
if (!perItem.started && item.startedVersion !== void 0) {
|
|
160060
|
+
this.publishV3Coalesced(turn, {
|
|
160061
|
+
...base,
|
|
160062
|
+
operation: "started",
|
|
160063
|
+
itemVersion: item.startedVersion,
|
|
160064
|
+
payload: item.payload
|
|
160065
|
+
});
|
|
160066
|
+
perItem.started = true;
|
|
160067
|
+
}
|
|
160068
|
+
turn.v3Items.set(item.itemId, perItem);
|
|
160069
|
+
this.publishV3Coalesced(turn, {
|
|
160070
|
+
...base,
|
|
160071
|
+
operation: item.status === "failed" ? "failed" : "completed",
|
|
160072
|
+
itemVersion: item.version,
|
|
160073
|
+
payload: item.payload
|
|
160074
|
+
});
|
|
160075
|
+
}
|
|
159978
160076
|
assignV3Seq(turn, frame) {
|
|
159979
160077
|
return { ...frame, epoch: turn.liveEpoch, seq: ++turn.v3Seq };
|
|
159980
160078
|
}
|
|
@@ -160112,11 +160210,17 @@ var init_live_chat = __esm({
|
|
|
160112
160210
|
const frame = {
|
|
160113
160211
|
protocolVersion: 3,
|
|
160114
160212
|
streamId: turn.liveStreamId,
|
|
160115
|
-
|
|
160213
|
+
// 账本轮次 id,与 item 帧同一套(#1249 把 emitV3 改成了 wireTurnId,收尾帧当时漏了)。
|
|
160214
|
+
// 客户端此前把收尾帧整条丢弃(`applyFrame`:「不建 item,仅推进 frameSeq」),所以这个
|
|
160215
|
+
// 号是对是错都看不出来;相位一挂上去它就得对——否则相位会记到一个桶里根本不存在的 turnId 上。
|
|
160216
|
+
turnId: this.wireTurnId(turn),
|
|
160116
160217
|
itemId: `oasis-turn:${turn.liveEpoch}:${status}`,
|
|
160117
160218
|
itemType: "control",
|
|
160118
160219
|
operation,
|
|
160119
160220
|
itemVersion: 0,
|
|
160221
|
+
// 相位被携带,不被推导(ADR-0510 D6):这一轮到底是成了还是败了,服务端此刻知道,
|
|
160222
|
+
// 客户端替换即可,不用再去把一轮里每条 item 的 status 取或来猜。
|
|
160223
|
+
phase: status === "done" ? "succeeded" : "failed",
|
|
160120
160224
|
epoch: turn.liveEpoch,
|
|
160121
160225
|
seq: ++turn.v3Seq
|
|
160122
160226
|
};
|
|
@@ -162347,6 +162451,28 @@ var init_planner = __esm({
|
|
|
162347
162451
|
});
|
|
162348
162452
|
|
|
162349
162453
|
// ../server/src/domains/collab/workorders.ts
|
|
162454
|
+
function systemActorFallback(id) {
|
|
162455
|
+
return isSystemActorId(id) ? { name: SYSTEM_ACTOR_DISPLAY_NAME, avatar: SYSTEM_AVATAR_REF } : void 0;
|
|
162456
|
+
}
|
|
162457
|
+
function defaultAvatarRefOf(id, kind) {
|
|
162458
|
+
if (kind === "system" || isSystemActorId(id)) return SYSTEM_AVATAR_REF;
|
|
162459
|
+
if (kind === "human" || isHumanActorId(id)) return defaultHumanAvatarRef(id);
|
|
162460
|
+
return void 0;
|
|
162461
|
+
}
|
|
162462
|
+
function actorRefFieldsOf(a) {
|
|
162463
|
+
const avatar = a.avatar ?? defaultAvatarRefOf(a.id, a.kind);
|
|
162464
|
+
return {
|
|
162465
|
+
name: a.name,
|
|
162466
|
+
...a.roles[0] ? { role: a.roles[0] } : {},
|
|
162467
|
+
...avatar ? { avatar } : {}
|
|
162468
|
+
};
|
|
162469
|
+
}
|
|
162470
|
+
function actorRefFallbackOf(id) {
|
|
162471
|
+
const system = systemActorFallback(id);
|
|
162472
|
+
if (system) return system;
|
|
162473
|
+
const avatar = defaultAvatarRefOf(id);
|
|
162474
|
+
return avatar ? { avatar } : void 0;
|
|
162475
|
+
}
|
|
162350
162476
|
function overviewRootIdOf(arts) {
|
|
162351
162477
|
const roots = arts.filter((a) => a.inputs.length === 0);
|
|
162352
162478
|
if (roots.length <= 1) return roots[0]?.id;
|
|
@@ -162607,6 +162733,7 @@ var init_workorders = __esm({
|
|
|
162607
162733
|
"../server/src/domains/collab/workorders.ts"() {
|
|
162608
162734
|
"use strict";
|
|
162609
162735
|
init_src5();
|
|
162736
|
+
init_src();
|
|
162610
162737
|
init_node_state();
|
|
162611
162738
|
init_planner();
|
|
162612
162739
|
init_workorder_detail();
|
|
@@ -196005,11 +196132,12 @@ var init_service3 = __esm({
|
|
|
196005
196132
|
);
|
|
196006
196133
|
}
|
|
196007
196134
|
}
|
|
196008
|
-
async settleFromSweep(childSessionId, reason, companyId) {
|
|
196135
|
+
async settleFromSweep(childSessionId, reason, companyId, opts) {
|
|
196009
196136
|
const store = await this.options.resolveStore(companyId).catch(() => null);
|
|
196010
196137
|
if (!store) return null;
|
|
196011
196138
|
const record8 = await store.getByChildSession(childSessionId).catch(() => null);
|
|
196012
196139
|
if (!record8 || record8.state !== "running") return record8;
|
|
196140
|
+
if (opts?.outcome === "succeeded") return this.settleSucceededFromSweep(record8, companyId);
|
|
196013
196141
|
const at = this.now();
|
|
196014
196142
|
const settledSeqAtRound = record8.settledSeq + 1;
|
|
196015
196143
|
const newSummary = {
|
|
@@ -196029,6 +196157,58 @@ var init_service3 = __esm({
|
|
|
196029
196157
|
if (settled) await this.notifySettled(settled, companyId);
|
|
196030
196158
|
return settled;
|
|
196031
196159
|
}
|
|
196160
|
+
/**
|
|
196161
|
+
* 清扫拍把这一轮收成 **succeeded** 时的收口(2026-09-10 生产实测的那个洞)。
|
|
196162
|
+
*
|
|
196163
|
+
* 什么时候会走到这里:`settleRound` 是**派发那一刻建的闭包**,serve 一重启它就随老进程消失,
|
|
196164
|
+
* 这一轮改由 `chat-recovery` 重挂、跑完、把 assistant 行写成 done;随后清扫拍按「行已终态」
|
|
196165
|
+
* 把轮次收成 succeeded。此前这一支不外报,于是**没有任何人**去记这一轮、判终态、通知父会话、
|
|
196166
|
+
* 把产物搬回主会话工作区——专家干完了,页面上却永远转圈。
|
|
196167
|
+
* 现场:委派 e6196ab7 第 6 轮 04:02 写完 `outputs/org-collaboration-relationship.md` 并收尾,
|
|
196168
|
+
* 轮次 04:03:30 succeeded,台账仍是 `running`/5 轮、`settled_at` 空。
|
|
196169
|
+
*
|
|
196170
|
+
* 与失败那一支的差别只有三处,其余(追加式记账、`settledSeq`、唯一出口 `notifySettled`)逐字相同:
|
|
196171
|
+
* ① 摘要正文取**子会话这一轮真正的回答**,不是「被时钟闭合」那句;
|
|
196172
|
+
* ② 收成 `done`,不带 `failureKind`,`consecutiveFails` 归零;
|
|
196173
|
+
* ③ 多一道幂等闸,见下。
|
|
196174
|
+
*
|
|
196175
|
+
* **幂等闸**:`roundSummaries.length > dispatchSeq` 就说明这一轮 `settleRound` 已经记过了
|
|
196176
|
+
* (第 N 轮用的是 `dispatchSeq = N-1`),直接收手。没有它,「本进程没重启、`settleRound`
|
|
196177
|
+
* 正在后台跑收尾」与这一拍撞车时会把同一轮记两遍——这正是这一支原先被整个关掉的理由,
|
|
196178
|
+
* 现在用一道显式的闸换掉那个一刀切。
|
|
196179
|
+
*/
|
|
196180
|
+
async settleSucceededFromSweep(record8, companyId) {
|
|
196181
|
+
const store = await this.options.resolveStore(companyId).catch(() => null);
|
|
196182
|
+
if (!store) return record8;
|
|
196183
|
+
const roundsRecorded = record8.roundSummaries.length;
|
|
196184
|
+
const thisRound = (record8.dispatchSeq ?? 0) + 1;
|
|
196185
|
+
if (roundsRecorded >= thisRound) return record8;
|
|
196186
|
+
const at = this.now();
|
|
196187
|
+
const text5 = await this.readChildRoundAnswer(record8, thisRound, companyId);
|
|
196188
|
+
const settledSeqAtRound = record8.settledSeq + 1;
|
|
196189
|
+
const newSummary = {
|
|
196190
|
+
round: (record8.roundSummaries[record8.roundSummaries.length - 1]?.round ?? 0) + 1,
|
|
196191
|
+
text: text5 || "\u8FD9\u4E00\u8F6E\u5DF2\u5B8C\u6210\uFF08\u6536\u5C3E\u65F6\u6B63\u6587\u6CA1\u8BFB\u5230\uFF0C\u4EA7\u7269\u4EE5\u5DE5\u4F5C\u533A\u91CC\u7684\u6587\u4EF6\u4E3A\u51C6\uFF09\u3002",
|
|
196192
|
+
at,
|
|
196193
|
+
settledSeq: settledSeqAtRound
|
|
196194
|
+
};
|
|
196195
|
+
const settled = await store.appendRoundSummary(record8.id, newSummary, {
|
|
196196
|
+
state: "done",
|
|
196197
|
+
consecutiveFails: 0,
|
|
196198
|
+
settledSeq: settledSeqAtRound,
|
|
196199
|
+
settledAt: at
|
|
196200
|
+
}).catch(() => null);
|
|
196201
|
+
if (settled) await this.notifySettled(settled, companyId);
|
|
196202
|
+
return settled;
|
|
196203
|
+
}
|
|
196204
|
+
/** 子会话第 `round` 轮那条回答行的正文(`childRoundMessageIds` 是唯一的 id 口径)。 */
|
|
196205
|
+
async readChildRoundAnswer(record8, round, companyId) {
|
|
196206
|
+
const chatStore = await this.options.resolveChatSessions(companyId).catch(() => null);
|
|
196207
|
+
if (!chatStore?.listMessages) return "";
|
|
196208
|
+
const { placeholderId } = childRoundMessageIds(record8.childSessionId, round);
|
|
196209
|
+
const messages = await chatStore.listMessages(record8.childSessionId).catch(() => []);
|
|
196210
|
+
return (messages.find((m2) => m2.id === placeholderId)?.content ?? "").trim();
|
|
196211
|
+
}
|
|
196032
196212
|
/**
|
|
196033
196213
|
* 队列里还有话没送到专家眼前 → 起下一轮,**把队列内容拼进 message**(§6.2.1 第二条路径)。
|
|
196034
196214
|
*
|
|
@@ -198889,8 +199069,11 @@ var init_github = __esm({
|
|
|
198889
199069
|
sessionEnv.set("GH_CONFIG_DIR", configDir);
|
|
198890
199070
|
const appSlug = credentials["app_slug"];
|
|
198891
199071
|
if (appSlug) sessionEnv.set("GH_APP_SLUG", appSlug);
|
|
198892
|
-
const
|
|
198893
|
-
const
|
|
199072
|
+
const rawBotEmail = credentials["app_bot_email"] ?? "";
|
|
199073
|
+
const rawBotName = credentials["app_bot_name"] ?? "";
|
|
199074
|
+
const staleBot = Boolean(appSlug && rawBotEmail && !rawBotEmail.includes(`+${appSlug}[bot]@`));
|
|
199075
|
+
const botEmail = staleBot ? "" : rawBotEmail;
|
|
199076
|
+
const botName = staleBot ? appSlug ? `${appSlug}[bot]` : "" : rawBotName;
|
|
198894
199077
|
const signName = botName || name;
|
|
198895
199078
|
sessionEnv.set("GIT_AUTHOR_NAME", signName);
|
|
198896
199079
|
sessionEnv.set("GIT_AUTHOR_EMAIL", botEmail);
|
|
@@ -201180,10 +201363,8 @@ async function adoptGithubApp(store, args, deps = {}) {
|
|
|
201180
201363
|
await put3("GITHUB_APP_TOKEN_EXPIRES_AT", String(minted.expiresAtMs), false);
|
|
201181
201364
|
if (args.slug) {
|
|
201182
201365
|
const bot = await fetchAppBotIdentity(args.slug, { token: minted.token }, deps);
|
|
201183
|
-
|
|
201184
|
-
|
|
201185
|
-
await put3("GITHUB_APP_BOT_NAME", bot.login, false);
|
|
201186
|
-
}
|
|
201366
|
+
await put3("GITHUB_APP_BOT_EMAIL", bot?.email ?? "", false);
|
|
201367
|
+
await put3("GITHUB_APP_BOT_NAME", bot?.login ?? `${args.slug}[bot]`, false);
|
|
201187
201368
|
}
|
|
201188
201369
|
return {
|
|
201189
201370
|
account: args.slug ? `${args.slug}[bot]` : args.appId,
|
|
@@ -201851,7 +202032,7 @@ async function draftActorResolver(registry2) {
|
|
|
201851
202032
|
const byId = new Map(actors.map((a) => [a.id, a]));
|
|
201852
202033
|
return (id) => {
|
|
201853
202034
|
const a = byId.get(id);
|
|
201854
|
-
return a ?
|
|
202035
|
+
return a ? actorRefFieldsOf(a) : actorRefFallbackOf(id);
|
|
201855
202036
|
};
|
|
201856
202037
|
}
|
|
201857
202038
|
async function draftRoleStaffingResolver(registry2) {
|
|
@@ -203083,18 +203264,27 @@ async function runView(kernel, oplog, engineStore, blobs, name, artifactId, para
|
|
|
203083
203264
|
);
|
|
203084
203265
|
const pendingGate = pendingConcludeAttempt(model, id);
|
|
203085
203266
|
const rejectionByRevision = /* @__PURE__ */ new Map();
|
|
203267
|
+
const contentEditedByRevision = /* @__PURE__ */ new Map();
|
|
203086
203268
|
if (engineStore) {
|
|
203087
203269
|
const snap = await engineStore.transaction((tx) => tx.loadWorkorder(artifact.workspace));
|
|
203088
203270
|
for (const w2 of snap?.works ?? []) {
|
|
203089
203271
|
if (w2.rejectedReason) rejectionByRevision.set(w2.id, w2.rejectedReason);
|
|
203090
203272
|
}
|
|
203273
|
+
for (const a of snap?.artifacts ?? []) {
|
|
203274
|
+
const at = a.contentEditedAt;
|
|
203275
|
+
if (!at) continue;
|
|
203276
|
+
const workId = a.workId;
|
|
203277
|
+
const prev = contentEditedByRevision.get(workId);
|
|
203278
|
+
if (!prev || at > prev) contentEditedByRevision.set(workId, at);
|
|
203279
|
+
}
|
|
203091
203280
|
}
|
|
203092
203281
|
const revisionViews = revisions.map((rev) => ({
|
|
203093
203282
|
...rev,
|
|
203094
203283
|
stage: revisionStage(artifact, rev),
|
|
203095
203284
|
approvals: (model.reviews.get(rev.id) ?? []).filter((v2) => v2.verdict === "approve").length,
|
|
203096
203285
|
required: pendingGate?.head === rev.id ? pendingGate.gate?.quorum ?? 0 : 0,
|
|
203097
|
-
...rejectionByRevision.has(rev.id) ? { rejection: { reason: rejectionByRevision.get(rev.id) } } : {}
|
|
203286
|
+
...rejectionByRevision.has(rev.id) ? { rejection: { reason: rejectionByRevision.get(rev.id) } } : {},
|
|
203287
|
+
...contentEditedByRevision.has(rev.id) ? { contentEditedAt: contentEditedByRevision.get(rev.id) } : {}
|
|
203098
203288
|
}));
|
|
203099
203289
|
return {
|
|
203100
203290
|
artifact,
|
|
@@ -206179,10 +206369,26 @@ ${composed}`;
|
|
|
206179
206369
|
...chatExit?.reason ? { reason: chatExit.reason } : {},
|
|
206180
206370
|
...chatExit?.detail ? { detail: chatExit.detail } : {}
|
|
206181
206371
|
});
|
|
206182
|
-
itemLedger.recordError(errText2, {
|
|
206372
|
+
const errorOutcome = itemLedger.recordError(errText2, {
|
|
206183
206373
|
...chatExit?.reason ? { reason: chatExit.reason } : {},
|
|
206184
206374
|
...chatExit?.detail ? { detail: chatExit.detail } : {}
|
|
206185
206375
|
});
|
|
206376
|
+
if (live) {
|
|
206377
|
+
live.publishServerItem({
|
|
206378
|
+
itemId: errorOutcome.itemId,
|
|
206379
|
+
itemType: "control",
|
|
206380
|
+
startedVersion: errorOutcome.startedVersion,
|
|
206381
|
+
version: errorOutcome.version,
|
|
206382
|
+
ord: errorOutcome.ord,
|
|
206383
|
+
status: "failed",
|
|
206384
|
+
payload: {
|
|
206385
|
+
code: "error",
|
|
206386
|
+
note: errText2,
|
|
206387
|
+
...chatExit?.reason ? { reason: chatExit.reason } : {},
|
|
206388
|
+
...chatExit?.detail ? { detail: chatExit.detail } : {}
|
|
206389
|
+
}
|
|
206390
|
+
});
|
|
206391
|
+
}
|
|
206186
206392
|
}
|
|
206187
206393
|
finished = true;
|
|
206188
206394
|
clearInterval(ckptTimer);
|
|
@@ -206795,7 +207001,19 @@ ${composed}`;
|
|
|
206795
207001
|
fromInstallation: Boolean(installationId),
|
|
206796
207002
|
/** App 申请了、但这次安装还没被授予——多半是权限升级还等着组织 owner 批准。 */
|
|
206797
207003
|
awaitingApproval: missingAppPermissions(effective).filter((m2) => meta.permissions[m2.key]).map((m2) => m2.key),
|
|
206798
|
-
|
|
207004
|
+
/**
|
|
207005
|
+
* 这次安装**真正被授予**的全部权限,一律用 GitHub 的英文原名 + 级别
|
|
207006
|
+
* (2026-09-10 发起人:「所有权限都用英文原文,不区分是不是八项权限」)。
|
|
207007
|
+
*
|
|
207008
|
+
* 此前这里是 `GITHUB_APP_PERMISSION_LABELS.filter(...)` —— 那张表只有本仓必需的
|
|
207009
|
+
* 八项中文标签,于是这一格实际上是「必备清单打勾」,人在 GitHub 上另外勾的一个都不显示,
|
|
207010
|
+
* 看上去像没生效。中间还试过「八项中文 + 额外项英文」两段式,也被否了:
|
|
207011
|
+
* 同一个列表两套命名,反而要人先分辨「这条为什么是中文那条为什么是英文」。
|
|
207012
|
+
*
|
|
207013
|
+
* **不编中文名**:GitHub 的权限键是开放集合且还在增,写死对照表迟早漏;
|
|
207014
|
+
* 而英文原名与 GitHub 设置页上的字**逐字一致**,对着改权限时不用做心理翻译。
|
|
207015
|
+
*/
|
|
207016
|
+
granted: Object.entries(effective).filter(([, level]) => Boolean(level)).map(([key, level]) => ({ key, level: String(level) })).sort((a, b2) => a.key.localeCompare(b2.key)),
|
|
206799
207017
|
missing,
|
|
206800
207018
|
settingsUrl: meta.slug ? appPermissionSettingsUrl(meta) : null
|
|
206801
207019
|
}));
|
|
@@ -206871,7 +207089,8 @@ ${composed}`;
|
|
|
206871
207089
|
const perms = pending.permissions ?? {};
|
|
206872
207090
|
const missing = missingAppPermissions(perms);
|
|
206873
207091
|
return {
|
|
206874
|
-
|
|
207092
|
+
// 与 app/permissions 那一处同口径:英文原名 + 级别,不分是不是本仓必需的八项。
|
|
207093
|
+
granted: Object.entries(perms).filter(([, level]) => Boolean(level)).map(([key, level]) => ({ key, level: String(level) })).sort((a, b2) => a.key.localeCompare(b2.key)),
|
|
206875
207094
|
missing,
|
|
206876
207095
|
...pending.slug ? { settingsUrl: appPermissionSettingsUrl({
|
|
206877
207096
|
slug: pending.slug,
|
|
@@ -206914,10 +207133,8 @@ ${composed}`;
|
|
|
206914
207133
|
}
|
|
206915
207134
|
if (pending.slug) {
|
|
206916
207135
|
const bot = await fetchAppBotIdentity(pending.slug, { token: minted.token });
|
|
206917
|
-
|
|
206918
|
-
|
|
206919
|
-
await put3("GITHUB_APP_BOT_NAME", bot.login, false);
|
|
206920
|
-
}
|
|
207136
|
+
await put3("GITHUB_APP_BOT_EMAIL", bot?.email ?? "", false);
|
|
207137
|
+
await put3("GITHUB_APP_BOT_NAME", bot?.login ?? `${pending.slug}[bot]`, false);
|
|
206921
207138
|
}
|
|
206922
207139
|
pending.done = true;
|
|
206923
207140
|
delete pending.privateKeyPem;
|
|
@@ -207078,7 +207295,16 @@ ${composed}`;
|
|
|
207078
207295
|
body: JSON.stringify({ client_id: GH_CLIENT_ID, scope: "repo,read:user,read:org" })
|
|
207079
207296
|
});
|
|
207080
207297
|
const d = await dr.json();
|
|
207081
|
-
|
|
207298
|
+
const verifyUrl = (() => {
|
|
207299
|
+
try {
|
|
207300
|
+
const u = new URL(d.verification_uri);
|
|
207301
|
+
u.searchParams.set("user_code", d.user_code);
|
|
207302
|
+
return u.toString();
|
|
207303
|
+
} catch {
|
|
207304
|
+
return d.verification_uri;
|
|
207305
|
+
}
|
|
207306
|
+
})();
|
|
207307
|
+
send({ type: "code", code: d.user_code, url: verifyUrl });
|
|
207082
207308
|
const deadline = Date.now() + d.expires_in * 1e3;
|
|
207083
207309
|
while (!closed && Date.now() < deadline) {
|
|
207084
207310
|
await new Promise((r) => setTimeout(r, d.interval * 1e3));
|
|
@@ -207193,7 +207419,7 @@ ${composed}`;
|
|
|
207193
207419
|
* **接进既有那一拍,不新写一套扫描**——新写的那套迟早和既有判据漂移。
|
|
207194
207420
|
* 委派域没装配时返回 null,调用方照常。
|
|
207195
207421
|
*/
|
|
207196
|
-
settleDelegationFromSweep: (childSessionId, reason, companyId) => delegationService?.settleFromSweep(childSessionId, reason, companyId) ?? Promise.resolve(null),
|
|
207422
|
+
settleDelegationFromSweep: (childSessionId, reason, companyId, opts2) => delegationService?.settleFromSweep(childSessionId, reason, companyId, opts2) ?? Promise.resolve(null),
|
|
207197
207423
|
close: async () => {
|
|
207198
207424
|
if (broadcastTimer) clearInterval(broadcastTimer);
|
|
207199
207425
|
if (pendingSystemNoticesRecoveryTimer) clearInterval(pendingSystemNoticesRecoveryTimer);
|
|
@@ -210562,6 +210788,10 @@ function buildRevisionDag(revisions) {
|
|
|
210562
210788
|
}))
|
|
210563
210789
|
};
|
|
210564
210790
|
}
|
|
210791
|
+
function laterOf(a, b2) {
|
|
210792
|
+
if (!b2) return a;
|
|
210793
|
+
return b2 > a ? b2 : a;
|
|
210794
|
+
}
|
|
210565
210795
|
var DEFAULT_AGENT_ACTOR, MemoryProjectStateStore, MemoryArtifactStateStore, ProjectsService;
|
|
210566
210796
|
var init_service4 = __esm({
|
|
210567
210797
|
"../server/src/domains/projects/service.ts"() {
|
|
@@ -211418,7 +211648,11 @@ var init_service4 = __esm({
|
|
|
211418
211648
|
if (arts.length === 0) continue;
|
|
211419
211649
|
const work = worksById.get(acceptedId);
|
|
211420
211650
|
const folderPath = `wo:${workOrderId}/node:${node2.id}`;
|
|
211421
|
-
const
|
|
211651
|
+
const workUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
|
|
211652
|
+
const folderUpdatedAt = arts.reduce(
|
|
211653
|
+
(acc, a) => laterOf(acc, a.contentEditedAt),
|
|
211654
|
+
workUpdatedAt
|
|
211655
|
+
);
|
|
211422
211656
|
const creatorId = work?.assigneeActorId ?? null;
|
|
211423
211657
|
const creatorKind = creatorId ? creatorId.startsWith("actor:human:") ? "human" : "agent" : null;
|
|
211424
211658
|
const creatorName = creatorId ? this.resolveActorName(creatorId) : null;
|
|
@@ -211442,7 +211676,7 @@ var init_service4 = __esm({
|
|
|
211442
211676
|
for (const a of sortedArts) {
|
|
211443
211677
|
const artifactId = node2.id;
|
|
211444
211678
|
const artifactType = a.type ?? node2.type;
|
|
211445
|
-
const rowUpdatedAt =
|
|
211679
|
+
const rowUpdatedAt = laterOf(workUpdatedAt, a.contentEditedAt);
|
|
211446
211680
|
if (a.contentKind === "manifest" && a.contentRef?.trim()) {
|
|
211447
211681
|
const content3 = await this.readContentRefText(a.contentRef);
|
|
211448
211682
|
const files = content3 !== null ? tryParseManifestFiles(content3) : void 0;
|
|
@@ -211838,6 +212072,7 @@ var init_service4 = __esm({
|
|
|
211838
212072
|
const arts = snap.artifacts.filter((a) => a.workId === acceptedId);
|
|
211839
212073
|
if (arts.length === 0) continue;
|
|
211840
212074
|
const work = worksById.get(acceptedId);
|
|
212075
|
+
const workUpdatedAt = work?.acceptedAt ?? work?.endedAt ?? work?.lastActivityAt ?? work?.createdAt ?? node2.updatedAt;
|
|
211841
212076
|
const documents = arts.slice().sort((a, b2) => a.ordinal - b2.ordinal).map((a) => ({
|
|
211842
212077
|
// collab 层 artifactId === engine nodeId(见 bridge-readmodel.ts:215)——预览端读的
|
|
211843
212078
|
// useArtifactView 也走这个 id。
|
|
@@ -211849,7 +212084,9 @@ var init_service4 = __esm({
|
|
|
211849
212084
|
contentRef: a.contentRef,
|
|
211850
212085
|
title: node2.title || a.name,
|
|
211851
212086
|
// 节点标题优先;否则用 engine artifact 的 "output" 兜底
|
|
211852
|
-
contentKind: engineContentKind(a.contentKind)
|
|
212087
|
+
contentKind: engineContentKind(a.contentKind),
|
|
212088
|
+
// 文档级时间:被人在预览里就地改写过就跳到改写时刻,否则就是这轮执行的时刻。
|
|
212089
|
+
updatedAt: laterOf(workUpdatedAt, a.contentEditedAt)
|
|
211853
212090
|
}));
|
|
211854
212091
|
out.push({
|
|
211855
212092
|
outputId: `engine:${acceptedId}`,
|
|
@@ -211860,7 +212097,9 @@ var init_service4 = __esm({
|
|
|
211860
212097
|
documents,
|
|
211861
212098
|
payload: {},
|
|
211862
212099
|
createdAt: work?.createdAt ?? node2.createdAt,
|
|
211863
|
-
|
|
212100
|
+
// 节点(= 文件夹行)的时间取「这轮执行的时刻」与「其下任一文档最后被就地改写的时刻」里更晚的那个:
|
|
212101
|
+
// 改过某个文件之后,文件夹不能还显示比自己的孩子更早的时间。
|
|
212102
|
+
updatedAt: documents.reduce((acc, d) => laterOf(acc, d.updatedAt), workUpdatedAt)
|
|
211864
212103
|
});
|
|
211865
212104
|
}
|
|
211866
212105
|
return out;
|
|
@@ -222932,7 +223171,8 @@ function toApiNodeOutputDocument(doc) {
|
|
|
222932
223171
|
summary: doc.summary,
|
|
222933
223172
|
...doc.contentKind !== void 0 ? { content_kind: doc.contentKind } : {},
|
|
222934
223173
|
...doc.size !== void 0 ? { size: doc.size } : {},
|
|
222935
|
-
...doc.files !== void 0 ? { files: doc.files.map((f2) => ({ path: f2.path, ...f2.size !== void 0 ? { size: f2.size } : {} })) } : {}
|
|
223174
|
+
...doc.files !== void 0 ? { files: doc.files.map((f2) => ({ path: f2.path, ...f2.size !== void 0 ? { size: f2.size } : {} })) } : {},
|
|
223175
|
+
...doc.updatedAt !== void 0 ? { updated_at: doc.updatedAt } : {}
|
|
222936
223176
|
};
|
|
222937
223177
|
}
|
|
222938
223178
|
function toApiNodeOutputValidation(validation) {
|
|
@@ -226529,12 +226769,16 @@ function buildWorkorderActivity(input) {
|
|
|
226529
226769
|
const reasonKinds = new Set(reasons.map((r) => String(r.kind)));
|
|
226530
226770
|
const executorId = w2?.assigneeActorId ?? rec.actorId;
|
|
226531
226771
|
if (isPendingActor(executorId)) break;
|
|
226532
|
-
const
|
|
226533
|
-
const
|
|
226534
|
-
|
|
226535
|
-
|
|
226536
|
-
|
|
226537
|
-
const
|
|
226772
|
+
const forcedBy = str4(reasons.find((r) => r.kind === "forced")?.actorId);
|
|
226773
|
+
const gapDriven = reasons.some((r) => r.kind === "issue" && r.issueKind === "gap");
|
|
226774
|
+
const status = reasonKinds.has("rework") ? ACTIVITY_COPY.reworking(nodeName(nodeId)) : reasonKinds.has("resumed") ? ACTIVITY_COPY.resumed(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("spec_changed") ? ACTIVITY_COPY.specChanged(nodeName(nodeId)) : gapDriven ? ACTIVITY_COPY.gapReworked(nameOf(executorId), nodeName(nodeId)) : reasonKinds.has("issue") ? ACTIVITY_COPY.issueDriven(nodeName(nodeId)) : forcedBy ? ACTIVITY_COPY.forcedRerun(nameOf(forcedBy), nodeName(nodeId)) : ACTIVITY_COPY.working(nodeName(nodeId));
|
|
226775
|
+
for (const r of reasons) {
|
|
226776
|
+
if (r.kind !== "issue") continue;
|
|
226777
|
+
const issueId = str4(r.issueId);
|
|
226778
|
+
if (!issueId) continue;
|
|
226779
|
+
workOfIssue.set(issueId, workId);
|
|
226780
|
+
const ic = plainCommentIssueIds.has(issueId) ? issueCards.get(issueId) : void 0;
|
|
226781
|
+
const commentAuthor = issueById.get(issueId)?.authorActorId;
|
|
226538
226782
|
if (ic && commentAuthor) {
|
|
226539
226783
|
touch(ic, rec);
|
|
226540
226784
|
ic.executorId = executorId;
|
|
@@ -227091,6 +227335,22 @@ var init_activity2 = __esm({
|
|
|
227091
227335
|
reworking: (node2) => `\u6B63\u5728\u6839\u636E\u5BA1\u6838\u4E0D\u901A\u8FC7\u539F\u56E0\u4FEE\u6539\u300A${node2}\u300B\u3002`,
|
|
227092
227336
|
specChanged: (node2) => `\u6B63\u5728\u6839\u636E\u4EFB\u52A1\u8BF4\u660E\u7684\u66F4\u65B0\u8C03\u6574\u300A${node2}\u300B\u3002`,
|
|
227093
227337
|
issueDriven: (node2) => `\u6B63\u5728\u6839\u636E\u53CD\u9988\u8C03\u6574\u300A${node2}\u300B\u3002`,
|
|
227338
|
+
/**
|
|
227339
|
+
* 缺口解开后**带着答复重开工**的那一轮(`reasons` 里有 `{kind:"issue", issueKind:"gap"}`)。
|
|
227340
|
+
*
|
|
227341
|
+
* 与 {@link issueDriven} 分开:那句说的是「按反馈调整」,而这一轮的事实是**卡点解除、重新开始**
|
|
227342
|
+
* ——中断链上最该看清的一拍。此前两者共用一句,恢复后的那一轮被说回泛泛的「正在根据反馈调整」。
|
|
227343
|
+
* 实测 next 213 次 / 23 单、v2 4 次,最近一次 2026-09-09(`issue` 里第二多的一档)。
|
|
227344
|
+
*/
|
|
227345
|
+
gapReworked: (executor, node2) => `\u7F3A\u53E3\u5DF2\u89E3\u51B3\uFF0C${executor} \u5E26\u7740\u7B54\u590D\u91CD\u65B0\u5F00\u5DE5\u300A${node2}\u300B\u3002`,
|
|
227346
|
+
/**
|
|
227347
|
+
* 人点了 UI 的「重新运行」(`kernel-bridge.rerunNode`,ADR 0137)——`reasons` 里那条
|
|
227348
|
+
* `{kind:"forced", actorId}` 就是点的人。
|
|
227349
|
+
*
|
|
227350
|
+
* 此前没有自己的文案、落到 `working` 说成「正在处理《X》。」,**页面上完全看不出这一轮是
|
|
227351
|
+
* 有人手动强推的**。实测两条生产线合计 80 次(next 73 / v2 7,最近一次 2026-09-09)。
|
|
227352
|
+
*/
|
|
227353
|
+
forcedRerun: (by, node2) => `${by} \u91CD\u65B0\u8FD0\u884C\u4E86\u300A${node2}\u300B\u3002`,
|
|
227094
227354
|
/* 自动判定的那一刻**只说事实,不断言谁介入了**。
|
|
227095
227355
|
`work.timeout` 由引擎发(`engine/activate.ts` 与 `server/run-settlement.ts`),失败后的重试
|
|
227096
227356
|
也是引擎做的(`activate.ts` 的 `tryToRun`)——管理者根本不知道这两件事,此前却写成
|
|
@@ -228289,6 +228549,21 @@ function nodeIdOfWorkId(workId) {
|
|
|
228289
228549
|
const nodeId = body2.slice(0, cut);
|
|
228290
228550
|
return nodeId.startsWith("artifact:") ? nodeId : null;
|
|
228291
228551
|
}
|
|
228552
|
+
function nodeIdOfIssueId(issueId, model) {
|
|
228553
|
+
if (!issueId) return null;
|
|
228554
|
+
const anchored = model.annotations.get(issueId)?.anchor.target;
|
|
228555
|
+
if (anchored) {
|
|
228556
|
+
if (anchored.startsWith("artifact:")) return anchored;
|
|
228557
|
+
const fromWork = nodeIdOfWorkId(anchored);
|
|
228558
|
+
if (fromWork) return fromWork;
|
|
228559
|
+
}
|
|
228560
|
+
const body2 = issueId.replace(/^(?:ann:rework:)?(?:gap:|esc:|ann:)/, "");
|
|
228561
|
+
if (!body2.startsWith("artifact:")) return null;
|
|
228562
|
+
const cut = body2.lastIndexOf(":");
|
|
228563
|
+
if (cut <= 0) return null;
|
|
228564
|
+
const nodeId = body2.slice(0, cut);
|
|
228565
|
+
return nodeId.startsWith("artifact:") ? nodeId : null;
|
|
228566
|
+
}
|
|
228292
228567
|
function eventToAuditEntry(rec) {
|
|
228293
228568
|
const ev = rec.event;
|
|
228294
228569
|
const artifactId = rec.nodeId ?? nodeIdOfWorkId(rec.workId) ?? "";
|
|
@@ -228301,20 +228576,25 @@ function eventToAuditEntry(rec) {
|
|
|
228301
228576
|
summary: `${KIND_LABEL2[ev.kind] ?? ev.kind}${artifactId ? ` \xB7 ${artifactId}` : ""}`
|
|
228302
228577
|
};
|
|
228303
228578
|
}
|
|
228304
|
-
function
|
|
228305
|
-
|
|
228306
|
-
|
|
228307
|
-
|
|
228308
|
-
|
|
228309
|
-
const
|
|
228310
|
-
|
|
228311
|
-
|
|
228312
|
-
return {
|
|
228313
|
-
...entry,
|
|
228314
|
-
nodeName: nodeLabel(a),
|
|
228315
|
-
...workspaceTitle ? { workspaceTitle } : {},
|
|
228316
|
-
visibility: "ok"
|
|
228579
|
+
function auditAnchorsOf(rec) {
|
|
228580
|
+
const issueId = rec.event.issueId;
|
|
228581
|
+
return { workorderId: rec.workorderId, ...typeof issueId === "string" && issueId ? { issueId } : {} };
|
|
228582
|
+
}
|
|
228583
|
+
function enrichAudit(entry, model, titleOf, anchors = {}) {
|
|
228584
|
+
const withNode = (id, a) => {
|
|
228585
|
+
const workspaceTitle2 = titleOf.get(a.workspace);
|
|
228586
|
+
return { ...entry, artifactId: id, nodeName: nodeLabel(a), ...workspaceTitle2 ? { workspaceTitle: workspaceTitle2 } : {}, visibility: "ok" };
|
|
228317
228587
|
};
|
|
228588
|
+
if (entry.artifactId) {
|
|
228589
|
+
const a = model.artifacts.get(entry.artifactId);
|
|
228590
|
+
if (!a) return { ...entry, visibility: "deleted" };
|
|
228591
|
+
return withNode(entry.artifactId, a);
|
|
228592
|
+
}
|
|
228593
|
+
const viaIssue = nodeIdOfIssueId(anchors.issueId, model);
|
|
228594
|
+
const viaIssueArtifact = viaIssue ? model.artifacts.get(viaIssue) : void 0;
|
|
228595
|
+
if (viaIssue && viaIssueArtifact) return withNode(viaIssue, viaIssueArtifact);
|
|
228596
|
+
const workspaceTitle = anchors.workorderId ? titleOf.get(anchors.workorderId) : void 0;
|
|
228597
|
+
return { ...entry, ...workspaceTitle ? { workspaceTitle } : {}, visibility: "ok" };
|
|
228318
228598
|
}
|
|
228319
228599
|
async function buildAudit(source, q2 = {}, model) {
|
|
228320
228600
|
const limit = Math.min(Math.max(q2.limit ?? 100, 1), 500);
|
|
@@ -228323,8 +228603,8 @@ async function buildAudit(source, q2 = {}, model) {
|
|
|
228323
228603
|
const items = [];
|
|
228324
228604
|
let lastCursor = null;
|
|
228325
228605
|
let truncated = false;
|
|
228326
|
-
const push2 = (entry, cursor,
|
|
228327
|
-
items.push(model && titleOf ? enrichAudit(entry, model, titleOf,
|
|
228606
|
+
const push2 = (entry, cursor, anchors) => {
|
|
228607
|
+
items.push(model && titleOf ? enrichAudit(entry, model, titleOf, anchors) : entry);
|
|
228328
228608
|
lastCursor = cursor;
|
|
228329
228609
|
};
|
|
228330
228610
|
const match = (actor, kind, at) => {
|
|
@@ -228341,7 +228621,7 @@ async function buildAudit(source, q2 = {}, model) {
|
|
|
228341
228621
|
for (const rec of records) {
|
|
228342
228622
|
const ev = rec.event;
|
|
228343
228623
|
if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
|
|
228344
|
-
collected.push({ entry: eventToAuditEntry(rec),
|
|
228624
|
+
collected.push({ entry: eventToAuditEntry(rec), anchors: auditAnchorsOf(rec) });
|
|
228345
228625
|
}
|
|
228346
228626
|
} else if (source.oplog) {
|
|
228347
228627
|
const src = source.oplog.readFilteredOps ? source.oplog.readFilteredOps({ ...q2.actor ? { actor: q2.actor } : {}, ...q2.kind ? { kind: q2.kind } : {} }, void 0, { latest: true }) : source.oplog.readAll();
|
|
@@ -228352,7 +228632,7 @@ async function buildAudit(source, q2 = {}, model) {
|
|
|
228352
228632
|
}
|
|
228353
228633
|
const tail = collected.slice(-limit).reverse();
|
|
228354
228634
|
return {
|
|
228355
|
-
items: model && titleOf ? tail.map(({ entry,
|
|
228635
|
+
items: model && titleOf ? tail.map(({ entry, anchors }) => enrichAudit(entry, model, titleOf, anchors)) : tail.map(({ entry }) => entry),
|
|
228356
228636
|
cursor: null
|
|
228357
228637
|
};
|
|
228358
228638
|
}
|
|
@@ -228361,7 +228641,7 @@ async function buildAudit(source, q2 = {}, model) {
|
|
|
228361
228641
|
for (const rec of records) {
|
|
228362
228642
|
const ev = rec.event;
|
|
228363
228643
|
if (!match(rec.actorId, ev.kind, rec.createdAt)) continue;
|
|
228364
|
-
push2(eventToAuditEntry(rec), String(rec.seq), rec
|
|
228644
|
+
push2(eventToAuditEntry(rec), String(rec.seq), auditAnchorsOf(rec));
|
|
228365
228645
|
if (items.length >= limit) {
|
|
228366
228646
|
truncated = true;
|
|
228367
228647
|
break;
|
|
@@ -229249,11 +229529,7 @@ async function buildResolver(registry2) {
|
|
|
229249
229529
|
const byId = new Map(actors.map((a) => [a.id, a]));
|
|
229250
229530
|
return (id) => {
|
|
229251
229531
|
const a = byId.get(id);
|
|
229252
|
-
return a ?
|
|
229253
|
-
name: a.name,
|
|
229254
|
-
...a.roles[0] ? { role: a.roles[0] } : {},
|
|
229255
|
-
...a.avatar ? { avatar: a.avatar } : {}
|
|
229256
|
-
} : void 0;
|
|
229532
|
+
return a ? actorRefFieldsOf(a) : actorRefFallbackOf(id);
|
|
229257
229533
|
};
|
|
229258
229534
|
}
|
|
229259
229535
|
async function buildProjectResolver(artifacts) {
|
|
@@ -233656,12 +233932,20 @@ function createChatSessionsDomain(opts) {
|
|
|
233656
233932
|
const limitRaw = Number(req.query?.get("limit") ?? "0") || 0;
|
|
233657
233933
|
const limit = limitRaw > 0 ? Math.min(limitRaw, MAX_SNAPSHOT_ITEMS) : DEFAULT_SNAPSHOT_ITEMS;
|
|
233658
233934
|
const cursor = opts.liveChat?.cursorFor(chatSessionId) ?? null;
|
|
233659
|
-
|
|
233660
|
-
|
|
233661
|
-
|
|
233662
|
-
|
|
233663
|
-
|
|
233664
|
-
|
|
233935
|
+
const turns = await turnsFor(req);
|
|
233936
|
+
const activeTurnRow = turns ? await turns.activeTurn(chatSessionId).catch(() => null) : null;
|
|
233937
|
+
const turnId = cursor?.turnId ?? activeTurnRow?.id ?? null;
|
|
233938
|
+
const activeTurn = (() => {
|
|
233939
|
+
if (!activeTurnRow) return null;
|
|
233940
|
+
const settled = activeTurnRow.status !== "reserved" && activeTurnRow.status !== "running";
|
|
233941
|
+
if (settled) return { turnId: activeTurnRow.id, phase: activeTurnRow.status };
|
|
233942
|
+
if (opts.liveChat?.isRunning(chatSessionId)) {
|
|
233943
|
+
return { turnId: activeTurnRow.id, phase: "running" };
|
|
233944
|
+
}
|
|
233945
|
+
const startedAt = Date.parse(activeTurnRow.startedAt ?? activeTurnRow.reservedAt);
|
|
233946
|
+
const stale = Number.isFinite(startedAt) && Date.now() - startedAt >= LINK_WINDOWS.chatTurnPhaseStaleAfterMs;
|
|
233947
|
+
return { turnId: activeTurnRow.id, phase: stale ? "interrupted" : "running" };
|
|
233948
|
+
})();
|
|
233665
233949
|
const snapshotEntries = [];
|
|
233666
233950
|
let nextSinceVersion = sinceVersion;
|
|
233667
233951
|
let hasMoreBefore = false;
|
|
@@ -233693,6 +233977,7 @@ function createChatSessionsDomain(opts) {
|
|
|
233693
233977
|
nextSinceVersion,
|
|
233694
233978
|
frameCursor: cursor ? { epoch: cursor.epoch, seq: cursor.seq } : { epoch: 0, seq: 0 },
|
|
233695
233979
|
canAppend: cursor?.canAppend ?? false,
|
|
233980
|
+
activeTurn,
|
|
233696
233981
|
oldestOrd,
|
|
233697
233982
|
hasMoreBefore
|
|
233698
233983
|
};
|
|
@@ -233830,7 +234115,7 @@ function createChatSessionsDomain(opts) {
|
|
|
233830
234115
|
const byId = new Map(actors.map((a) => [a.id, a]));
|
|
233831
234116
|
const resolver2 = (id) => {
|
|
233832
234117
|
const a = byId.get(id);
|
|
233833
|
-
return a ? { name: a.name } :
|
|
234118
|
+
return a ? { name: a.name } : systemActorFallback(id);
|
|
233834
234119
|
};
|
|
233835
234120
|
const allSummaries = buildWorkorderSummaries(wkKernel.model, resolver2, (wo) => byWo.get(wo) ?? null, (wo) => held.has(wo), void 0, void 0, (role) => wkKernel.actorForRole(role));
|
|
233836
234121
|
const summaryById = new Map(allSummaries.map((s2) => [s2.id, s2]));
|
|
@@ -256333,6 +256618,7 @@ var init_src11 = __esm({
|
|
|
256333
256618
|
init_node_store();
|
|
256334
256619
|
init_tokens();
|
|
256335
256620
|
init_collab();
|
|
256621
|
+
init_workorders();
|
|
256336
256622
|
init_collab();
|
|
256337
256623
|
init_closure_report();
|
|
256338
256624
|
init_workorders();
|
|
@@ -258049,7 +258335,8 @@ async function startServe(opts) {
|
|
|
258049
258335
|
kernel: defaultCompanyKernel,
|
|
258050
258336
|
// B2:读路径走 oplog 派生投影——同一 oplog 写后重放重建
|
|
258051
258337
|
oplog,
|
|
258052
|
-
|
|
258338
|
+
// 系统身份(`actor:system*`)不在名册里,缓存必然打空——补同一份展示名,别让它退成裸 id。
|
|
258339
|
+
actorNames: { resolve: (actorId) => registryActorNames.get(actorId) ?? systemActorFallback(actorId)?.name },
|
|
258053
258340
|
blobs,
|
|
258054
258341
|
// 项目普通文件上传入 blob 库
|
|
258055
258342
|
actorDirectory: registryStore,
|
|
@@ -258085,7 +258372,7 @@ async function startServe(opts) {
|
|
|
258085
258372
|
...engine2.blobs ? { blobs: engine2.blobs } : {},
|
|
258086
258373
|
actorDirectory: companyRegistry,
|
|
258087
258374
|
actorNames: {
|
|
258088
|
-
resolve: (actorId) => perCompanyActorNames.get(companyId)?.get(actorId)
|
|
258375
|
+
resolve: (actorId) => perCompanyActorNames.get(companyId)?.get(actorId) ?? systemActorFallback(actorId)?.name
|
|
258089
258376
|
},
|
|
258090
258377
|
resolveActorContext: async (actorId) => {
|
|
258091
258378
|
const config2 = await companyRegistry.latestConfig(actorId).catch(() => null);
|
|
@@ -258146,7 +258433,8 @@ async function startServe(opts) {
|
|
|
258146
258433
|
ops: [],
|
|
258147
258434
|
nodeOutputs: persisted.nodeOutputs,
|
|
258148
258435
|
workspaceBindings: persisted.workspaceBindings,
|
|
258149
|
-
|
|
258436
|
+
// 系统身份(`actor:system*`)不在名册里,缓存必然打空——补同一份展示名,别让它退成裸 id。
|
|
258437
|
+
actorNames: { resolve: (actorId) => registryActorNames.get(actorId) ?? systemActorFallback(actorId)?.name }
|
|
258150
258438
|
});
|
|
258151
258439
|
};
|
|
258152
258440
|
const traceDsn = process.env["OASIS_TRACE_PG_DSN"] ?? pgDsn;
|
|
@@ -261209,7 +261497,8 @@ async function startServe(opts) {
|
|
|
261209
261497
|
await server.settleDelegationFromSweep(
|
|
261210
261498
|
chatSessionId,
|
|
261211
261499
|
`\u5B50\u4F1A\u8BDD\u8FD9\u4E00\u8F6E\u88AB\u6062\u590D\u5668\u6536\u53E3\u4E3A ${status}\uFF08${reason}\uFF09`,
|
|
261212
|
-
companyId
|
|
261500
|
+
companyId,
|
|
261501
|
+
{ outcome: status === "succeeded" ? "succeeded" : "failed" }
|
|
261213
261502
|
);
|
|
261214
261503
|
},
|
|
261215
261504
|
log: (m2) => console.log(m2),
|
|
@@ -270551,7 +270840,7 @@ function shimScript() {
|
|
|
270551
270840
|
}
|
|
270552
270841
|
|
|
270553
270842
|
// src/index.ts
|
|
270554
|
-
var PKG_VERSION = true ? "2.2.
|
|
270843
|
+
var PKG_VERSION = true ? "2.2.10" : "dev";
|
|
270555
270844
|
var LOCAL_BIN = localBin();
|
|
270556
270845
|
var NPM_PREFIX = npmPrefix();
|
|
270557
270846
|
var INSTANCE = DEFAULT_INSTANCE;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oasis_test_v2",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.10",
|
|
4
4
|
"description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
|
|
5
5
|
"bin": {
|
|
6
6
|
"oasis": "./dist/index.js"
|
|
@@ -26,6 +26,6 @@
|
|
|
26
26
|
"node": ">=20"
|
|
27
27
|
},
|
|
28
28
|
"oasisRelease": {
|
|
29
|
-
"sourceHead": "
|
|
29
|
+
"sourceHead": "3eac5f26307c5d0cf750e4a7edc6a5f60bdbe63d"
|
|
30
30
|
}
|
|
31
31
|
}
|