newmark-agent 0.6.3 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +206 -9
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +60 -4
- package/dist/core/agentKernelRunner.js +15 -2
- package/dist/core/branchIdentity.d.ts +29 -0
- package/dist/core/branchIdentity.js +54 -0
- package/dist/core/continuation/contracts.d.ts +1 -1
- package/dist/core/continuation/store.d.ts +28 -0
- package/dist/core/continuation/store.js +89 -1
- package/dist/core/conversationKernel.d.ts +13 -1
- package/dist/core/conversationKernel.js +62 -0
- package/dist/main.js +1 -1
- package/dist/ui/index.html +343 -44
- package/dist/wsl-agent-host.bundle.cjs +206 -9
- package/package.json +4 -2
|
@@ -126966,6 +126966,23 @@ var SkillsManager = class {
|
|
|
126966
126966
|
// src/core/agent.ts
|
|
126967
126967
|
init_flow();
|
|
126968
126968
|
|
|
126969
|
+
// src/core/branchIdentity.ts
|
|
126970
|
+
var BRANCH_IDENTITY_SEPARATOR = "::branch:";
|
|
126971
|
+
function branchConversationIdentity(conversationId, branchNodeId) {
|
|
126972
|
+
const conversation = String(conversationId || "").trim() || "default";
|
|
126973
|
+
const branch = String(branchNodeId || "").trim();
|
|
126974
|
+
return branch ? `${conversation}${BRANCH_IDENTITY_SEPARATOR}${branch}` : conversation;
|
|
126975
|
+
}
|
|
126976
|
+
function assertFreshBranchIdentity(conversationId, branchNodeId, existing) {
|
|
126977
|
+
const branch = String(branchNodeId || "").trim();
|
|
126978
|
+
if (!branch) throw new Error("Branch conversation identity requires a branch node id.");
|
|
126979
|
+
for (const known of existing) {
|
|
126980
|
+
if (String(known || "") === branch) {
|
|
126981
|
+
throw new Error(`Branch conversation identity ${branchConversationIdentity(conversationId, branch)} is already in use.`);
|
|
126982
|
+
}
|
|
126983
|
+
}
|
|
126984
|
+
}
|
|
126985
|
+
|
|
126969
126986
|
// src/core/agentKernelRunner.ts
|
|
126970
126987
|
var fs19 = __toESM(require("fs"));
|
|
126971
126988
|
var path21 = __toESM(require("path"));
|
|
@@ -128046,10 +128063,16 @@ var HIDDEN_LINE_PREFIXES = [
|
|
|
128046
128063
|
];
|
|
128047
128064
|
function providerSessionIdentity(agent) {
|
|
128048
128065
|
const conversationId = String(agent.activeConversationId || "default");
|
|
128066
|
+
let runBranchId = "";
|
|
128067
|
+
try {
|
|
128068
|
+
runBranchId = String(agent.activeWorkRunBranchId() || "");
|
|
128069
|
+
} catch {
|
|
128070
|
+
runBranchId = "";
|
|
128071
|
+
}
|
|
128072
|
+
if (runBranchId) return branchConversationIdentity(conversationId, runBranchId);
|
|
128049
128073
|
try {
|
|
128050
128074
|
const snapshot2 = agent.getConversationSnapshot(conversationId);
|
|
128051
|
-
|
|
128052
|
-
return branchId ? `${conversationId}::branch:${branchId}` : conversationId;
|
|
128075
|
+
return branchConversationIdentity(conversationId, String(snapshot2.runtimeBranchId || snapshot2.activeBranchId || ""));
|
|
128053
128076
|
} catch {
|
|
128054
128077
|
return conversationId;
|
|
128055
128078
|
}
|
|
@@ -132797,6 +132820,22 @@ var Agent5 = class _Agent {
|
|
|
132797
132820
|
currentWorkRunId() {
|
|
132798
132821
|
return this.activeWorkRunId || this.finalizingWorkRunId || "";
|
|
132799
132822
|
}
|
|
132823
|
+
/**
|
|
132824
|
+
* The branch node that owns the currently active Build. Provider session ids
|
|
132825
|
+
* and branch-scoped caches must use this identity instead of "the branch that
|
|
132826
|
+
* happens to be active", otherwise a concurrently running sibling branch
|
|
132827
|
+
* (branch-communication mode) or a page switched mid-Build would read another
|
|
132828
|
+
* branch's remote session and folded history.
|
|
132829
|
+
*/
|
|
132830
|
+
activeWorkRunBranchId() {
|
|
132831
|
+
const runId = this.currentWorkRunId();
|
|
132832
|
+
const run = runId ? this.workRuns.find((item) => String(item.runId || "") === runId) : void 0;
|
|
132833
|
+
return String(run?.branchNodeId || "") || this.currentBranchNodeId();
|
|
132834
|
+
}
|
|
132835
|
+
/** Full conversation identity of one branch node (shared by every branch kind). */
|
|
132836
|
+
branchConversationIdentity(branchNodeId) {
|
|
132837
|
+
return branchConversationIdentity(this.activeConversationId || "default", branchNodeId);
|
|
132838
|
+
}
|
|
132800
132839
|
activeModelConfig() {
|
|
132801
132840
|
const deployment = this.activeDeployment();
|
|
132802
132841
|
return deployment ? this.config.findDeployment(deployment) : this.config.findModel(this.model);
|
|
@@ -135035,6 +135074,7 @@ ${next2.body}`;
|
|
|
135035
135074
|
});
|
|
135036
135075
|
const reusableGroup = Object.values(tree.branchGroups).find((group) => group.sourceNodeId === parentNodeId && (targetGuideNodeId && group.sourceGuideId === targetGuideNodeId || !targetGuideNodeId && targetMessageId && group.sourceMessageId === targetMessageId || !group.sourceGuideId && !group.sourceMessageId && group.sourceMessageIndex === index2) || group.nodeIds.includes(parentNodeId) && group.sourceMessageIndex === index2 && (targetGuideNodeId && group.pageGuideIds?.[parentNodeId] === targetGuideNodeId || !targetGuideNodeId && targetMessageId && group.pageMessageIds?.[parentNodeId] === targetMessageId));
|
|
135037
135076
|
const branchId = crypto15.randomUUID();
|
|
135077
|
+
assertFreshBranchIdentity(clean || this.activeConversationId || "default", branchId, Object.keys(tree.nodes));
|
|
135038
135078
|
const branch = this.treeNodeFromEntry(branchId, parentNodeId, index2, text3, {
|
|
135039
135079
|
...entry,
|
|
135040
135080
|
chatMessages: sourceNode.chatMessages.slice(0, index2),
|
|
@@ -135619,7 +135659,7 @@ Conversation title (a few words):`;
|
|
|
135619
135659
|
chatMessages: [...this.chatMessages],
|
|
135620
135660
|
history: [...this.history],
|
|
135621
135661
|
providerUsage: this.conversationProviderUsage(),
|
|
135622
|
-
compressionCache:
|
|
135662
|
+
compressionCache: this.mergedCompressionCacheForPersist(stored.conversations[stateKey2]),
|
|
135623
135663
|
branchMailbox: [...this.branchMailbox],
|
|
135624
135664
|
branchCommunication: this.branchCommunicationEnabled,
|
|
135625
135665
|
plan: this.normalizeConversationPlan(this.conversationPlan),
|
|
@@ -135677,7 +135717,7 @@ Conversation title (a few words):`;
|
|
|
135677
135717
|
if (saved) {
|
|
135678
135718
|
this.restoreProviderUsage(saved.providerUsage);
|
|
135679
135719
|
this.history = [...saved.history];
|
|
135680
|
-
this.compressionCache =
|
|
135720
|
+
this.compressionCache = this.compressionCacheForBranch(saved.compressionCache);
|
|
135681
135721
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map((entry) => Number(entry.id.replace(/^ctx-cache-/, "")) || 0)) + 1;
|
|
135682
135722
|
this.branchMailbox = (saved.branchMailbox || []).map((message) => ({ ...message }));
|
|
135683
135723
|
this.nextBranchMessageSequence = Math.max(1, ...this.branchMailbox.map((message) => Number(message.sequence) || 0)) + 1;
|
|
@@ -135706,7 +135746,7 @@ Conversation title (a few words):`;
|
|
|
135706
135746
|
this.restoreProviderUsage(persisted?.providerUsage);
|
|
135707
135747
|
const repairedHistory = this.repairDanglingToolCalls(persisted?.history ? [...persisted.history] : []);
|
|
135708
135748
|
this.history = repairedHistory.messages;
|
|
135709
|
-
this.compressionCache = persisted?.compressionCache
|
|
135749
|
+
this.compressionCache = this.compressionCacheForBranch(persisted?.compressionCache);
|
|
135710
135750
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map((entry) => Number(entry.id.replace(/^ctx-cache-/, "")) || 0)) + 1;
|
|
135711
135751
|
this.branchMailbox = (persisted?.branchMailbox || []).map((message) => ({ ...message }));
|
|
135712
135752
|
this.nextBranchMessageSequence = Math.max(1, ...this.branchMailbox.map((message) => Number(message.sequence) || 0)) + 1;
|
|
@@ -140558,7 +140598,36 @@ ${text3.slice(-tailChars).trimStart()}`;
|
|
|
140558
140598
|
}
|
|
140559
140599
|
compressionArchiveScopeKey() {
|
|
140560
140600
|
if (this.isSubagentRuntime || !this.config.getBool("context", "compression_archive_enabled")) return null;
|
|
140561
|
-
|
|
140601
|
+
const base = this.workspaceConversationKey();
|
|
140602
|
+
if (!base) return null;
|
|
140603
|
+
return branchConversationIdentity(base, this.currentBranchNodeId());
|
|
140604
|
+
}
|
|
140605
|
+
/**
|
|
140606
|
+
* The hot folded-history cache belongs to one branch node. Legacy entries
|
|
140607
|
+
* (created before branch scoping) belong to the root node, which is where
|
|
140608
|
+
* they were produced.
|
|
140609
|
+
*/
|
|
140610
|
+
compressionCacheForBranch(entries) {
|
|
140611
|
+
const branch = String(this.currentBranchNodeId() || "");
|
|
140612
|
+
const root3 = String(this.conversationTree()?.rootNodeId || "");
|
|
140613
|
+
return (Array.isArray(entries) ? entries : []).flatMap((entry) => {
|
|
140614
|
+
const owner = String(entry.branchNodeId || root3 || "");
|
|
140615
|
+
if (branch && owner && owner !== branch) return [];
|
|
140616
|
+
if (branch && !owner) return [];
|
|
140617
|
+
return [{ ...entry, branchNodeId: owner || branch || void 0, messages: [...entry.messages] }];
|
|
140618
|
+
});
|
|
140619
|
+
}
|
|
140620
|
+
/**
|
|
140621
|
+
* Persist the union of every branch's folded history: the live in-memory
|
|
140622
|
+
* cache only holds the active branch, and the stored entry holds the rest.
|
|
140623
|
+
*/
|
|
140624
|
+
mergedCompressionCacheForPersist(storedEntry) {
|
|
140625
|
+
const branch = String(this.currentBranchNodeId() || "");
|
|
140626
|
+
const others = (storedEntry?.compressionCache || []).filter((entry) => {
|
|
140627
|
+
const owner = String(entry.branchNodeId || "");
|
|
140628
|
+
return !!owner && owner !== branch;
|
|
140629
|
+
});
|
|
140630
|
+
return [...others, ...this.compressionCache.map((entry) => ({ ...entry, messages: [...entry.messages] }))];
|
|
140562
140631
|
}
|
|
140563
140632
|
coldCompressionEntries() {
|
|
140564
140633
|
const scopeKey = this.compressionArchiveScopeKey();
|
|
@@ -140621,7 +140690,8 @@ ${text3.slice(-tailChars).trimStart()}`;
|
|
|
140621
140690
|
foldedEntries: messages.length,
|
|
140622
140691
|
foldedChars,
|
|
140623
140692
|
model,
|
|
140624
|
-
fallback
|
|
140693
|
+
fallback,
|
|
140694
|
+
branchNodeId: this.currentBranchNodeId() || void 0
|
|
140625
140695
|
});
|
|
140626
140696
|
this.nextCompressionCacheId += 1;
|
|
140627
140697
|
const maxEntries = Math.max(0, Math.floor(this.config.getNum("context", "compression_cache_max") || 8));
|
|
@@ -141702,6 +141772,81 @@ var GuardedContinuationStore = class {
|
|
|
141702
141772
|
return { ...build2, input: cloneInput(build2.input) };
|
|
141703
141773
|
});
|
|
141704
141774
|
}
|
|
141775
|
+
/**
|
|
141776
|
+
* dev-0.6.4 hotfix: 当某个被受理的 Build 失败/取消后,它的后继会永远无法被
|
|
141777
|
+
* claim(`claimBuild` 只认「父 = 已提交 frontier」),整条队列就此卡死,新提交
|
|
141778
|
+
* 也只是接在失败链后面。
|
|
141779
|
+
*
|
|
141780
|
+
* 这里提供一条**显式**修复通道:把被阻断的排队 Build 重新挂到当前已提交的
|
|
141781
|
+
* frontier 上,保持原有队列顺序,并为每一次改挂写入 `BuildQueueRepaired`
|
|
141782
|
+
* 审计事件(记录旧 parent / 新 parent / 原因)。它不是静默 rebase:调用方是
|
|
141783
|
+
* 用户的显式动作(恢复队列)或一次新的入队命令。
|
|
141784
|
+
*/
|
|
141785
|
+
repairBlockedQueue(input) {
|
|
141786
|
+
return this.mutate((ledger) => {
|
|
141787
|
+
const branch = this.requireBranch(ledger, input);
|
|
141788
|
+
const frontier = branch.headBuildId || null;
|
|
141789
|
+
const queued = Object.values(ledger.builds).filter((build2) => build2.branchId === branch.branchId && build2.status === "QUEUED").sort((a, b) => a.queueSequence - b.queueSequence);
|
|
141790
|
+
const repaired = [];
|
|
141791
|
+
let chainParent = frontier;
|
|
141792
|
+
let mutated = false;
|
|
141793
|
+
for (const build2 of queued) {
|
|
141794
|
+
if ((build2.parentBuildId || null) !== (chainParent || null)) {
|
|
141795
|
+
const previousParentBuildId = build2.parentBuildId;
|
|
141796
|
+
build2.parentBuildId = chainParent;
|
|
141797
|
+
build2.waitingReason = void 0;
|
|
141798
|
+
build2.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
141799
|
+
repaired.push(build2.buildId);
|
|
141800
|
+
mutated = true;
|
|
141801
|
+
this.appendEvent(ledger, {
|
|
141802
|
+
type: "BuildQueueRepaired",
|
|
141803
|
+
workspaceId: this.workspaceId,
|
|
141804
|
+
rootId: branch.rootId,
|
|
141805
|
+
branchId: branch.branchId,
|
|
141806
|
+
buildId: build2.buildId,
|
|
141807
|
+
attemptId: null,
|
|
141808
|
+
fence: null,
|
|
141809
|
+
payload: {
|
|
141810
|
+
previousParentBuildId,
|
|
141811
|
+
newParentBuildId: chainParent,
|
|
141812
|
+
reason: String(input.reason || "committed-frontier-repair")
|
|
141813
|
+
}
|
|
141814
|
+
});
|
|
141815
|
+
}
|
|
141816
|
+
chainParent = build2.buildId;
|
|
141817
|
+
}
|
|
141818
|
+
if (mutated) {
|
|
141819
|
+
branch.queueRevision += 1;
|
|
141820
|
+
branch.lifecycleRevision += 1;
|
|
141821
|
+
branch.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
141822
|
+
}
|
|
141823
|
+
return { repaired, frontier };
|
|
141824
|
+
});
|
|
141825
|
+
}
|
|
141826
|
+
/**
|
|
141827
|
+
* dev-0.6.4 hotfix: 读取视角的受阻推导。
|
|
141828
|
+
*
|
|
141829
|
+
* 一个 QUEUED Build 只有在它的父正好是「已提交 frontier」或队列里位于它之前
|
|
141830
|
+
* 的可执行 Build 时才可能被 claim(见 `claimBuild`)。这里按队列顺序推导哪些行
|
|
141831
|
+
* 目前不可执行:父不是链上前驱、或父已 FAILED/CANCELLED 的行即为受阻,且受阻点
|
|
141832
|
+
* 之后的所有行同样受阻。
|
|
141833
|
+
*/
|
|
141834
|
+
blockedQueuedBuildIds(ledger, branch) {
|
|
141835
|
+
const blocked = /* @__PURE__ */ new Set();
|
|
141836
|
+
const queued = Object.values(ledger.builds).filter((build2) => build2.branchId === branch.branchId && build2.status === "QUEUED").sort((a, b) => a.queueSequence - b.queueSequence);
|
|
141837
|
+
let chainParent = branch.headBuildId || null;
|
|
141838
|
+
for (const build2 of queued) {
|
|
141839
|
+
const parent2 = build2.parentBuildId ? ledger.builds[build2.parentBuildId] : null;
|
|
141840
|
+
const parentIsChainPredecessor = (build2.parentBuildId || null) === (chainParent || null);
|
|
141841
|
+
const parentFailed = !!parent2 && (parent2.status === "FAILED" || parent2.status === "CANCELLED");
|
|
141842
|
+
if (!parentIsChainPredecessor || parentFailed) {
|
|
141843
|
+
blocked.add(build2.buildId);
|
|
141844
|
+
continue;
|
|
141845
|
+
}
|
|
141846
|
+
chainParent = build2.buildId;
|
|
141847
|
+
}
|
|
141848
|
+
return blocked;
|
|
141849
|
+
}
|
|
141705
141850
|
deleteQueuedBuild(input) {
|
|
141706
141851
|
this.mutate((ledger) => {
|
|
141707
141852
|
const build2 = ledger.builds[input.buildId];
|
|
@@ -142103,7 +142248,12 @@ var GuardedContinuationStore = class {
|
|
|
142103
142248
|
}
|
|
142104
142249
|
snapshotUnlocked(ledger, branch) {
|
|
142105
142250
|
const guard = ledger.guards[branch.branchId] || defaultGuard(branch.branchId);
|
|
142106
|
-
const
|
|
142251
|
+
const blockedBuildIds = this.blockedQueuedBuildIds(ledger, branch);
|
|
142252
|
+
const builds = Object.values(ledger.builds).filter((build2) => build2.branchId === branch.branchId).sort((a, b) => a.queueSequence - b.queueSequence).map((build2) => ({
|
|
142253
|
+
...build2,
|
|
142254
|
+
input: cloneInput(build2.input),
|
|
142255
|
+
waitingReason: build2.waitingReason || (blockedBuildIds.has(build2.buildId) ? "DEPENDENCY_FAILED" : void 0)
|
|
142256
|
+
}));
|
|
142107
142257
|
return {
|
|
142108
142258
|
workspaceId: this.workspaceId,
|
|
142109
142259
|
rootId: branch.rootId,
|
|
@@ -142706,11 +142856,25 @@ var ConversationKernel = class {
|
|
|
142706
142856
|
queueItems(target) {
|
|
142707
142857
|
const runtime = this.findRuntime(target);
|
|
142708
142858
|
if (!runtime) return [];
|
|
142859
|
+
let waitingByBuildId = /* @__PURE__ */ new Map();
|
|
142860
|
+
const continuation = this.continuationStore(runtime);
|
|
142861
|
+
if (continuation) {
|
|
142862
|
+
try {
|
|
142863
|
+
const snapshot2 = continuation.store.snapshot({
|
|
142864
|
+
workspaceId: continuation.workspaceId,
|
|
142865
|
+
rootId: continuation.rootId,
|
|
142866
|
+
branchId: continuation.branchId
|
|
142867
|
+
});
|
|
142868
|
+
waitingByBuildId = new Map(snapshot2.builds.filter((build2) => !!build2.waitingReason).map((build2) => [build2.buildId, String(build2.waitingReason)]));
|
|
142869
|
+
} catch {
|
|
142870
|
+
}
|
|
142871
|
+
}
|
|
142709
142872
|
return runtime.pendingNextTurn.flatMap((item) => {
|
|
142710
142873
|
if (item.queueMode !== "followUp") return [];
|
|
142711
142874
|
if (typeof item.message === "string") return [];
|
|
142712
142875
|
const id = String(item.message.clientMessageId || "");
|
|
142713
142876
|
if (!id) return [];
|
|
142877
|
+
const buildId = String(item.message.buildId || "");
|
|
142714
142878
|
return [{
|
|
142715
142879
|
id,
|
|
142716
142880
|
buildId: item.message.buildId,
|
|
@@ -142723,7 +142887,9 @@ var ConversationKernel = class {
|
|
|
142723
142887
|
images: item.message.images?.map((image) => ({ ...image })),
|
|
142724
142888
|
branchNodeId: item.message.branchNodeId,
|
|
142725
142889
|
branchPath: item.message.branchPath?.slice(),
|
|
142726
|
-
modelSelection: item.message.modelSelection
|
|
142890
|
+
modelSelection: item.message.modelSelection,
|
|
142891
|
+
waitingReason: waitingByBuildId.get(buildId),
|
|
142892
|
+
blocked: waitingByBuildId.has(buildId)
|
|
142727
142893
|
}];
|
|
142728
142894
|
});
|
|
142729
142895
|
}
|
|
@@ -142997,6 +143163,35 @@ ${text3}`;
|
|
|
142997
143163
|
}
|
|
142998
143164
|
return runtime.queuePaused;
|
|
142999
143165
|
}
|
|
143166
|
+
/**
|
|
143167
|
+
* dev-0.6.4 hotfix: 显式修复被失败前置阻断的排队行。
|
|
143168
|
+
*
|
|
143169
|
+
* 0.6.3 的合约是「失败/取消阻断后继,且不静默跳过」,所以这里不挂在恢复动作上
|
|
143170
|
+
* 自动执行;它必须由用户的显式动作触发(界面上的“修复受阻队列”按钮)。每一次
|
|
143171
|
+
* 改挂都写入 `BuildQueueRepaired` 事件并发布可见状态,触发者与原因可审计。
|
|
143172
|
+
*/
|
|
143173
|
+
repairBlockedQueueNow(runtime) {
|
|
143174
|
+
const continuation = this.continuationStore(runtime);
|
|
143175
|
+
if (!continuation) return 0;
|
|
143176
|
+
try {
|
|
143177
|
+
const result = continuation.store.repairBlockedQueue({
|
|
143178
|
+
workspaceId: continuation.workspaceId,
|
|
143179
|
+
rootId: continuation.rootId,
|
|
143180
|
+
branchId: continuation.branchId,
|
|
143181
|
+
reason: "explicit-user-repair"
|
|
143182
|
+
});
|
|
143183
|
+
if (result.repaired.length) {
|
|
143184
|
+
runtime.runner.recordWorkStatus(
|
|
143185
|
+
`${result.repaired.length} queued row(s) blocked by a failed predecessor were re-anchored to the last committed build; the queue resumes in order.`
|
|
143186
|
+
);
|
|
143187
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
143188
|
+
}
|
|
143189
|
+
return result.repaired.length;
|
|
143190
|
+
} catch (error2) {
|
|
143191
|
+
runtime.runner.recordWorkStatus(`Queue repair failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
143192
|
+
return 0;
|
|
143193
|
+
}
|
|
143194
|
+
}
|
|
143000
143195
|
queueAction(target, action, input = {}) {
|
|
143001
143196
|
const normalized = this.normalizeTarget(target);
|
|
143002
143197
|
const runtime = this.findRuntime(normalized) || this.runtime(normalized, {
|
|
@@ -143031,6 +143226,8 @@ ${text3}`;
|
|
|
143031
143226
|
this.setQueuePaused(runtime.target, !runtime.queuePaused);
|
|
143032
143227
|
} else if (action === "set_pause") {
|
|
143033
143228
|
this.setQueuePaused(runtime.target, input.paused === true);
|
|
143229
|
+
} else if (action === "repair_blocked") {
|
|
143230
|
+
this.repairBlockedQueueNow(runtime);
|
|
143034
143231
|
} else if (action === "guide") {
|
|
143035
143232
|
if (input.text !== void 0) this.updateQueueItem(runtime.target, String(input.id || ""), input.text, input);
|
|
143036
143233
|
const item = this.queueItems(runtime.target).find((entry) => entry.id === String(input.id || ""));
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -85,6 +85,8 @@ export interface CompressionCacheEntry {
|
|
|
85
85
|
foldedChars: number;
|
|
86
86
|
model: string;
|
|
87
87
|
fallback: boolean;
|
|
88
|
+
/** Branch node that owns this folded history. Sibling pages must never read it. */
|
|
89
|
+
branchNodeId?: string;
|
|
88
90
|
}
|
|
89
91
|
type ConversationModelSelection = {
|
|
90
92
|
kind: 'auto';
|
|
@@ -528,6 +530,16 @@ export declare class Agent {
|
|
|
528
530
|
activeModelName(): string;
|
|
529
531
|
modelSelectionValue(): string;
|
|
530
532
|
currentWorkRunId(): string;
|
|
533
|
+
/**
|
|
534
|
+
* The branch node that owns the currently active Build. Provider session ids
|
|
535
|
+
* and branch-scoped caches must use this identity instead of "the branch that
|
|
536
|
+
* happens to be active", otherwise a concurrently running sibling branch
|
|
537
|
+
* (branch-communication mode) or a page switched mid-Build would read another
|
|
538
|
+
* branch's remote session and folded history.
|
|
539
|
+
*/
|
|
540
|
+
activeWorkRunBranchId(): string;
|
|
541
|
+
/** Full conversation identity of one branch node (shared by every branch kind). */
|
|
542
|
+
branchConversationIdentity(branchNodeId: string): string;
|
|
531
543
|
activeModelConfig(): ReturnType<ConfigManager['allModels']>[number] | undefined;
|
|
532
544
|
requestedModelSelection(): ModelSelection;
|
|
533
545
|
resetAutoRoute(): void;
|
|
@@ -1229,6 +1241,17 @@ export declare class Agent {
|
|
|
1229
1241
|
private formatCompressionSummary;
|
|
1230
1242
|
private persistCompressedHistory;
|
|
1231
1243
|
private compressionArchiveScopeKey;
|
|
1244
|
+
/**
|
|
1245
|
+
* The hot folded-history cache belongs to one branch node. Legacy entries
|
|
1246
|
+
* (created before branch scoping) belong to the root node, which is where
|
|
1247
|
+
* they were produced.
|
|
1248
|
+
*/
|
|
1249
|
+
private compressionCacheForBranch;
|
|
1250
|
+
/**
|
|
1251
|
+
* Persist the union of every branch's folded history: the live in-memory
|
|
1252
|
+
* cache only holds the active branch, and the stored entry holds the rest.
|
|
1253
|
+
*/
|
|
1254
|
+
private mergedCompressionCacheForPersist;
|
|
1232
1255
|
private coldCompressionEntries;
|
|
1233
1256
|
private compressionArchiveEntryCount;
|
|
1234
1257
|
private archiveColdCompressionEntries;
|
package/dist/core/agent.js
CHANGED
|
@@ -58,6 +58,7 @@ const skills_1 = require("./skills");
|
|
|
58
58
|
const flow_1 = require("./flow");
|
|
59
59
|
const memoryLab_1 = require("./memoryLab");
|
|
60
60
|
const agentKernelRunner_1 = require("./agentKernelRunner");
|
|
61
|
+
const branchIdentity_1 = require("./branchIdentity");
|
|
61
62
|
const toolPolicy_1 = require("./toolPolicy");
|
|
62
63
|
const conversationTarget_1 = require("./conversationTarget");
|
|
63
64
|
const utilityHostToolBridge_1 = require("./utilityHostToolBridge");
|
|
@@ -1020,6 +1021,22 @@ class Agent {
|
|
|
1020
1021
|
currentWorkRunId() {
|
|
1021
1022
|
return this.activeWorkRunId || this.finalizingWorkRunId || '';
|
|
1022
1023
|
}
|
|
1024
|
+
/**
|
|
1025
|
+
* The branch node that owns the currently active Build. Provider session ids
|
|
1026
|
+
* and branch-scoped caches must use this identity instead of "the branch that
|
|
1027
|
+
* happens to be active", otherwise a concurrently running sibling branch
|
|
1028
|
+
* (branch-communication mode) or a page switched mid-Build would read another
|
|
1029
|
+
* branch's remote session and folded history.
|
|
1030
|
+
*/
|
|
1031
|
+
activeWorkRunBranchId() {
|
|
1032
|
+
const runId = this.currentWorkRunId();
|
|
1033
|
+
const run = runId ? this.workRuns.find(item => String(item.runId || '') === runId) : undefined;
|
|
1034
|
+
return String(run?.branchNodeId || '') || this.currentBranchNodeId();
|
|
1035
|
+
}
|
|
1036
|
+
/** Full conversation identity of one branch node (shared by every branch kind). */
|
|
1037
|
+
branchConversationIdentity(branchNodeId) {
|
|
1038
|
+
return (0, branchIdentity_1.branchConversationIdentity)(this.activeConversationId || 'default', branchNodeId);
|
|
1039
|
+
}
|
|
1023
1040
|
activeModelConfig() {
|
|
1024
1041
|
const deployment = this.activeDeployment();
|
|
1025
1042
|
return deployment ? this.config.findDeployment(deployment) : this.config.findModel(this.model);
|
|
@@ -3615,6 +3632,10 @@ class Agent {
|
|
|
3615
3632
|
&& ((targetGuideNodeId && group.pageGuideIds?.[parentNodeId] === targetGuideNodeId)
|
|
3616
3633
|
|| (!targetGuideNodeId && targetMessageId && group.pageMessageIds?.[parentNodeId] === targetMessageId))));
|
|
3617
3634
|
const branchId = crypto.randomUUID();
|
|
3635
|
+
// dev-0.6.4: 新分支(用户分页编辑与实验性分支交流共用这一事务)必须拿到
|
|
3636
|
+
// 全新的全对话 id。复用节点 id 会让两个分支共享 provider 会话序列、
|
|
3637
|
+
// 压缩历史与本地缓存,正是「跨分支缓存」必须禁止的情形。
|
|
3638
|
+
(0, branchIdentity_1.assertFreshBranchIdentity)(clean || this.activeConversationId || 'default', branchId, Object.keys(tree.nodes));
|
|
3618
3639
|
const branch = this.treeNodeFromEntry(branchId, parentNodeId, index, text, {
|
|
3619
3640
|
...entry,
|
|
3620
3641
|
chatMessages: sourceNode.chatMessages.slice(0, index),
|
|
@@ -4317,7 +4338,7 @@ class Agent {
|
|
|
4317
4338
|
chatMessages: [...this.chatMessages],
|
|
4318
4339
|
history: [...this.history],
|
|
4319
4340
|
providerUsage: this.conversationProviderUsage(),
|
|
4320
|
-
compressionCache:
|
|
4341
|
+
compressionCache: this.mergedCompressionCacheForPersist(stored.conversations[stateKey]),
|
|
4321
4342
|
branchMailbox: [...this.branchMailbox],
|
|
4322
4343
|
branchCommunication: this.branchCommunicationEnabled,
|
|
4323
4344
|
plan: this.normalizeConversationPlan(this.conversationPlan),
|
|
@@ -4377,7 +4398,7 @@ class Agent {
|
|
|
4377
4398
|
if (saved) {
|
|
4378
4399
|
this.restoreProviderUsage(saved.providerUsage);
|
|
4379
4400
|
this.history = [...saved.history];
|
|
4380
|
-
this.compressionCache =
|
|
4401
|
+
this.compressionCache = this.compressionCacheForBranch(saved.compressionCache);
|
|
4381
4402
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map(entry => Number(entry.id.replace(/^ctx-cache-/, '')) || 0)) + 1;
|
|
4382
4403
|
this.branchMailbox = (saved.branchMailbox || []).map(message => ({ ...message }));
|
|
4383
4404
|
this.nextBranchMessageSequence = Math.max(1, ...this.branchMailbox.map(message => Number(message.sequence) || 0)) + 1;
|
|
@@ -4406,7 +4427,7 @@ class Agent {
|
|
|
4406
4427
|
this.restoreProviderUsage(persisted?.providerUsage);
|
|
4407
4428
|
const repairedHistory = this.repairDanglingToolCalls(persisted?.history ? [...persisted.history] : []);
|
|
4408
4429
|
this.history = repairedHistory.messages;
|
|
4409
|
-
this.compressionCache = persisted?.compressionCache
|
|
4430
|
+
this.compressionCache = this.compressionCacheForBranch(persisted?.compressionCache);
|
|
4410
4431
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map(entry => Number(entry.id.replace(/^ctx-cache-/, '')) || 0)) + 1;
|
|
4411
4432
|
this.branchMailbox = (persisted?.branchMailbox || []).map(message => ({ ...message }));
|
|
4412
4433
|
this.nextBranchMessageSequence = Math.max(1, ...this.branchMailbox.map(message => Number(message.sequence) || 0)) + 1;
|
|
@@ -9795,7 +9816,41 @@ class Agent {
|
|
|
9795
9816
|
compressionArchiveScopeKey() {
|
|
9796
9817
|
if (this.isSubagentRuntime || !this.config.getBool('context', 'compression_archive_enabled'))
|
|
9797
9818
|
return null;
|
|
9798
|
-
|
|
9819
|
+
const base = this.workspaceConversationKey();
|
|
9820
|
+
if (!base)
|
|
9821
|
+
return null;
|
|
9822
|
+
// dev-0.6.4: 折叠历史必须按分支节点隔离——用户分页产生的分支与实验性分支
|
|
9823
|
+
// 交流产生的分支共用这一条身份规则,兄弟分支不得互相读取压缩归档。
|
|
9824
|
+
return (0, branchIdentity_1.branchConversationIdentity)(base, this.currentBranchNodeId());
|
|
9825
|
+
}
|
|
9826
|
+
/**
|
|
9827
|
+
* The hot folded-history cache belongs to one branch node. Legacy entries
|
|
9828
|
+
* (created before branch scoping) belong to the root node, which is where
|
|
9829
|
+
* they were produced.
|
|
9830
|
+
*/
|
|
9831
|
+
compressionCacheForBranch(entries) {
|
|
9832
|
+
const branch = String(this.currentBranchNodeId() || '');
|
|
9833
|
+
const root = String(this.conversationTree()?.rootNodeId || '');
|
|
9834
|
+
return (Array.isArray(entries) ? entries : []).flatMap(entry => {
|
|
9835
|
+
const owner = String(entry.branchNodeId || root || '');
|
|
9836
|
+
if (branch && owner && owner !== branch)
|
|
9837
|
+
return [];
|
|
9838
|
+
if (branch && !owner)
|
|
9839
|
+
return [];
|
|
9840
|
+
return [{ ...entry, branchNodeId: owner || branch || undefined, messages: [...entry.messages] }];
|
|
9841
|
+
});
|
|
9842
|
+
}
|
|
9843
|
+
/**
|
|
9844
|
+
* Persist the union of every branch's folded history: the live in-memory
|
|
9845
|
+
* cache only holds the active branch, and the stored entry holds the rest.
|
|
9846
|
+
*/
|
|
9847
|
+
mergedCompressionCacheForPersist(storedEntry) {
|
|
9848
|
+
const branch = String(this.currentBranchNodeId() || '');
|
|
9849
|
+
const others = (storedEntry?.compressionCache || []).filter(entry => {
|
|
9850
|
+
const owner = String(entry.branchNodeId || '');
|
|
9851
|
+
return !!owner && owner !== branch;
|
|
9852
|
+
});
|
|
9853
|
+
return [...others, ...this.compressionCache.map(entry => ({ ...entry, messages: [...entry.messages] }))];
|
|
9799
9854
|
}
|
|
9800
9855
|
coldCompressionEntries() {
|
|
9801
9856
|
const scopeKey = this.compressionArchiveScopeKey();
|
|
@@ -9872,6 +9927,7 @@ class Agent {
|
|
|
9872
9927
|
foldedChars,
|
|
9873
9928
|
model,
|
|
9874
9929
|
fallback,
|
|
9930
|
+
branchNodeId: this.currentBranchNodeId() || undefined,
|
|
9875
9931
|
});
|
|
9876
9932
|
this.nextCompressionCacheId += 1;
|
|
9877
9933
|
const maxEntries = Math.max(0, Math.floor(this.config.getNum('context', 'compression_cache_max') || 8));
|
|
@@ -39,6 +39,7 @@ exports.resetPublicAssistantDeltaFilter = resetPublicAssistantDeltaFilter;
|
|
|
39
39
|
exports.runAgentKernel = runAgentKernel;
|
|
40
40
|
exports.routeToolSurfaceV2 = routeToolSurfaceV2;
|
|
41
41
|
exports.toolResultObjectiveOutcome = toolResultObjectiveOutcome;
|
|
42
|
+
const branchIdentity_1 = require("./branchIdentity");
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
43
44
|
const path = __importStar(require("path"));
|
|
44
45
|
const terminalTakeover_1 = require("../tools/terminalTakeover");
|
|
@@ -75,13 +76,25 @@ const HIDDEN_LINE_PREFIXES = [
|
|
|
75
76
|
* a fork that reuses the root conversation id can read the other branch's
|
|
76
77
|
* history. Keep the id stable within one branch (normal sequential turns still
|
|
77
78
|
* cache-hit) and change it the moment the runtime branch changes.
|
|
79
|
+
*
|
|
80
|
+
* dev-0.6.4: 分支身份固定在 Build 启动时创建的那条分支节点上(用户分页分支与
|
|
81
|
+
* 实验性分支交流分支共用同一规则)。这里绝不读取「当前正在查看/激活的分支」,
|
|
82
|
+
* 否则正在并行运行的兄弟分支或中途切换的页面会把请求绑到别的分支的远程会话。
|
|
78
83
|
*/
|
|
79
84
|
function providerSessionIdentity(agent) {
|
|
80
85
|
const conversationId = String(agent.activeConversationId || 'default');
|
|
86
|
+
let runBranchId = '';
|
|
87
|
+
try {
|
|
88
|
+
runBranchId = String(agent.activeWorkRunBranchId() || '');
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
runBranchId = '';
|
|
92
|
+
}
|
|
93
|
+
if (runBranchId)
|
|
94
|
+
return (0, branchIdentity_1.branchConversationIdentity)(conversationId, runBranchId);
|
|
81
95
|
try {
|
|
82
96
|
const snapshot = agent.getConversationSnapshot(conversationId);
|
|
83
|
-
|
|
84
|
-
return branchId ? `${conversationId}::branch:${branchId}` : conversationId;
|
|
97
|
+
return (0, branchIdentity_1.branchConversationIdentity)(conversationId, String(snapshot.runtimeBranchId || snapshot.activeBranchId || ''));
|
|
85
98
|
}
|
|
86
99
|
catch {
|
|
87
100
|
return conversationId;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch conversation identity (dev-0.6.4).
|
|
3
|
+
*
|
|
4
|
+
* A conversation owns a tree of branch nodes. Every node is its own full
|
|
5
|
+
* conversation: the provider-side session sequence id, the folded-history
|
|
6
|
+
* archive and every local transcript cache are scoped by the node id, so
|
|
7
|
+
* sibling pages can never read each other's remote session or cached history.
|
|
8
|
+
*
|
|
9
|
+
* The user's pagination edits and the experimental branch-communication
|
|
10
|
+
* branches are created by the same `Agent.branchConversation` transaction, so
|
|
11
|
+
* both kinds of branch must share this single identity function. Never derive
|
|
12
|
+
* the identity from "the branch that happens to be active" at request time.
|
|
13
|
+
*/
|
|
14
|
+
export declare const BRANCH_IDENTITY_SEPARATOR = "::branch:";
|
|
15
|
+
/** The full conversation id of one branch node: `<conversationId>::branch:<branchNodeId>`. */
|
|
16
|
+
export declare function branchConversationIdentity(conversationId: string, branchNodeId: string): string;
|
|
17
|
+
/** True when a value already carries a branch node identity. */
|
|
18
|
+
export declare function isBranchConversationIdentity(value: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Workspace-wide cache scope for one branch. Used by caches that must never be
|
|
21
|
+
* shared across sibling branches (compression history, provider payloads).
|
|
22
|
+
*/
|
|
23
|
+
export declare function branchCacheScopeKey(workspaceId: string, conversationId: string, branchNodeId: string): string;
|
|
24
|
+
/**
|
|
25
|
+
* A branch may never reuse the identity of another node. Callers that mint a
|
|
26
|
+
* new page assert this before the node becomes reachable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertFreshBranchIdentity(conversationId: string, branchNodeId: string, existing: Iterable<string>): void;
|
|
29
|
+
//# sourceMappingURL=branchIdentity.d.ts.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Branch conversation identity (dev-0.6.4).
|
|
4
|
+
*
|
|
5
|
+
* A conversation owns a tree of branch nodes. Every node is its own full
|
|
6
|
+
* conversation: the provider-side session sequence id, the folded-history
|
|
7
|
+
* archive and every local transcript cache are scoped by the node id, so
|
|
8
|
+
* sibling pages can never read each other's remote session or cached history.
|
|
9
|
+
*
|
|
10
|
+
* The user's pagination edits and the experimental branch-communication
|
|
11
|
+
* branches are created by the same `Agent.branchConversation` transaction, so
|
|
12
|
+
* both kinds of branch must share this single identity function. Never derive
|
|
13
|
+
* the identity from "the branch that happens to be active" at request time.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.BRANCH_IDENTITY_SEPARATOR = void 0;
|
|
17
|
+
exports.branchConversationIdentity = branchConversationIdentity;
|
|
18
|
+
exports.isBranchConversationIdentity = isBranchConversationIdentity;
|
|
19
|
+
exports.branchCacheScopeKey = branchCacheScopeKey;
|
|
20
|
+
exports.assertFreshBranchIdentity = assertFreshBranchIdentity;
|
|
21
|
+
exports.BRANCH_IDENTITY_SEPARATOR = '::branch:';
|
|
22
|
+
/** The full conversation id of one branch node: `<conversationId>::branch:<branchNodeId>`. */
|
|
23
|
+
function branchConversationIdentity(conversationId, branchNodeId) {
|
|
24
|
+
const conversation = String(conversationId || '').trim() || 'default';
|
|
25
|
+
const branch = String(branchNodeId || '').trim();
|
|
26
|
+
return branch ? `${conversation}${exports.BRANCH_IDENTITY_SEPARATOR}${branch}` : conversation;
|
|
27
|
+
}
|
|
28
|
+
/** True when a value already carries a branch node identity. */
|
|
29
|
+
function isBranchConversationIdentity(value) {
|
|
30
|
+
return String(value || '').includes(exports.BRANCH_IDENTITY_SEPARATOR);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Workspace-wide cache scope for one branch. Used by caches that must never be
|
|
34
|
+
* shared across sibling branches (compression history, provider payloads).
|
|
35
|
+
*/
|
|
36
|
+
function branchCacheScopeKey(workspaceId, conversationId, branchNodeId) {
|
|
37
|
+
const workspace = String(workspaceId || '').trim() || 'none';
|
|
38
|
+
return `${workspace}::${branchConversationIdentity(conversationId, branchNodeId)}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A branch may never reuse the identity of another node. Callers that mint a
|
|
42
|
+
* new page assert this before the node becomes reachable.
|
|
43
|
+
*/
|
|
44
|
+
function assertFreshBranchIdentity(conversationId, branchNodeId, existing) {
|
|
45
|
+
const branch = String(branchNodeId || '').trim();
|
|
46
|
+
if (!branch)
|
|
47
|
+
throw new Error('Branch conversation identity requires a branch node id.');
|
|
48
|
+
for (const known of existing) {
|
|
49
|
+
if (String(known || '') === branch) {
|
|
50
|
+
throw new Error(`Branch conversation identity ${branchConversationIdentity(conversationId, branch)} is already in use.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=branchIdentity.js.map
|
|
@@ -146,7 +146,7 @@ export interface FinalRecord {
|
|
|
146
146
|
export interface ContinuationEvent {
|
|
147
147
|
eventId: string;
|
|
148
148
|
cursor: number;
|
|
149
|
-
type: 'BuildAccepted' | 'AttemptStarted' | 'AttemptHeartbeat' | 'BuildCommitted' | 'BuildFailed' | 'BuildCancelled' | 'BranchForked' | 'BranchPaused' | 'BranchResumed' | 'StaleWriteRejected';
|
|
149
|
+
type: 'BuildAccepted' | 'AttemptStarted' | 'AttemptHeartbeat' | 'BuildCommitted' | 'BuildFailed' | 'BuildCancelled' | 'BranchForked' | 'BranchPaused' | 'BranchResumed' | 'BuildQueueRepaired' | 'StaleWriteRejected';
|
|
150
150
|
workspaceId: WorkspaceId;
|
|
151
151
|
rootId: RootId;
|
|
152
152
|
branchId: BranchId;
|
|
@@ -47,6 +47,34 @@ export declare class GuardedContinuationStore {
|
|
|
47
47
|
buildId: BuildId;
|
|
48
48
|
input: BuildInput;
|
|
49
49
|
}): BuildRecord;
|
|
50
|
+
/**
|
|
51
|
+
* dev-0.6.4 hotfix: 当某个被受理的 Build 失败/取消后,它的后继会永远无法被
|
|
52
|
+
* claim(`claimBuild` 只认「父 = 已提交 frontier」),整条队列就此卡死,新提交
|
|
53
|
+
* 也只是接在失败链后面。
|
|
54
|
+
*
|
|
55
|
+
* 这里提供一条**显式**修复通道:把被阻断的排队 Build 重新挂到当前已提交的
|
|
56
|
+
* frontier 上,保持原有队列顺序,并为每一次改挂写入 `BuildQueueRepaired`
|
|
57
|
+
* 审计事件(记录旧 parent / 新 parent / 原因)。它不是静默 rebase:调用方是
|
|
58
|
+
* 用户的显式动作(恢复队列)或一次新的入队命令。
|
|
59
|
+
*/
|
|
60
|
+
repairBlockedQueue(input: {
|
|
61
|
+
workspaceId: string;
|
|
62
|
+
rootId: RootId;
|
|
63
|
+
branchId: BranchId;
|
|
64
|
+
reason?: string;
|
|
65
|
+
}): {
|
|
66
|
+
repaired: BuildId[];
|
|
67
|
+
frontier: BuildId | null;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* dev-0.6.4 hotfix: 读取视角的受阻推导。
|
|
71
|
+
*
|
|
72
|
+
* 一个 QUEUED Build 只有在它的父正好是「已提交 frontier」或队列里位于它之前
|
|
73
|
+
* 的可执行 Build 时才可能被 claim(见 `claimBuild`)。这里按队列顺序推导哪些行
|
|
74
|
+
* 目前不可执行:父不是链上前驱、或父已 FAILED/CANCELLED 的行即为受阻,且受阻点
|
|
75
|
+
* 之后的所有行同样受阻。
|
|
76
|
+
*/
|
|
77
|
+
private blockedQueuedBuildIds;
|
|
50
78
|
deleteQueuedBuild(input: {
|
|
51
79
|
workspaceId: string;
|
|
52
80
|
rootId: RootId;
|