newmark-agent 0.3.10 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.example.json +6 -0
- package/dist/cli-commands.d.ts +7 -0
- package/dist/cli-commands.js +206 -15
- package/dist/cli-discovery.d.ts +15 -0
- package/dist/cli-discovery.js +182 -0
- package/dist/cli-help.d.ts +4 -0
- package/dist/cli-help.js +46 -0
- package/dist/conversation-utility-host.bundle.cjs +548 -137
- package/dist/core/agent.d.ts +18 -3
- package/dist/core/agent.js +236 -34
- package/dist/core/agentKernelRunner.js +72 -10
- package/dist/core/browserControl.d.ts +8 -0
- package/dist/core/browserUsePageAdapter.d.ts +3 -0
- package/dist/core/browserUsePageAdapter.js +19 -2
- package/dist/core/computerUseSession.d.ts +44 -0
- package/dist/core/computerUseSession.js +105 -0
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.d.ts +1 -0
- package/dist/core/conversationKernel.js +39 -1
- package/dist/core/electronBrowserUseHost.js +7 -0
- package/dist/core/electronUtilityAgentClient.js +84 -2
- package/dist/core/electronUtilityRuntimePool.d.ts +11 -0
- package/dist/core/electronUtilityRuntimePool.js +62 -0
- package/dist/core/flow-runner.js +1 -1
- package/dist/core/modelValidationStore.d.ts +4 -1
- package/dist/core/modelValidationStore.js +7 -1
- package/dist/core/utilityHostToolRouter.d.ts +7 -0
- package/dist/core/utilityHostToolRouter.js +25 -33
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.js +14 -0
- package/dist/core/wslAgentRuntimePool.d.ts +4 -0
- package/dist/core/wslAgentRuntimePool.js +56 -0
- package/dist/launcher.js +51 -10
- package/dist/llm/provider.d.ts +8 -5
- package/dist/llm/provider.js +85 -33
- package/dist/main.js +297 -61
- package/dist/preload.js +10 -2
- package/dist/providers/chat-completions.adapter.js +1 -5
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +44 -0
- package/dist/providers/responses.adapter.js +1 -3
- package/dist/tools/index.js +36 -49
- package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
- package/dist/tui/src/app.js +47 -13
- package/dist/tui/src/render.js +23 -7
- package/dist/tui/src/state.js +61 -9
- package/dist/ui/index.html +545 -122
- package/dist/wsl-agent-host.bundle.cjs +548 -137
- package/package.json +14 -5
package/dist/core/agent.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export interface AgentRuntimeOptions {
|
|
|
33
33
|
actorId?: string;
|
|
34
34
|
conversationId?: string;
|
|
35
35
|
runtimeLifecycleRole?: RuntimeLifecycleRole;
|
|
36
|
+
readOnlyConfig?: boolean;
|
|
36
37
|
linkedPlanAccess?: {
|
|
37
38
|
get(conversationId?: string): LinkedPlanState;
|
|
38
39
|
update(markdown: string, expectedRevision: number, actorId: string, conversationId?: string): LinkedPlanState;
|
|
@@ -487,7 +488,7 @@ export declare class Agent {
|
|
|
487
488
|
private normalizeConversationChatMessages;
|
|
488
489
|
persistGuideMessage(clientMessageId: string, content: string, runId?: string, historyContent?: unknown, attachments?: ConversationImageAttachment[], guideId?: string): boolean;
|
|
489
490
|
setConversationWorkRunExpanded(runId: string, expanded: boolean): boolean;
|
|
490
|
-
finishConversationWorkRun(runId: string, status: Exclude<ConversationWorkRunStatus, 'running'>, endedAt?: string): boolean;
|
|
491
|
+
finishConversationWorkRun(runId: string, status: Exclude<ConversationWorkRunStatus, 'running'>, endedAt?: string, errorMessage?: string): boolean;
|
|
491
492
|
private ensureCompletedWorkRunFinalResult;
|
|
492
493
|
emitWorkEvent(input: Omit<AgentWorkEvent, 'id' | 'conversationId' | 'mode' | 'model' | 'timestamp'> & Partial<Pick<AgentWorkEvent, 'conversationId' | 'mode' | 'model' | 'timestamp'>>): AgentWorkEvent;
|
|
493
494
|
appendWorkflowMessage(content: string, toolName?: string, toolArgs?: string, persist?: boolean): void;
|
|
@@ -598,6 +599,7 @@ export declare class Agent {
|
|
|
598
599
|
private applyWorkspaceContext;
|
|
599
600
|
selectWorkspace(id: string): WorkspaceInfo | null;
|
|
600
601
|
selectWorkspaceFromStorage(id: string): WorkspaceInfo | null;
|
|
602
|
+
refreshWorkspaceRegistryFromStorage(): WorkspaceInfo | null;
|
|
601
603
|
setConversation(id: string): string;
|
|
602
604
|
setConversationFromStorage(id: string): string;
|
|
603
605
|
persistActiveConversationSelection(id: string, ws?: WorkspaceInfo | null): string;
|
|
@@ -688,9 +690,20 @@ export declare class Agent {
|
|
|
688
690
|
claimGoalContinuationMessage(options?: {
|
|
689
691
|
force?: boolean;
|
|
690
692
|
}): AgentPromptMessage | null;
|
|
693
|
+
private buildSessionArchive;
|
|
691
694
|
private writeSessionArchive;
|
|
695
|
+
private writeSessionArchiveAsync;
|
|
692
696
|
archiveSession(): string;
|
|
693
697
|
archiveConversation(conversationId: string): string | null;
|
|
698
|
+
/**
|
|
699
|
+
* Non-blocking archive writer used by the desktop IPC path. The conversation
|
|
700
|
+
* state merge remains synchronous and lock-protected, but the potentially
|
|
701
|
+
* large markdown payload and manifest use promise-based filesystem I/O so
|
|
702
|
+
* independent workspaces can archive in parallel without freezing Electron.
|
|
703
|
+
*/
|
|
704
|
+
archiveConversationAsync(conversationId: string): Promise<string | null>;
|
|
705
|
+
private archiveConversationAsyncUnlocked;
|
|
706
|
+
private finalizeAsyncConversationArchive;
|
|
694
707
|
private listStoredConversationIds;
|
|
695
708
|
listArchives(scope?: 'workspace' | 'all'): Array<{
|
|
696
709
|
id: string;
|
|
@@ -743,7 +756,9 @@ export declare class Agent {
|
|
|
743
756
|
switchToFallbackModel(errorText?: string): string | null;
|
|
744
757
|
isLlmErrorText(text: string): boolean;
|
|
745
758
|
private scopedSwitchModels;
|
|
746
|
-
validateModels(selectedNames?: string[]
|
|
759
|
+
validateModels(selectedNames?: string[], options?: {
|
|
760
|
+
persist?: boolean;
|
|
761
|
+
}): Promise<ModelValidationResult[]>;
|
|
747
762
|
isModelValidationRunning(): boolean;
|
|
748
763
|
modelValidationStatus(): ModelValidationProgress;
|
|
749
764
|
private runModelValidation;
|
|
@@ -825,7 +840,7 @@ export declare class Agent {
|
|
|
825
840
|
handleSkillDownload(args: string): Promise<void>;
|
|
826
841
|
refreshSkills(): void;
|
|
827
842
|
private processOpencode;
|
|
828
|
-
maybeCompress(msgs: Array<Record<string, unknown>>, provider?: LLMProvider | null, signal?: AbortSignal, compressionModel?: string, force?: boolean): Promise<
|
|
843
|
+
maybeCompress(msgs: Array<Record<string, unknown>>, provider?: LLMProvider | null, signal?: AbortSignal, compressionModel?: string, force?: boolean): Promise<boolean>;
|
|
829
844
|
private buildCompressionSummary;
|
|
830
845
|
private localCompressionSummary;
|
|
831
846
|
private latestUserHistoryText;
|
package/dist/core/agent.js
CHANGED
|
@@ -292,7 +292,7 @@ class Agent {
|
|
|
292
292
|
this.subagentName = options.subagentName || '';
|
|
293
293
|
this.subagentPrompt = options.subagentPrompt || '';
|
|
294
294
|
this.linkedPlanAccess = options.linkedPlanAccess;
|
|
295
|
-
this.config = new config_1.ConfigManager(rootPath);
|
|
295
|
+
this.config = new config_1.ConfigManager(rootPath, { readOnly: options.readOnlyConfig === true });
|
|
296
296
|
this.compressionHistoryArchive = new compressionHistoryArchive_1.CompressionHistoryArchive(rootPath);
|
|
297
297
|
this.contextV2 = new agent_context_manager_1.AgentContextManager(rootPath, this.config);
|
|
298
298
|
this.agentRunService = this.config.contextFlag('agent_runtime_v2')
|
|
@@ -763,7 +763,12 @@ class Agent {
|
|
|
763
763
|
}
|
|
764
764
|
const previousAuto = this.model === 'auto' ? this.resolvedDeployment : null;
|
|
765
765
|
const qualified = parseDeploymentSelectionValue(requested);
|
|
766
|
-
const
|
|
766
|
+
const legacyQualified = requested.includes('/')
|
|
767
|
+
? this.config.allModels().filter(model => `${model.provider_id}/${model.name}` === requested || `${model.provider}/${model.name}` === requested)
|
|
768
|
+
: [];
|
|
769
|
+
const current = qualified
|
|
770
|
+
? this.config.findDeployment(qualified)
|
|
771
|
+
: (legacyQualified.length === 1 ? legacyQualified[0] : (requested ? this.config.findModel(requested) : undefined));
|
|
767
772
|
this.model = current?.name || requested;
|
|
768
773
|
this.fixedDeployment = current ? this.deploymentRef(current) : qualified;
|
|
769
774
|
this.resolvedDeployment = null;
|
|
@@ -2062,7 +2067,7 @@ class Agent {
|
|
|
2062
2067
|
this.saveWorkspaceConversationState(true);
|
|
2063
2068
|
return true;
|
|
2064
2069
|
}
|
|
2065
|
-
finishConversationWorkRun(runId, status, endedAt = this.nowIso()) {
|
|
2070
|
+
finishConversationWorkRun(runId, status, endedAt = this.nowIso(), errorMessage = '') {
|
|
2066
2071
|
const run = this.workRuns.find(item => item.runId === String(runId || ''));
|
|
2067
2072
|
if (!run)
|
|
2068
2073
|
return false;
|
|
@@ -2110,7 +2115,9 @@ class Agent {
|
|
|
2110
2115
|
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
2111
2116
|
this.emitWorkEvent({
|
|
2112
2117
|
type: status === 'completed' ? 'done' : status === 'error' ? 'error' : 'status',
|
|
2113
|
-
content: status === '
|
|
2118
|
+
content: status === 'error'
|
|
2119
|
+
? (String(errorMessage || '').trim() || 'Agent run failed.')
|
|
2120
|
+
: status === 'force_interrupted' ? 'Force interrupted.' : status === 'interrupted' ? 'Interrupted.' : 'Response complete.',
|
|
2114
2121
|
status,
|
|
2115
2122
|
runId: run.runId,
|
|
2116
2123
|
conversationId: run.target.conversationId,
|
|
@@ -2281,6 +2288,12 @@ class Agent {
|
|
|
2281
2288
|
this.awaitingAgentKernelRuntime = false;
|
|
2282
2289
|
if (!runtime)
|
|
2283
2290
|
return;
|
|
2291
|
+
// A user stop can arrive while the Native Kernel is still being loaded or
|
|
2292
|
+
// assembling its first context. In that handoff window
|
|
2293
|
+
// abortActiveKernelRun() can only abort the outer process signal; make a
|
|
2294
|
+
// runtime that attaches afterwards observe the already-aborted state too.
|
|
2295
|
+
if (this.activeProcessAbortController?.signal.aborted)
|
|
2296
|
+
runtime.abort?.();
|
|
2284
2297
|
const queued = this.pendingAgentKernelQueue.splice(0);
|
|
2285
2298
|
for (const item of queued) {
|
|
2286
2299
|
const accepted = this.forwardAgentKernelQueueMessage(item.content, item.queueMode, item.clientMessageId, item.runId, item.images, item.hiddenUserInput);
|
|
@@ -3595,6 +3608,28 @@ class Agent {
|
|
|
3595
3608
|
this.loadWorkspaceConversationState();
|
|
3596
3609
|
return selected;
|
|
3597
3610
|
}
|
|
3611
|
+
refreshWorkspaceRegistryFromStorage() {
|
|
3612
|
+
const before = JSON.stringify({
|
|
3613
|
+
internal: this.workspace.internal,
|
|
3614
|
+
external: this.workspace.external,
|
|
3615
|
+
current: this.workspace.current,
|
|
3616
|
+
});
|
|
3617
|
+
const selected = this.workspace.reloadFromStorage();
|
|
3618
|
+
const after = JSON.stringify({
|
|
3619
|
+
internal: this.workspace.internal,
|
|
3620
|
+
external: this.workspace.external,
|
|
3621
|
+
current: this.workspace.current,
|
|
3622
|
+
});
|
|
3623
|
+
if (before === after)
|
|
3624
|
+
return selected;
|
|
3625
|
+
if (selected)
|
|
3626
|
+
this.config.loadWorkspaceConfig(selected.path);
|
|
3627
|
+
else
|
|
3628
|
+
this.config.clearWorkspaceOverrides();
|
|
3629
|
+
this.workspaceConversations.clear();
|
|
3630
|
+
this.loadWorkspaceConversationState();
|
|
3631
|
+
return selected;
|
|
3632
|
+
}
|
|
3598
3633
|
setConversation(id) {
|
|
3599
3634
|
const clean = this.safeConversationId(id || 'default');
|
|
3600
3635
|
// Conversation runners may bind a target workspace directly before their
|
|
@@ -4663,29 +4698,52 @@ class Agent {
|
|
|
4663
4698
|
this.saveWorkspaceConversationState(true);
|
|
4664
4699
|
return { text, hiddenUserInput: true, goalContinuation: true };
|
|
4665
4700
|
}
|
|
4666
|
-
|
|
4701
|
+
buildSessionArchive(messages, mode, model, archiveDir) {
|
|
4667
4702
|
const stamp = new Date().toISOString().replace(/[:.]/g, '').replace('T', '_').replace('Z', '');
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
const
|
|
4672
|
-
let
|
|
4673
|
-
|
|
4674
|
-
|
|
4703
|
+
// Millisecond-only names collide when a user clicks several archive
|
|
4704
|
+
// buttons in one event-loop turn. Keep the readable timestamp and add a
|
|
4705
|
+
// cryptographic suffix so every request owns an independent file.
|
|
4706
|
+
const filename = `session_${stamp}_${crypto.randomUUID().slice(0, 8)}.md`;
|
|
4707
|
+
let markdown = `# Newmark Session — ${stamp}\n\n`;
|
|
4708
|
+
markdown += `**Mode**: ${mode}\n**Model**: ${model}\n`;
|
|
4709
|
+
markdown += `**Messages**: ${messages.length}\n\n---\n\n`;
|
|
4675
4710
|
if (this.goal)
|
|
4676
|
-
|
|
4711
|
+
markdown += `**Goal**: ${this.goal.objective}\n\n`;
|
|
4677
4712
|
for (const msg of messages) {
|
|
4678
|
-
|
|
4713
|
+
markdown += `**[${msg.role}] ${msg.timestamp}**\n\n${msg.content}\n\n`;
|
|
4679
4714
|
for (const attachment of (0, conversationAttachments_1.hydrateConversationImageAttachments)(this.rootPath, msg.attachments)) {
|
|
4680
4715
|
const archived = (0, conversationAttachments_1.archiveConversationImageAttachment)(this.rootPath, archiveDir, attachment);
|
|
4681
4716
|
if (!archived)
|
|
4682
4717
|
continue;
|
|
4683
4718
|
const alt = archived.name.replace(/[\]\r\n]/g, ' ').trim() || 'Submitted image';
|
|
4684
|
-
|
|
4719
|
+
markdown += `\n\n`;
|
|
4685
4720
|
}
|
|
4686
4721
|
}
|
|
4687
|
-
|
|
4688
|
-
|
|
4722
|
+
return { filename, markdown };
|
|
4723
|
+
}
|
|
4724
|
+
writeSessionArchive(messages, mode, model) {
|
|
4725
|
+
const archiveDir = this.archiveDir();
|
|
4726
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
4727
|
+
const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
|
|
4728
|
+
fs.writeFileSync(path.join(archiveDir, archive.filename), archive.markdown, 'utf-8');
|
|
4729
|
+
return archive.filename;
|
|
4730
|
+
}
|
|
4731
|
+
async writeSessionArchiveAsync(messages, mode, model, archiveDir = this.archiveDir()) {
|
|
4732
|
+
const archive = this.buildSessionArchive(messages, mode, model, archiveDir);
|
|
4733
|
+
await fs.promises.mkdir(archiveDir, { recursive: true });
|
|
4734
|
+
const outPath = path.join(archiveDir, archive.filename);
|
|
4735
|
+
const tempPath = `${outPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
4736
|
+
try {
|
|
4737
|
+
await fs.promises.writeFile(tempPath, archive.markdown, 'utf-8');
|
|
4738
|
+
await fs.promises.rename(tempPath, outPath);
|
|
4739
|
+
}
|
|
4740
|
+
finally {
|
|
4741
|
+
try {
|
|
4742
|
+
await fs.promises.unlink(tempPath);
|
|
4743
|
+
}
|
|
4744
|
+
catch { }
|
|
4745
|
+
}
|
|
4746
|
+
return archive.filename;
|
|
4689
4747
|
}
|
|
4690
4748
|
archiveSession() {
|
|
4691
4749
|
return this.writeSessionArchive(this.chatMessages, this.modeName(), this.model);
|
|
@@ -4759,6 +4817,111 @@ class Agent {
|
|
|
4759
4817
|
}
|
|
4760
4818
|
return filename;
|
|
4761
4819
|
}
|
|
4820
|
+
/**
|
|
4821
|
+
* Non-blocking archive writer used by the desktop IPC path. The conversation
|
|
4822
|
+
* state merge remains synchronous and lock-protected, but the potentially
|
|
4823
|
+
* large markdown payload and manifest use promise-based filesystem I/O so
|
|
4824
|
+
* independent workspaces can archive in parallel without freezing Electron.
|
|
4825
|
+
*/
|
|
4826
|
+
async archiveConversationAsync(conversationId) {
|
|
4827
|
+
// Archive payloads intentionally start in parallel. The final state
|
|
4828
|
+
// mutation below operates on the latest locked disk snapshot, so no
|
|
4829
|
+
// JavaScript queue is needed for independent targets in one workspace.
|
|
4830
|
+
return await this.archiveConversationAsyncUnlocked(conversationId);
|
|
4831
|
+
}
|
|
4832
|
+
async archiveConversationAsyncUnlocked(conversationId) {
|
|
4833
|
+
const ws = this.workspace.current;
|
|
4834
|
+
if (!ws)
|
|
4835
|
+
return null;
|
|
4836
|
+
const clean = this.safeConversationId(conversationId || 'default');
|
|
4837
|
+
const stateKey = this.workspaceConversationStateKey(clean);
|
|
4838
|
+
if (!stateKey)
|
|
4839
|
+
return null;
|
|
4840
|
+
const memoryKey = `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
|
|
4841
|
+
const archiveDir = path.join(ws.path, 'archive');
|
|
4842
|
+
const workspacePrefix = this.workspaceConversationPrefix() || '';
|
|
4843
|
+
const archiveMode = this.modeName();
|
|
4844
|
+
const archiveModel = this.model;
|
|
4845
|
+
// readStoredConversationState returns a cache object. Clone it before any
|
|
4846
|
+
// asynchronous gap so concurrent archive requests cannot mutate one
|
|
4847
|
+
// another's source snapshot.
|
|
4848
|
+
const cachedStored = this.readStoredConversationState(ws);
|
|
4849
|
+
const stored = JSON.parse(JSON.stringify(cachedStored || {}));
|
|
4850
|
+
const persisted = stored.conversations?.[stateKey];
|
|
4851
|
+
if (persisted)
|
|
4852
|
+
this.normalizeConversationTree(persisted);
|
|
4853
|
+
const memory = this.workspaceConversations.get(memoryKey);
|
|
4854
|
+
const persistedMessagesAvailable = persisted?.chatMessages !== undefined;
|
|
4855
|
+
const sourceMessages = persisted?.chatMessages ?? memory?.chatMessages ?? [];
|
|
4856
|
+
const sourceHistory = persistedMessagesAvailable
|
|
4857
|
+
? (persisted?.history ?? [])
|
|
4858
|
+
: (memory?.history ?? persisted?.history ?? []);
|
|
4859
|
+
const messages = this.normalizeConversationChatMessages(sourceMessages, sourceHistory);
|
|
4860
|
+
const filename = await this.writeSessionArchiveAsync(messages, archiveMode, archiveModel, archiveDir);
|
|
4861
|
+
const archiveEntry = persisted ? JSON.parse(JSON.stringify(persisted)) : {
|
|
4862
|
+
title: this.titleFromMessages(messages, clean),
|
|
4863
|
+
chatMessages: messages,
|
|
4864
|
+
history: sourceHistory,
|
|
4865
|
+
plan: memory?.plan,
|
|
4866
|
+
linkedPlan: memory?.linkedPlan,
|
|
4867
|
+
subagentState: memory?.subagentState,
|
|
4868
|
+
workRuns: memory?.workRuns,
|
|
4869
|
+
continuations: memory?.continuations,
|
|
4870
|
+
updatedAt: new Date().toISOString(),
|
|
4871
|
+
};
|
|
4872
|
+
const manifest = {
|
|
4873
|
+
version: 2,
|
|
4874
|
+
kind: 'newmark-conversation-archive',
|
|
4875
|
+
archivedAt: new Date().toISOString(),
|
|
4876
|
+
conversationId: clean,
|
|
4877
|
+
workspaceId: ws.id,
|
|
4878
|
+
workspaceName: ws.name,
|
|
4879
|
+
workspacePath: ws.path,
|
|
4880
|
+
workspaceInternal: ws.isInternal,
|
|
4881
|
+
statePrefix: workspacePrefix,
|
|
4882
|
+
entry: this.conversationEntryForDisk(archiveEntry),
|
|
4883
|
+
};
|
|
4884
|
+
const manifestPath = this.archiveManifestPath(path.join(archiveDir, filename));
|
|
4885
|
+
const manifestTempPath = `${manifestPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
4886
|
+
try {
|
|
4887
|
+
await fs.promises.writeFile(manifestTempPath, JSON.stringify(manifest, null, 2), 'utf-8');
|
|
4888
|
+
await fs.promises.rename(manifestTempPath, manifestPath);
|
|
4889
|
+
}
|
|
4890
|
+
finally {
|
|
4891
|
+
try {
|
|
4892
|
+
await fs.promises.unlink(manifestTempPath);
|
|
4893
|
+
}
|
|
4894
|
+
catch { }
|
|
4895
|
+
}
|
|
4896
|
+
this.finalizeAsyncConversationArchive(clean, stateKey, memoryKey, ws);
|
|
4897
|
+
return filename;
|
|
4898
|
+
}
|
|
4899
|
+
finalizeAsyncConversationArchive(clean, stateKey, memoryKey, ws) {
|
|
4900
|
+
let nextActiveId = '';
|
|
4901
|
+
this.mutateStoredConversationState(ws, latest => {
|
|
4902
|
+
latest.conversations = latest.conversations || {};
|
|
4903
|
+
delete latest.conversations[stateKey];
|
|
4904
|
+
// Derive the target prefix from the captured state key rather than the
|
|
4905
|
+
// Agent's possibly changed foreground workspace.
|
|
4906
|
+
const prefix = stateKey.slice(0, Math.max(0, stateKey.length - clean.length - 1)) + '-';
|
|
4907
|
+
const remaining = Object.keys(latest.conversations)
|
|
4908
|
+
.filter(key => !prefix || key.startsWith(prefix))
|
|
4909
|
+
.map(key => key.slice(prefix.length))
|
|
4910
|
+
.filter(Boolean);
|
|
4911
|
+
const currentActiveId = this.safeConversationId(latest.activeConversationId || this.activeConversationId || 'default');
|
|
4912
|
+
if (clean === currentActiveId)
|
|
4913
|
+
latest.activeConversationId = remaining[0] || 'default';
|
|
4914
|
+
nextActiveId = latest.activeConversationId || remaining[0] || 'default';
|
|
4915
|
+
return latest;
|
|
4916
|
+
});
|
|
4917
|
+
this.workspaceConversations.delete(memoryKey);
|
|
4918
|
+
const duplicateMemoryKey = `${ws.isInternal ? 'internal' : 'external'}:${path.resolve(ws.path)}::conversation:${clean}`;
|
|
4919
|
+
this.workspaceConversations.delete(duplicateMemoryKey);
|
|
4920
|
+
if (clean === this.safeConversationId(this.activeConversationId || 'default')) {
|
|
4921
|
+
this.activeConversationId = nextActiveId || 'default';
|
|
4922
|
+
this.loadWorkspaceConversationState();
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4762
4925
|
listStoredConversationIds(stored) {
|
|
4763
4926
|
const prefix = `${this.workspaceConversationPrefix() || ''}-`;
|
|
4764
4927
|
return Object.keys(stored.conversations || {})
|
|
@@ -5393,10 +5556,10 @@ class Agent {
|
|
|
5393
5556
|
const provider = this.config.findProvider(providerId);
|
|
5394
5557
|
return all.filter(m => m.provider_id === (provider?.id || providerId));
|
|
5395
5558
|
}
|
|
5396
|
-
async validateModels(selectedNames) {
|
|
5559
|
+
async validateModels(selectedNames, options = {}) {
|
|
5397
5560
|
if (this.modelValidationPromise)
|
|
5398
5561
|
return this.modelValidationPromise;
|
|
5399
|
-
const validation = this.runModelValidation(selectedNames);
|
|
5562
|
+
const validation = this.runModelValidation(selectedNames, options.persist !== false);
|
|
5400
5563
|
this.modelValidationPromise = validation;
|
|
5401
5564
|
try {
|
|
5402
5565
|
return await validation;
|
|
@@ -5416,7 +5579,7 @@ class Agent {
|
|
|
5416
5579
|
recentChecks: this.modelValidationProgress.recentChecks.map(item => ({ ...item })),
|
|
5417
5580
|
};
|
|
5418
5581
|
}
|
|
5419
|
-
async runModelValidation(selectedNames) {
|
|
5582
|
+
async runModelValidation(selectedNames, persist = true) {
|
|
5420
5583
|
const selectedModels = this.config.modelsForSelections(selectedNames);
|
|
5421
5584
|
if (!selectedModels.length) {
|
|
5422
5585
|
this.modelValidationProgress = {
|
|
@@ -5427,7 +5590,10 @@ class Agent {
|
|
|
5427
5590
|
}
|
|
5428
5591
|
const results = [];
|
|
5429
5592
|
const catalogByProvider = new Map();
|
|
5430
|
-
|
|
5593
|
+
// Read-only CLI validation may reuse already persisted, redacted evidence
|
|
5594
|
+
// without rewriting it. This keeps the no-mutation contract while making
|
|
5595
|
+
// repeated release/user checks local when the seven-day record is fresh.
|
|
5596
|
+
const cache = new modelValidationStore_1.FileModelValidationCache(this.rootPath, { readOnly: !persist });
|
|
5431
5597
|
const checksPerModel = 11;
|
|
5432
5598
|
let currentModel = '';
|
|
5433
5599
|
let currentModelChecks = 0;
|
|
@@ -5568,7 +5734,8 @@ class Agent {
|
|
|
5568
5734
|
completedModels: this.modelValidationProgress.completedModels + 1,
|
|
5569
5735
|
};
|
|
5570
5736
|
}
|
|
5571
|
-
|
|
5737
|
+
if (persist)
|
|
5738
|
+
this.config.save();
|
|
5572
5739
|
this.modelValidationProgress = {
|
|
5573
5740
|
...this.modelValidationProgress,
|
|
5574
5741
|
running: false,
|
|
@@ -5851,7 +6018,14 @@ class Agent {
|
|
|
5851
6018
|
const text = typeof input === 'string' ? input : String(input.text || '');
|
|
5852
6019
|
const inputEnvelope = typeof input === 'string' ? null : input;
|
|
5853
6020
|
const hiddenUserInput = inputEnvelope?.hiddenUserInput === true;
|
|
5854
|
-
|
|
6021
|
+
// An empty selection may be repaired to the configured default, but an
|
|
6022
|
+
// explicitly requested fixed model must remain visible to the
|
|
6023
|
+
// fail-closed availability check below. Otherwise a typo such as
|
|
6024
|
+
// provider-that-does-not-exist/missing-model silently becomes the first
|
|
6025
|
+
// configured fixture/default deployment.
|
|
6026
|
+
const explicitFixedModel = this.model !== '' && this.model !== 'auto';
|
|
6027
|
+
if (!explicitFixedModel)
|
|
6028
|
+
this.ensureUsableModelSelection();
|
|
5855
6029
|
const clientMessageId = String(inputEnvelope?.clientMessageId || '').trim();
|
|
5856
6030
|
const inputRunId = String(inputEnvelope?.runId || this.activeWorkRunId || '').trim();
|
|
5857
6031
|
const rawImages = typeof input === 'string' ? [] : (Array.isArray(input.images) ? input.images : []);
|
|
@@ -5949,7 +6123,16 @@ class Agent {
|
|
|
5949
6123
|
await this.evaluateAndSwitch(displayText, inputEnvelope?.routePolicy);
|
|
5950
6124
|
}
|
|
5951
6125
|
if (this.model && this.modelIsUnavailable(this.model)) {
|
|
6126
|
+
const requestedModel = this.model;
|
|
5952
6127
|
this.switchToFallbackModel();
|
|
6128
|
+
if (this.modelIsUnavailable(this.model)) {
|
|
6129
|
+
const message = `[Error] Model '${requestedModel || 'unknown'}' is unavailable or not configured. Select a configured model or enable a valid provider before sending.`;
|
|
6130
|
+
this.status = 'error';
|
|
6131
|
+
// Do not return an error token as if it were a successful assistant
|
|
6132
|
+
// response. The normal catch/finalizer path must publish an error
|
|
6133
|
+
// terminal event and keep the work run out of completed state.
|
|
6134
|
+
throw new Error(message);
|
|
6135
|
+
}
|
|
5953
6136
|
}
|
|
5954
6137
|
// Use external opencode CLI engine
|
|
5955
6138
|
if (this.engine === 'opencode') {
|
|
@@ -7059,13 +7242,13 @@ class Agent {
|
|
|
7059
7242
|
}
|
|
7060
7243
|
async maybeCompress(msgs, provider, signal, compressionModel, force = false) {
|
|
7061
7244
|
if (signal?.aborted)
|
|
7062
|
-
return;
|
|
7245
|
+
return false;
|
|
7063
7246
|
if (!this.config.getBool('context', 'auto_compress'))
|
|
7064
|
-
return;
|
|
7247
|
+
return false;
|
|
7065
7248
|
const total = msgs.reduce((sum, m) => sum + (typeof m.content === 'string' ? m.content.length : JSON.stringify(m.content || '').length), 0);
|
|
7066
7249
|
const budget = this.compressionBudget(msgs);
|
|
7067
7250
|
if (budget.estimatedTokens < budget.triggerTokens && !force)
|
|
7068
|
-
return;
|
|
7251
|
+
return false;
|
|
7069
7252
|
if (!force && this.lastCompression && String(msgs[0]?.content || '').includes(this.lastCompression.summary)) {
|
|
7070
7253
|
const baselineChars = Math.max(0, Number(this.lastCompression.compressedChars || 0));
|
|
7071
7254
|
const baselineTokens = Math.max(0, Number(this.lastCompression.compressedTokens || 0));
|
|
@@ -7074,12 +7257,12 @@ class Agent {
|
|
|
7074
7257
|
const minCharGrowth = Math.max(12_000, Math.floor(baselineChars * 0.25));
|
|
7075
7258
|
const minTokenGrowth = Math.max(1_024, Math.floor(budget.triggerTokens * 0.2));
|
|
7076
7259
|
if (charGrowth < minCharGrowth && tokenGrowth < minTokenGrowth)
|
|
7077
|
-
return;
|
|
7260
|
+
return false;
|
|
7078
7261
|
}
|
|
7079
7262
|
const originalMessageCount = msgs.length;
|
|
7080
7263
|
const configuredKeepLast = this.config.getNum('context', 'keep_recent_messages') || 10;
|
|
7081
7264
|
if (msgs.length <= 1)
|
|
7082
|
-
return;
|
|
7265
|
+
return false;
|
|
7083
7266
|
// Reserve room for the one-time post-compression continuation anchor so
|
|
7084
7267
|
// adding it cannot push a near-limit request back over the target budget.
|
|
7085
7268
|
const continuationAnchorTokens = this.estimateContextTokens([this.postCompressionContinuationMessage()]);
|
|
@@ -7087,7 +7270,7 @@ class Agent {
|
|
|
7087
7270
|
const recent = this.recentContextSuffix(msgs, configuredKeepLast, recentBudget);
|
|
7088
7271
|
const recentStart = Math.max(0, msgs.length - recent.length);
|
|
7089
7272
|
if (recentStart <= 0)
|
|
7090
|
-
return;
|
|
7273
|
+
return false;
|
|
7091
7274
|
// The first history item is usually the first user task, not foundational
|
|
7092
7275
|
// context. Keeping it forever makes an old task more salient after every
|
|
7093
7276
|
// compaction. Foundational rules are rebuilt by buildSystemPrompt(); the
|
|
@@ -7096,7 +7279,7 @@ class Agent {
|
|
|
7096
7279
|
const currentInstruction = this.latestUserHistoryText(recent);
|
|
7097
7280
|
const compression = await this.buildCompressionSummary(middle, total, budget, provider, signal, compressionModel || this.activeModelName(), currentInstruction);
|
|
7098
7281
|
if (signal?.aborted)
|
|
7099
|
-
return;
|
|
7282
|
+
return false;
|
|
7100
7283
|
const compressed = [{
|
|
7101
7284
|
role: 'system',
|
|
7102
7285
|
content: compression.summary,
|
|
@@ -7123,6 +7306,7 @@ class Agent {
|
|
|
7123
7306
|
};
|
|
7124
7307
|
this.pushCompressionCacheEntry(compression.summary, middle, compression.model, compression.fallback);
|
|
7125
7308
|
this.persistCompressedHistory(compression.summary, recent.length, msgs);
|
|
7309
|
+
return true;
|
|
7126
7310
|
}
|
|
7127
7311
|
async buildCompressionSummary(middle, totalChars, budget, provider, signal, compressionModel, currentInstruction = '') {
|
|
7128
7312
|
const workspacePath = this.workspace.current?.path || this.rootPath;
|
|
@@ -8045,10 +8229,28 @@ class GoalStateImpl {
|
|
|
8045
8229
|
return s;
|
|
8046
8230
|
}
|
|
8047
8231
|
checkComplete(response) {
|
|
8048
|
-
const
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8232
|
+
const lines = String(response || '').replace(/\r\n?/g, '\n').split('\n');
|
|
8233
|
+
const completionMarkers = '(?:goal\\s+complete|objective\\s+achieved|task\\s+finished|all\\s+done|goal\\s+accomplished)';
|
|
8234
|
+
const explicitLinePatterns = [
|
|
8235
|
+
new RegExp(`^\\s*(?:[*#_~\\-]+\\s*)*(?:\\[\\s*)?${completionMarkers}(?:\\s*\\])?(?=\\s|[!.,:;]|$)`, 'i'),
|
|
8236
|
+
/^\s*(?:[*#_~\-]+\s*)*(?:i|we)\s+(?:have\s+)?(?:now\s+)?(?:fully\s+)?(?:completed|finished|achieved|accomplished)\s+(?:the\s+)?(?:goal|objective|task)\b/i,
|
|
8237
|
+
/^\s*(?:[*#_~\-]+\s*)*(?:the\s+)?(?:goal|objective|task)\s+(?:is|was)\s+(?:now\s+)?(?:complete|achieved|finished|accomplished)\b/i,
|
|
8238
|
+
];
|
|
8239
|
+
const completionContext = new RegExp(completionMarkers, 'i');
|
|
8240
|
+
const deferredBefore = new RegExp(`\\b(?:must|will|should|would|can|could)\\s+(?:still\\s+)?(?:contain(?:s|ed)?|include(?:s|d)?|say|state|use|write|appear)\\b.{0,180}${completionMarkers}`, 'i');
|
|
8241
|
+
const negatedBefore = new RegExp(`\\b(?:not|isn't|is not|never|don't|do not|won't|will not)\\b.{0,180}${completionMarkers}`, 'i');
|
|
8242
|
+
const pendingBefore = new RegExp(`\\b(?:one\\s+step\\s+remains|still\\s+required|one\\s+more\\s+(?:step|call|turn)|not\\s+(?:the\\s+)?final)\\b.{0,180}${completionMarkers}`, 'i');
|
|
8243
|
+
const deferredAfter = new RegExp(`${completionMarkers}.{0,180}\\b(?:not\\s+(?:the\\s+)?final|not\\s+yet|one\\s+more\\s+model\\s+call|next\\s+(?:call|step)|not\\s+done)\\b`, 'i');
|
|
8244
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
8245
|
+
const line = lines[index];
|
|
8246
|
+
if (!explicitLinePatterns.some(pattern => pattern.test(line)))
|
|
8247
|
+
continue;
|
|
8248
|
+
const context = lines.slice(Math.max(0, index - 2), Math.min(lines.length, index + 3)).join(' ');
|
|
8249
|
+
if (completionContext.test(context) && (deferredBefore.test(context) || negatedBefore.test(context) || pendingBefore.test(context) || deferredAfter.test(context)))
|
|
8250
|
+
continue;
|
|
8251
|
+
return true;
|
|
8252
|
+
}
|
|
8253
|
+
return false;
|
|
8052
8254
|
}
|
|
8053
8255
|
}
|
|
8054
8256
|
//# sourceMappingURL=agent.js.map
|
|
@@ -205,6 +205,9 @@ class ProviderRunError extends Error {
|
|
|
205
205
|
function kernelTurnFailed(agent, turn) {
|
|
206
206
|
return turn.stopReason === 'error' || agent.isLlmErrorText(turn.text);
|
|
207
207
|
}
|
|
208
|
+
function providerTurnIsEmpty(turn) {
|
|
209
|
+
return /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
|
|
210
|
+
}
|
|
208
211
|
function removeTrailingFailedAssistant(agent, messages) {
|
|
209
212
|
const last = messages[messages.length - 1];
|
|
210
213
|
if (last?.role !== 'assistant')
|
|
@@ -255,17 +258,40 @@ function normalizePublicProviderError(error, secrets = []) {
|
|
|
255
258
|
}
|
|
256
259
|
return raw.slice(0, 1_200);
|
|
257
260
|
}
|
|
261
|
+
function throwIfKernelAborted(signal) {
|
|
262
|
+
if (!signal?.aborted)
|
|
263
|
+
return;
|
|
264
|
+
const reason = signal.reason;
|
|
265
|
+
if (reason instanceof Error) {
|
|
266
|
+
reason.name = 'AbortError';
|
|
267
|
+
throw reason;
|
|
268
|
+
}
|
|
269
|
+
const error = new Error(reason ? String(reason) : 'Agent run aborted');
|
|
270
|
+
error.name = 'AbortError';
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
258
273
|
async function runAgentKernel(agent) {
|
|
259
274
|
const stopContextTimer = (0, performanceDiagnostics_1.performanceTimer)('context_prepare', { conversationId: agent.activeConversationId });
|
|
275
|
+
const processSignal = agent.activeProcessSignal();
|
|
276
|
+
if (processSignal?.aborted) {
|
|
277
|
+
stopContextTimer();
|
|
278
|
+
throwIfKernelAborted(processSignal);
|
|
279
|
+
}
|
|
260
280
|
if (!agent.engineModel()) {
|
|
281
|
+
const message = 'No LLM configured. Add provider in Settings > Models.';
|
|
261
282
|
agent.status = 'error';
|
|
262
283
|
agent.saveWorkspaceConversationState();
|
|
263
|
-
|
|
284
|
+
// A missing provider is a terminal run failure, not a visible assistant
|
|
285
|
+
// response. Throwing here lets Agent.process and ConversationKernel share
|
|
286
|
+
// the normal error finalization path, so GUI/TUI/CLI cannot turn this into
|
|
287
|
+
// a synthetic successful Build with an empty final summary.
|
|
288
|
+
throw new Error(message);
|
|
264
289
|
}
|
|
265
290
|
const [{ Agent: NativeAgent }, KernelStreamCompat] = await Promise.all([
|
|
266
291
|
import('./agentKernel/index.js'),
|
|
267
292
|
import('./agentKernel/stream-types.js'),
|
|
268
293
|
]);
|
|
294
|
+
throwIfKernelAborted(processSignal);
|
|
269
295
|
const toolProvisioning = new ToolProvisionSession([], []);
|
|
270
296
|
let activeToolSurfaceIdentity = '';
|
|
271
297
|
let activeToolSurfaceNotice = '';
|
|
@@ -297,6 +323,7 @@ async function runAgentKernel(agent) {
|
|
|
297
323
|
const initialToolSurface = refreshToolSurface(true);
|
|
298
324
|
const assembledContext = agent.assembleContextV2(initialToolSurface.systemPromptNotice);
|
|
299
325
|
const systemPrompt = assembledContext.text;
|
|
326
|
+
throwIfKernelAborted(processSignal);
|
|
300
327
|
let providerRequestCount = 0;
|
|
301
328
|
let bootstrappedCompressionAt = agent.lastCompression?.at || '';
|
|
302
329
|
stopContextTimer();
|
|
@@ -317,6 +344,28 @@ async function runAgentKernel(agent) {
|
|
|
317
344
|
kernel.state.tools = toKernelTools(agent, initialToolSurface.definitions, toolProvisioning);
|
|
318
345
|
kernel.state.messages = toKernelMessages(agent);
|
|
319
346
|
agent.attachAgentKernelRuntime(kernel);
|
|
347
|
+
let detachProcessAbort = () => { };
|
|
348
|
+
if (processSignal) {
|
|
349
|
+
const abortKernel = () => kernel.abort();
|
|
350
|
+
if (processSignal.aborted) {
|
|
351
|
+
kernel.abort();
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
processSignal.addEventListener('abort', abortKernel, { once: true });
|
|
355
|
+
detachProcessAbort = () => processSignal.removeEventListener('abort', abortKernel);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
// abort() cannot cancel a NativeAgent before its internal run exists. The
|
|
360
|
+
// explicit check closes the remaining handoff window between attaching the
|
|
361
|
+
// kernel and entering kernel.prompt().
|
|
362
|
+
throwIfKernelAborted(processSignal);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
detachProcessAbort();
|
|
366
|
+
agent.attachAgentKernelRuntime(null);
|
|
367
|
+
throw error;
|
|
368
|
+
}
|
|
320
369
|
const tokens = [];
|
|
321
370
|
const runOnce = async (promptMessages, appendPromptToAgentHistory) => {
|
|
322
371
|
let lastAssistant = null;
|
|
@@ -338,10 +387,14 @@ async function runAgentKernel(agent) {
|
|
|
338
387
|
}
|
|
339
388
|
await kernel.prompt(promptMessages);
|
|
340
389
|
const assistant = lastAssistant;
|
|
390
|
+
const text = assistant ? KernelMessageText(assistant) : '';
|
|
391
|
+
const hasToolCall = !!assistant?.content?.some(content => content.type === 'toolCall');
|
|
392
|
+
const emptyResponse = !assistant
|
|
393
|
+
|| (!text.trim() && !hasToolCall && String(assistant?.stopReason || '') !== 'aborted');
|
|
341
394
|
return {
|
|
342
|
-
text:
|
|
395
|
+
text: emptyResponse ? '[Error] Provider returned an empty response.' : text,
|
|
343
396
|
stopReason: String(assistant?.stopReason || ''),
|
|
344
|
-
errorMessage: String(assistant?.errorMessage || ''),
|
|
397
|
+
errorMessage: String(assistant?.errorMessage || (emptyResponse ? 'Provider returned an empty response.' : '')),
|
|
345
398
|
};
|
|
346
399
|
}
|
|
347
400
|
finally {
|
|
@@ -380,6 +433,16 @@ async function runAgentKernel(agent) {
|
|
|
380
433
|
if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some(t => t.text?.includes('[Model fallback]'))) {
|
|
381
434
|
tokens.unshift({ type: 'text', text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
|
|
382
435
|
}
|
|
436
|
+
let emptyResponseRetries = 0;
|
|
437
|
+
while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
|
|
438
|
+
removeTrailingFailedAssistant(agent, kernel.state.messages);
|
|
439
|
+
emptyResponseRetries += 1;
|
|
440
|
+
const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
|
|
441
|
+
tokens.push({ type: 'text', text: notice });
|
|
442
|
+
agent.recordWorkStatus(notice);
|
|
443
|
+
await agent.waitForPlannedRouteRetry();
|
|
444
|
+
lastTurn = await runWithCompressionResume([], false);
|
|
445
|
+
}
|
|
383
446
|
let routeRetries = 0;
|
|
384
447
|
while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
|
|
385
448
|
const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
|
|
@@ -416,6 +479,7 @@ async function runAgentKernel(agent) {
|
|
|
416
479
|
}
|
|
417
480
|
}
|
|
418
481
|
finally {
|
|
482
|
+
detachProcessAbort();
|
|
419
483
|
agent.attachAgentKernelRuntime(null);
|
|
420
484
|
}
|
|
421
485
|
agent.status = 'idle';
|
|
@@ -597,14 +661,12 @@ async function transformContext(agent, messages, signal) {
|
|
|
597
661
|
// projection so the internal broker call/result and its compact catalog can
|
|
598
662
|
// never be written into conversation state or revived after a reload.
|
|
599
663
|
const newmarkMessages = publicHistoryFromKernelMessages(messages);
|
|
600
|
-
const beforeCompression = JSON.stringify(newmarkMessages);
|
|
601
664
|
const compressionAt = agent.lastCompression?.at || '';
|
|
602
|
-
await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
|
|
665
|
+
let compressed = await agent.maybeCompress(newmarkMessages, provider, processSignal, compressionModel);
|
|
603
666
|
if (processSignal?.aborted)
|
|
604
667
|
return messages;
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
|
|
668
|
+
if (compressed && agent.estimateContextTokens(newmarkMessages) >= Math.floor(agent.contextWindow(compressionModel).maxTokens * 0.82)) {
|
|
669
|
+
compressed = (await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true)) || compressed;
|
|
608
670
|
}
|
|
609
671
|
// Hard safety net: even a conservative worst-case token estimate must never
|
|
610
672
|
// leave a request that could exceed the model's context window. The improved
|
|
@@ -614,9 +676,9 @@ async function transformContext(agent, messages, signal) {
|
|
|
614
676
|
const windowMax = agent.contextWindow(compressionModel).maxTokens;
|
|
615
677
|
const conservativeTokens = agent.estimateContextTokens(newmarkMessages);
|
|
616
678
|
if (conservativeTokens >= Math.floor(windowMax * 0.9) && !processSignal?.aborted) {
|
|
617
|
-
await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true);
|
|
679
|
+
compressed = (await agent.maybeCompress(newmarkMessages, null, processSignal, compressionModel, true)) || compressed;
|
|
618
680
|
}
|
|
619
|
-
if (
|
|
681
|
+
if (!compressed)
|
|
620
682
|
return messages;
|
|
621
683
|
const durableMessages = toKernelMessagesFromHistory(newmarkMessages, agent);
|
|
622
684
|
if (agent.lastCompression?.at && agent.lastCompression.at !== compressionAt) {
|