newmark-agent 0.6.2 → 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 +2149 -475
- package/dist/core/agent.d.ts +37 -0
- package/dist/core/agent.js +104 -7
- package/dist/core/agentKernelRunner.js +37 -2
- package/dist/core/branchIdentity.d.ts +29 -0
- package/dist/core/branchIdentity.js +54 -0
- package/dist/core/continuation/contracts.d.ts +197 -0
- package/dist/core/continuation/contracts.js +72 -0
- package/dist/core/continuation/store.d.ts +206 -0
- package/dist/core/continuation/store.js +1077 -0
- package/dist/core/conversationKernel.d.ts +93 -2
- package/dist/core/conversationKernel.js +648 -36
- package/dist/core/requestContextEstimate.d.ts +5 -1
- package/dist/llm/provider.js +9 -2
- package/dist/main.js +40 -5
- package/dist/providers/chat-completions.adapter.js +6 -1
- package/dist/providers/responses.adapter.js +4 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/dist/ui/index.html +413 -55
- package/dist/wsl-agent-host.bundle.cjs +2152 -478
- package/package.json +5 -2
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';
|
|
@@ -294,8 +296,15 @@ export interface ConversationBranchGroupSnapshot {
|
|
|
294
296
|
export interface ConversationContinuation {
|
|
295
297
|
content: string;
|
|
296
298
|
queueMode: 'steer' | 'followUp';
|
|
299
|
+
buildId?: string;
|
|
297
300
|
clientMessageId?: string;
|
|
298
301
|
runId?: string;
|
|
302
|
+
branchNodeId?: string;
|
|
303
|
+
branchPath?: string[];
|
|
304
|
+
ledgerBranchId?: string;
|
|
305
|
+
targetRuntimeKey?: string;
|
|
306
|
+
workspaceKey?: string;
|
|
307
|
+
modelSelection?: string;
|
|
299
308
|
images?: Array<{
|
|
300
309
|
dataUrl: string;
|
|
301
310
|
name?: string;
|
|
@@ -521,6 +530,16 @@ export declare class Agent {
|
|
|
521
530
|
activeModelName(): string;
|
|
522
531
|
modelSelectionValue(): string;
|
|
523
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;
|
|
524
543
|
activeModelConfig(): ReturnType<ConfigManager['allModels']>[number] | undefined;
|
|
525
544
|
requestedModelSelection(): ModelSelection;
|
|
526
545
|
resetAutoRoute(): void;
|
|
@@ -787,6 +806,13 @@ export declare class Agent {
|
|
|
787
806
|
*/
|
|
788
807
|
private deriveConversationTitleFromProvider;
|
|
789
808
|
private conversationTitleFailureCause;
|
|
809
|
+
/**
|
|
810
|
+
* Deterministic provider rejections (bad key, forbidden endpoint, unknown
|
|
811
|
+
* model, invalid request, payment required) cannot succeed by retrying the
|
|
812
|
+
* identical title probe. Only transient transport, timeout, rate-limit and
|
|
813
|
+
* provider-internal failures enter the bounded 5-stage backoff.
|
|
814
|
+
*/
|
|
815
|
+
private conversationTitleFailureIsRetryable;
|
|
790
816
|
private waitForConversationTitleRetry;
|
|
791
817
|
/** Legacy completion hook retained as a no-op; naming is pre-response only. */
|
|
792
818
|
private maybeAutoRenameConversationFromRun;
|
|
@@ -1215,6 +1241,17 @@ export declare class Agent {
|
|
|
1215
1241
|
private formatCompressionSummary;
|
|
1216
1242
|
private persistCompressedHistory;
|
|
1217
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;
|
|
1218
1255
|
private coldCompressionEntries;
|
|
1219
1256
|
private compressionArchiveEntryCount;
|
|
1220
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);
|
|
@@ -2735,8 +2752,15 @@ class Agent {
|
|
|
2735
2752
|
deduped.set(key, {
|
|
2736
2753
|
content,
|
|
2737
2754
|
queueMode,
|
|
2755
|
+
buildId: String(raw.buildId || '').trim() || undefined,
|
|
2738
2756
|
clientMessageId,
|
|
2739
2757
|
runId: String(raw.runId || '').trim() || undefined,
|
|
2758
|
+
branchNodeId: String(raw.branchNodeId || '').trim() || undefined,
|
|
2759
|
+
branchPath: Array.isArray(raw.branchPath) ? raw.branchPath.map(id => String(id || '')).filter(Boolean) : undefined,
|
|
2760
|
+
ledgerBranchId: String(raw.ledgerBranchId || '').trim() || undefined,
|
|
2761
|
+
targetRuntimeKey: String(raw.targetRuntimeKey || '').trim() || undefined,
|
|
2762
|
+
workspaceKey: String(raw.workspaceKey || '').trim() || undefined,
|
|
2763
|
+
modelSelection: String(raw.modelSelection || '').trim() || undefined,
|
|
2740
2764
|
images: images.length ? images : undefined,
|
|
2741
2765
|
attachments: attachments.length ? attachments : undefined,
|
|
2742
2766
|
hiddenUserInput: raw.hiddenUserInput === true,
|
|
@@ -3608,6 +3632,10 @@ class Agent {
|
|
|
3608
3632
|
&& ((targetGuideNodeId && group.pageGuideIds?.[parentNodeId] === targetGuideNodeId)
|
|
3609
3633
|
|| (!targetGuideNodeId && targetMessageId && group.pageMessageIds?.[parentNodeId] === targetMessageId))));
|
|
3610
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));
|
|
3611
3639
|
const branch = this.treeNodeFromEntry(branchId, parentNodeId, index, text, {
|
|
3612
3640
|
...entry,
|
|
3613
3641
|
chatMessages: sourceNode.chatMessages.slice(0, index),
|
|
@@ -3950,6 +3978,19 @@ class Agent {
|
|
|
3950
3978
|
return 'provider returned invalid JSON';
|
|
3951
3979
|
return 'provider request failed';
|
|
3952
3980
|
}
|
|
3981
|
+
/**
|
|
3982
|
+
* Deterministic provider rejections (bad key, forbidden endpoint, unknown
|
|
3983
|
+
* model, invalid request, payment required) cannot succeed by retrying the
|
|
3984
|
+
* identical title probe. Only transient transport, timeout, rate-limit and
|
|
3985
|
+
* provider-internal failures enter the bounded 5-stage backoff.
|
|
3986
|
+
*/
|
|
3987
|
+
conversationTitleFailureIsRetryable(error) {
|
|
3988
|
+
const message = error instanceof Error ? error.message : '';
|
|
3989
|
+
const status = message.match(/^\[LLM Error:\s*([1-5]\d{2})\]/)?.[1];
|
|
3990
|
+
if (!status)
|
|
3991
|
+
return true;
|
|
3992
|
+
return status === '408' || status === '429' || status.startsWith('5');
|
|
3993
|
+
}
|
|
3953
3994
|
async waitForConversationTitleRetry(delayMs, signal) {
|
|
3954
3995
|
if (signal?.aborted || delayMs <= 0)
|
|
3955
3996
|
return;
|
|
@@ -3981,17 +4022,20 @@ class Agent {
|
|
|
3981
4022
|
// Failure state belongs to this title attempt sequence, not the Agent or
|
|
3982
4023
|
// another conversation. A later empty title replaces an earlier HTTP error.
|
|
3983
4024
|
let lastFailure = 'provider returned an empty or unusable title';
|
|
4025
|
+
let terminalFailure = false;
|
|
3984
4026
|
const requestTitle = async () => {
|
|
3985
4027
|
lastFailure = 'provider returned an empty or unusable title';
|
|
4028
|
+
terminalFailure = false;
|
|
3986
4029
|
try {
|
|
3987
4030
|
return await this.deriveConversationTitleFromProvider(firstUserInput, provider, modelName, intelligence, signal);
|
|
3988
4031
|
}
|
|
3989
4032
|
catch (error) {
|
|
3990
4033
|
lastFailure = this.conversationTitleFailureCause(error, provider);
|
|
4034
|
+
terminalFailure = !this.conversationTitleFailureIsRetryable(error);
|
|
3991
4035
|
return '';
|
|
3992
4036
|
}
|
|
3993
4037
|
};
|
|
3994
|
-
const exhaustedFailure = () => new Error(`Conversation title generation failed; ${lastFailure}. The first Agent request was not started. Retry the first input.`);
|
|
4038
|
+
const exhaustedFailure = () => new Error(`Conversation title generation failed for ${provider.name}/${modelName}; ${lastFailure}. The first Agent request was not started. Retry the first input or switch the model.`);
|
|
3995
4039
|
if (!stateKey) {
|
|
3996
4040
|
// Pure Agent/CLI mode has no workspace conversation file to rename, but
|
|
3997
4041
|
// still uses the title request as the required first model-availability
|
|
@@ -3999,6 +4043,8 @@ class Agent {
|
|
|
3999
4043
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
4000
4044
|
if (await requestTitle())
|
|
4001
4045
|
return true;
|
|
4046
|
+
if (terminalFailure)
|
|
4047
|
+
break;
|
|
4002
4048
|
if (signal?.aborted)
|
|
4003
4049
|
return false;
|
|
4004
4050
|
if (attempt < maxAttempts - 1)
|
|
@@ -4036,6 +4082,8 @@ class Agent {
|
|
|
4036
4082
|
this.publishConversationTitle(title, conversationId);
|
|
4037
4083
|
return true;
|
|
4038
4084
|
}
|
|
4085
|
+
if (terminalFailure)
|
|
4086
|
+
break;
|
|
4039
4087
|
if (signal?.aborted)
|
|
4040
4088
|
return false;
|
|
4041
4089
|
if (attempt < maxAttempts - 1)
|
|
@@ -4290,7 +4338,7 @@ class Agent {
|
|
|
4290
4338
|
chatMessages: [...this.chatMessages],
|
|
4291
4339
|
history: [...this.history],
|
|
4292
4340
|
providerUsage: this.conversationProviderUsage(),
|
|
4293
|
-
compressionCache:
|
|
4341
|
+
compressionCache: this.mergedCompressionCacheForPersist(stored.conversations[stateKey]),
|
|
4294
4342
|
branchMailbox: [...this.branchMailbox],
|
|
4295
4343
|
branchCommunication: this.branchCommunicationEnabled,
|
|
4296
4344
|
plan: this.normalizeConversationPlan(this.conversationPlan),
|
|
@@ -4350,7 +4398,7 @@ class Agent {
|
|
|
4350
4398
|
if (saved) {
|
|
4351
4399
|
this.restoreProviderUsage(saved.providerUsage);
|
|
4352
4400
|
this.history = [...saved.history];
|
|
4353
|
-
this.compressionCache =
|
|
4401
|
+
this.compressionCache = this.compressionCacheForBranch(saved.compressionCache);
|
|
4354
4402
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map(entry => Number(entry.id.replace(/^ctx-cache-/, '')) || 0)) + 1;
|
|
4355
4403
|
this.branchMailbox = (saved.branchMailbox || []).map(message => ({ ...message }));
|
|
4356
4404
|
this.nextBranchMessageSequence = Math.max(1, ...this.branchMailbox.map(message => Number(message.sequence) || 0)) + 1;
|
|
@@ -4379,7 +4427,7 @@ class Agent {
|
|
|
4379
4427
|
this.restoreProviderUsage(persisted?.providerUsage);
|
|
4380
4428
|
const repairedHistory = this.repairDanglingToolCalls(persisted?.history ? [...persisted.history] : []);
|
|
4381
4429
|
this.history = repairedHistory.messages;
|
|
4382
|
-
this.compressionCache = persisted?.compressionCache
|
|
4430
|
+
this.compressionCache = this.compressionCacheForBranch(persisted?.compressionCache);
|
|
4383
4431
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map(entry => Number(entry.id.replace(/^ctx-cache-/, '')) || 0)) + 1;
|
|
4384
4432
|
this.branchMailbox = (persisted?.branchMailbox || []).map(message => ({ ...message }));
|
|
4385
4433
|
this.nextBranchMessageSequence = Math.max(1, ...this.branchMailbox.map(message => Number(message.sequence) || 0)) + 1;
|
|
@@ -6015,7 +6063,7 @@ class Agent {
|
|
|
6015
6063
|
let requestContext = null;
|
|
6016
6064
|
const context = saved?.version === 1 ? saved.requestContext : null;
|
|
6017
6065
|
if (context && typeof context.requestId === 'string' && typeof context.runId === 'string' && typeof context.at === 'string') {
|
|
6018
|
-
requestContext = { requestId: context.requestId, runId: context.runId, model: String(context.model || ''), at: context.at,
|
|
6066
|
+
requestContext = { requestId: context.requestId, runId: context.runId, branchId: String(context.branchId || ''), contextHash: String(context.contextHash || ''), model: String(context.model || ''), at: context.at,
|
|
6019
6067
|
estimatedTokens: 0, longHistoryTokens: 0, buildBlockTokens: 0, systemPromptTokens: 0, toolSchemaTokens: 0, messageCount: 0, hasImages: context.hasImages === true };
|
|
6020
6068
|
for (const key of ['estimatedTokens', 'longHistoryTokens', 'buildBlockTokens', 'systemPromptTokens', 'toolSchemaTokens', 'messageCount', 'inputTokens', 'cacheReadTokens']) {
|
|
6021
6069
|
const value = context[key];
|
|
@@ -6083,9 +6131,16 @@ class Agent {
|
|
|
6083
6131
|
return request;
|
|
6084
6132
|
}
|
|
6085
6133
|
recordRequestContext(request, messages, system, tools, model) {
|
|
6134
|
+
const contextHash = crypto.createHash('sha256').update(JSON.stringify({
|
|
6135
|
+
model,
|
|
6136
|
+
system,
|
|
6137
|
+
tools,
|
|
6138
|
+
messages,
|
|
6139
|
+
})).digest('hex').slice(0, 24);
|
|
6086
6140
|
const context = {
|
|
6087
6141
|
...(0, requestContextEstimate_1.estimateSubmittedContext)(messages, system, tools, this.compressionBuildBlockStart(messages)),
|
|
6088
|
-
requestId: request.id, runId: this.currentWorkRunId(),
|
|
6142
|
+
requestId: request.id, runId: this.currentWorkRunId(), branchId: this.currentBranchNodeId(),
|
|
6143
|
+
contextHash, model, at: new Date().toISOString(),
|
|
6089
6144
|
};
|
|
6090
6145
|
this.mutateRequestUsage(request, usage => { usage.requestContext = context; });
|
|
6091
6146
|
}
|
|
@@ -8187,6 +8242,13 @@ class Agent {
|
|
|
8187
8242
|
if (!titleProvider || !titleModelName) {
|
|
8188
8243
|
throw new Error('Conversation title generation failed; no resolved model deployment is available. No LLM configured. Add provider in Settings > Models.');
|
|
8189
8244
|
}
|
|
8245
|
+
// The title probe is a hard gate before the first formal request. Show
|
|
8246
|
+
// the exact deployment being checked immediately so a slow or failing
|
|
8247
|
+
// first turn never looks like an unexplained freeze.
|
|
8248
|
+
this.emitWorkEvent({
|
|
8249
|
+
type: 'status',
|
|
8250
|
+
content: `Preparing the first response: checking ${titleProvider.name}/${titleModelName} and generating the conversation title.`,
|
|
8251
|
+
});
|
|
8190
8252
|
const titled = await this.startFirstInputConversationTitle(firstResponseTitleInput.messageId, firstResponseTitleInput.input, titleProvider, titleModelName, titleIntelligence, processSignal);
|
|
8191
8253
|
if (!titled)
|
|
8192
8254
|
throw new Error('Conversation title generation failed; the first Agent request was not started. Retry the first input.');
|
|
@@ -9754,7 +9816,41 @@ class Agent {
|
|
|
9754
9816
|
compressionArchiveScopeKey() {
|
|
9755
9817
|
if (this.isSubagentRuntime || !this.config.getBool('context', 'compression_archive_enabled'))
|
|
9756
9818
|
return null;
|
|
9757
|
-
|
|
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] }))];
|
|
9758
9854
|
}
|
|
9759
9855
|
coldCompressionEntries() {
|
|
9760
9856
|
const scopeKey = this.compressionArchiveScopeKey();
|
|
@@ -9831,6 +9927,7 @@ class Agent {
|
|
|
9831
9927
|
foldedChars,
|
|
9832
9928
|
model,
|
|
9833
9929
|
fallback,
|
|
9930
|
+
branchNodeId: this.currentBranchNodeId() || undefined,
|
|
9834
9931
|
});
|
|
9835
9932
|
this.nextCompressionCacheId += 1;
|
|
9836
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");
|
|
@@ -69,6 +70,36 @@ const HIDDEN_LINE_PREFIXES = [
|
|
|
69
70
|
'analysis:',
|
|
70
71
|
'analysis:',
|
|
71
72
|
];
|
|
73
|
+
/**
|
|
74
|
+
* Optional provider-side session cache identity. A remote session is mutable
|
|
75
|
+
* state, so it must never be shared by two branches of the same conversation:
|
|
76
|
+
* a fork that reuses the root conversation id can read the other branch's
|
|
77
|
+
* history. Keep the id stable within one branch (normal sequential turns still
|
|
78
|
+
* cache-hit) and change it the moment the runtime branch changes.
|
|
79
|
+
*
|
|
80
|
+
* dev-0.6.4: 分支身份固定在 Build 启动时创建的那条分支节点上(用户分页分支与
|
|
81
|
+
* 实验性分支交流分支共用同一规则)。这里绝不读取「当前正在查看/激活的分支」,
|
|
82
|
+
* 否则正在并行运行的兄弟分支或中途切换的页面会把请求绑到别的分支的远程会话。
|
|
83
|
+
*/
|
|
84
|
+
function providerSessionIdentity(agent) {
|
|
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);
|
|
95
|
+
try {
|
|
96
|
+
const snapshot = agent.getConversationSnapshot(conversationId);
|
|
97
|
+
return (0, branchIdentity_1.branchConversationIdentity)(conversationId, String(snapshot.runtimeBranchId || snapshot.activeBranchId || ''));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return conversationId;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
72
103
|
function partialMarkerSuffix(value, marker) {
|
|
73
104
|
const lower = value.toLowerCase();
|
|
74
105
|
const target = marker.toLowerCase();
|
|
@@ -660,7 +691,11 @@ async function runAgentKernel(agent) {
|
|
|
660
691
|
currentAgent.checkpointPeerInput();
|
|
661
692
|
peerInputCheckpointed = true;
|
|
662
693
|
}
|
|
663
|
-
const { temperature,
|
|
694
|
+
const { temperature, reasoningEffort } = currentProvider.intelligenceConfig(currentAgent.intelligence);
|
|
695
|
+
// A normal Agent reply has no client-side output cap. The provider
|
|
696
|
+
// endpoint owns its real limit; protocols that require a positive
|
|
697
|
+
// max_tokens resolve one internally.
|
|
698
|
+
const maxTokens = 0;
|
|
664
699
|
const newmarkMessages = fromKernelMessages(context.messages).map(message => message.role === 'assistant'
|
|
665
700
|
// Use the same public-content boundary on its first provider
|
|
666
701
|
// submission and after persistence. Trimming only the durable
|
|
@@ -697,7 +732,7 @@ async function runAgentKernel(agent) {
|
|
|
697
732
|
});
|
|
698
733
|
}
|
|
699
734
|
for await (const token of currentProvider.chatStreamWithTools(currentModelName, newmarkMessages, requestSystemPrompt, temperature, maxTokens, providerTools, options?.signal, reasoningEffort, currentAgent.config.getBool('context', 'provider_session_id')
|
|
700
|
-
? currentAgent
|
|
735
|
+
? providerSessionIdentity(currentAgent)
|
|
701
736
|
: undefined)) {
|
|
702
737
|
if (!firstTokenRecorded && ((token.type === 'text' && token.text) || (token.type === 'tool_call' && token.toolCall))) {
|
|
703
738
|
firstTokenRecorded = true;
|
|
@@ -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
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* continuation-v1 contracts.
|
|
3
|
+
*
|
|
4
|
+
* These are the runtime-checked boundaries shared by the PC GUI, the hosted
|
|
5
|
+
* mobile API and the isolated runtimes. A view may read a snapshot, but it can
|
|
6
|
+
* never supply the execution identity that a write is allowed to use.
|
|
7
|
+
*/
|
|
8
|
+
export type WorkspaceId = string;
|
|
9
|
+
export type RootId = string;
|
|
10
|
+
export type BranchId = string;
|
|
11
|
+
export type BuildId = string;
|
|
12
|
+
export type AttemptId = string;
|
|
13
|
+
export type CommandId = string;
|
|
14
|
+
export type SnapshotId = string;
|
|
15
|
+
export declare const CONTINUATION_SCHEMA_VERSION: 1;
|
|
16
|
+
export type BuildStatus = 'QUEUED' | 'RUNNING' | 'WAITING_TOOL' | 'WAITING_APPROVAL' | 'WAITING_SUBAGENT' | 'FINALIZING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED';
|
|
17
|
+
export type AttemptStatus = 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED' | 'EXPIRED';
|
|
18
|
+
export type ContinuationErrorCode = 'TARGET_REQUIRED' | 'ROUTE_MISMATCH' | 'QUEUE_REVISION_CONFLICT' | 'ANCHOR_NOT_IN_LINEAGE' | 'ANCHOR_NOT_COMMITTED' | 'IDEMPOTENCY_KEY_REUSE' | 'STALE_EXECUTION' | 'FINAL_ALREADY_COMMITTED' | 'BUILD_NOT_READY' | 'BRANCH_PAUSED' | 'EFFECT_UNCERTAIN' | 'UNKNOWN_BRANCH' | 'UNKNOWN_BUILD' | 'UNKNOWN_COMMAND';
|
|
19
|
+
export declare class ContinuationError extends Error {
|
|
20
|
+
readonly code: ContinuationErrorCode;
|
|
21
|
+
readonly detail?: Record<string, unknown> | undefined;
|
|
22
|
+
constructor(code: ContinuationErrorCode, message: string, detail?: Record<string, unknown> | undefined);
|
|
23
|
+
}
|
|
24
|
+
export interface CommandTarget {
|
|
25
|
+
workspaceId: WorkspaceId;
|
|
26
|
+
expectedRootId: RootId;
|
|
27
|
+
branchId: BranchId;
|
|
28
|
+
}
|
|
29
|
+
export interface BuildInput {
|
|
30
|
+
text: string;
|
|
31
|
+
images?: Array<{
|
|
32
|
+
dataUrl: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
type?: string;
|
|
35
|
+
}>;
|
|
36
|
+
attachments?: Array<{
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
mimeType: string;
|
|
40
|
+
dataUrl?: string;
|
|
41
|
+
}>;
|
|
42
|
+
requestedMode?: string;
|
|
43
|
+
goalObjective?: string;
|
|
44
|
+
/** Provider-qualified model frozen at command acceptance time. */
|
|
45
|
+
modelSelection?: string;
|
|
46
|
+
/** Branch node captured when the user submitted the command. */
|
|
47
|
+
branchNodeId?: string;
|
|
48
|
+
branchPath?: string[];
|
|
49
|
+
}
|
|
50
|
+
export interface EnqueueBuildCommand {
|
|
51
|
+
schemaVersion: typeof CONTINUATION_SCHEMA_VERSION;
|
|
52
|
+
type: 'ENQUEUE_BUILD';
|
|
53
|
+
commandId: CommandId;
|
|
54
|
+
actorId: string;
|
|
55
|
+
target: CommandTarget;
|
|
56
|
+
expectedTailBuildId: BuildId | null;
|
|
57
|
+
expectedQueueRevision: number;
|
|
58
|
+
input: BuildInput;
|
|
59
|
+
recipeSnapshotId: SnapshotId;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
}
|
|
62
|
+
export interface CommandReceipt {
|
|
63
|
+
schemaVersion: typeof CONTINUATION_SCHEMA_VERSION;
|
|
64
|
+
commandId: CommandId;
|
|
65
|
+
requestHash: string;
|
|
66
|
+
workspaceId: WorkspaceId;
|
|
67
|
+
rootId: RootId;
|
|
68
|
+
branchId: BranchId;
|
|
69
|
+
buildId: BuildId;
|
|
70
|
+
parentBuildId: BuildId | null;
|
|
71
|
+
queueSequence: number;
|
|
72
|
+
queueRevision: number;
|
|
73
|
+
lifecycleRevision: number;
|
|
74
|
+
acceptedAt: string;
|
|
75
|
+
duplicate?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface ExecutionScope {
|
|
78
|
+
workspaceId: WorkspaceId;
|
|
79
|
+
rootId: RootId;
|
|
80
|
+
branchId: BranchId;
|
|
81
|
+
buildId: BuildId;
|
|
82
|
+
attemptId: AttemptId;
|
|
83
|
+
fence: number;
|
|
84
|
+
parentBuildId: BuildId | null;
|
|
85
|
+
baseContextSnapshotId: SnapshotId;
|
|
86
|
+
environmentSnapshotId: SnapshotId;
|
|
87
|
+
workerCapability: string;
|
|
88
|
+
}
|
|
89
|
+
export interface BranchGuardSnapshot {
|
|
90
|
+
branchId: BranchId;
|
|
91
|
+
activeBuildId: BuildId | null;
|
|
92
|
+
activeAttemptId: AttemptId | null;
|
|
93
|
+
fence: number;
|
|
94
|
+
leaseUntil: number;
|
|
95
|
+
workerCapability: string;
|
|
96
|
+
}
|
|
97
|
+
export interface QueueSnapshot {
|
|
98
|
+
workspaceId: WorkspaceId;
|
|
99
|
+
rootId: RootId;
|
|
100
|
+
branchId: BranchId;
|
|
101
|
+
headBuildId: BuildId | null;
|
|
102
|
+
tailBuildId: BuildId | null;
|
|
103
|
+
queueRevision: number;
|
|
104
|
+
lifecycleRevision: number;
|
|
105
|
+
paused: boolean;
|
|
106
|
+
activeBuildId: BuildId | null;
|
|
107
|
+
guard: BranchGuardSnapshot;
|
|
108
|
+
builds: BuildRecord[];
|
|
109
|
+
}
|
|
110
|
+
export interface BuildRecord {
|
|
111
|
+
buildId: BuildId;
|
|
112
|
+
workspaceId: WorkspaceId;
|
|
113
|
+
rootId: RootId;
|
|
114
|
+
branchId: BranchId;
|
|
115
|
+
parentBuildId: BuildId | null;
|
|
116
|
+
inputHash: string;
|
|
117
|
+
input: BuildInput;
|
|
118
|
+
recipeSnapshotId: SnapshotId;
|
|
119
|
+
status: BuildStatus;
|
|
120
|
+
queueSequence: number;
|
|
121
|
+
createdAt: string;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
finalId?: string;
|
|
124
|
+
waitingReason?: string;
|
|
125
|
+
}
|
|
126
|
+
export interface AttemptRecord {
|
|
127
|
+
attemptId: AttemptId;
|
|
128
|
+
buildId: BuildId;
|
|
129
|
+
branchId: BranchId;
|
|
130
|
+
fence: number;
|
|
131
|
+
status: AttemptStatus;
|
|
132
|
+
leaseUntil: number;
|
|
133
|
+
workerCapability: string;
|
|
134
|
+
startedAt: string;
|
|
135
|
+
endedAt?: string;
|
|
136
|
+
}
|
|
137
|
+
export interface FinalRecord {
|
|
138
|
+
finalId: string;
|
|
139
|
+
buildId: BuildId;
|
|
140
|
+
branchId: BranchId;
|
|
141
|
+
finalHash: string;
|
|
142
|
+
stateSnapshotId: SnapshotId;
|
|
143
|
+
text: string;
|
|
144
|
+
committedAt: string;
|
|
145
|
+
}
|
|
146
|
+
export interface ContinuationEvent {
|
|
147
|
+
eventId: string;
|
|
148
|
+
cursor: number;
|
|
149
|
+
type: 'BuildAccepted' | 'AttemptStarted' | 'AttemptHeartbeat' | 'BuildCommitted' | 'BuildFailed' | 'BuildCancelled' | 'BranchForked' | 'BranchPaused' | 'BranchResumed' | 'BuildQueueRepaired' | 'StaleWriteRejected';
|
|
150
|
+
workspaceId: WorkspaceId;
|
|
151
|
+
rootId: RootId;
|
|
152
|
+
branchId: BranchId;
|
|
153
|
+
buildId: BuildId | null;
|
|
154
|
+
attemptId: AttemptId | null;
|
|
155
|
+
fence: number | null;
|
|
156
|
+
at: string;
|
|
157
|
+
payload?: Record<string, unknown>;
|
|
158
|
+
}
|
|
159
|
+
export interface LedgerFile {
|
|
160
|
+
version: typeof CONTINUATION_SCHEMA_VERSION;
|
|
161
|
+
workspaceId: WorkspaceId;
|
|
162
|
+
roots: Record<RootId, {
|
|
163
|
+
rootId: RootId;
|
|
164
|
+
workspaceId: WorkspaceId;
|
|
165
|
+
defaultBranchId: BranchId;
|
|
166
|
+
createdAt: string;
|
|
167
|
+
}>;
|
|
168
|
+
branches: Record<BranchId, BranchRecord>;
|
|
169
|
+
builds: Record<BuildId, BuildRecord>;
|
|
170
|
+
attempts: Record<AttemptId, AttemptRecord>;
|
|
171
|
+
guards: Record<BranchId, BranchGuardSnapshot>;
|
|
172
|
+
commands: Record<string, CommandReceipt>;
|
|
173
|
+
finals: Record<BuildId, FinalRecord>;
|
|
174
|
+
events: ContinuationEvent[];
|
|
175
|
+
nextEventCursor: number;
|
|
176
|
+
nextQueueSequence: number;
|
|
177
|
+
}
|
|
178
|
+
export interface BranchRecord {
|
|
179
|
+
branchId: BranchId;
|
|
180
|
+
workspaceId: WorkspaceId;
|
|
181
|
+
rootId: RootId;
|
|
182
|
+
parentAnchorBuildId: BuildId | null;
|
|
183
|
+
headBuildId: BuildId | null;
|
|
184
|
+
tailBuildId: BuildId | null;
|
|
185
|
+
queueRevision: number;
|
|
186
|
+
lifecycleRevision: number;
|
|
187
|
+
paused: boolean;
|
|
188
|
+
createdAt: string;
|
|
189
|
+
updatedAt: string;
|
|
190
|
+
}
|
|
191
|
+
export declare function stableJson(value: unknown): string;
|
|
192
|
+
export declare function emptyLedger(workspaceId: WorkspaceId): LedgerFile;
|
|
193
|
+
export declare function defaultGuard(branchId: BranchId): BranchGuardSnapshot;
|
|
194
|
+
export declare function commandKey(actorId: string, commandId: CommandId): string;
|
|
195
|
+
export declare function buildInputHash(input: BuildInput): string;
|
|
196
|
+
export declare function requestHash(command: EnqueueBuildCommand): string;
|
|
197
|
+
//# sourceMappingURL=contracts.d.ts.map
|