newmark-agent 0.5.12 → 0.5.13
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 +75276 -26574
- package/dist/core/agent.d.ts +42 -17
- package/dist/core/agent.js +297 -64
- package/dist/core/browserUse.d.ts +7 -0
- package/dist/core/browserUse.js +24 -7
- package/dist/core/displayImages.d.ts +10 -0
- package/dist/core/displayImages.js +31 -8
- package/dist/core/electronBrowserUseHost.d.ts +4 -0
- package/dist/core/electronBrowserUseHost.js +32 -6
- package/dist/core/electronUtilityAgentClient.d.ts +1 -0
- package/dist/core/electronUtilityAgentClient.js +50 -3
- package/dist/core/runtimeDiagnostics.d.ts +18 -0
- package/dist/core/runtimeDiagnostics.js +89 -0
- package/dist/core/runtimeLifecycle.d.ts +7 -1
- package/dist/core/runtimeLifecycle.js +29 -0
- package/dist/core/searchMcpPool.d.ts +80 -0
- package/dist/core/searchMcpPool.js +419 -0
- package/dist/main.js +72 -1
- package/dist/server.js +35 -0
- package/dist/tools/index.d.ts +17 -1
- package/dist/tools/index.js +203 -64
- package/dist/tui/src/data.js +2 -2
- package/dist/ui/index.html +80 -17
- package/dist/wsl-agent-host.bundle.cjs +75283 -26581
- package/package.json +11 -2
package/dist/core/agent.js
CHANGED
|
@@ -111,12 +111,12 @@ let CORE_SYSTEM_PROMPT = `You are Newmark Agent, a powerful AI coding assistant
|
|
|
111
111
|
- delete_file: Delete ONE file at a time under Agent supervision (absolute path; refuses directories and wildcards)
|
|
112
112
|
- glob: Find files by pattern
|
|
113
113
|
- grep: Search file contents with regex
|
|
114
|
-
- web_search: Search the
|
|
114
|
+
- web_search: Search through the configured search-only MCP pool, then fall back to Bing HTTP and finally DuckDuckGo HTTP
|
|
115
115
|
- web_fetch: Fetch and extract content from URLs
|
|
116
116
|
- browser_use: Preferred native built-in-browser workflow. Observe first, then use the returned page generation, observation id, and opaque refs for click/type/select/scroll/key/navigation/wait/extraction. Recognition order is enforced as rendered DOM text first, then an ephemeral screenshot sent to a validated vision model when text is unavailable, and only then local OCR. A successful action receipt is enough to continue the Build; do not wait for the browser session or window to close. Every receipt is bound to the current workspace/conversation runtime and actor. Stale page capabilities are rejected; observe again to recover.
|
|
117
117
|
- browser_open/browser_snapshot/browser_click/browser_type/browser_eval/browser_back/browser_forward/browser_reload/browser_cdp: Legacy and expert Chromium controls. Prefer browser_use for normal interactive work; raw eval/CDP remain advanced escape hatches.
|
|
118
118
|
- computer_use: Native desktop Computer Use control for full desktop or app-scoped observe/move/click/scroll/type/key/wait against Windows desktop applications. A successful takeover_start receipt means the persistent control surface started and the Build may continue immediately; do not wait for takeover_stop or closure before taking the next step. Use takeover_stop when control is no longer needed. Use app_list/app_observe/app_activate/app_click/app_scroll/app_type/app_key when the task can be scoped to a visible taskbar application by title, process name, PID, or window handle; this narrows screenshots and actions to that application. Use observe/app_observe first, reason over returned screenshot plus UI Automation objects. If the model supports vision, Newmark sends the screenshot image and UI object tree together in the same tool-result context; use both for stable decisions. Prefer target_id from perception.scene_summary.high_priority_objects or perception.objects for move/click/scroll when available; fall back to exact coordinates only when necessary.
|
|
119
|
-
- image_inspect:
|
|
119
|
+
- image_inspect: Inspect durable user-submitted visual attachments, or use action=inspect with a workspace-relative PNG/JPEG path to send that file to the current validated vision model. Query source_info before cropping submitted images when dimensions are unknown. Workspace observations and derived crops are current-turn-only; image bytes are never saved in tool text or durable history.
|
|
120
120
|
- image_display: Present one workspace PNG/JPEG to the user. Use it for diagrams or other visual evidence that materially helps explain the current Build; pass a workspace-relative path and optional caption hint. When validated vision is active, Newmark inspects the actual image and generates the displayed descriptive title; otherwise the caption or filename is used as fallback.
|
|
121
121
|
- ocr_read: LAST-RESORT, approximate Simplified-Chinese/English OCR only. Never call it before normal text extraction and validated vision input. When it returns text, use its Agent repair prompt to conservatively correct likely substitutions, spacing, and line breaks from surrounding context; never invent unsupported content.
|
|
122
122
|
- task: Create a subagent for parallel work
|
|
@@ -226,6 +226,13 @@ class Agent {
|
|
|
226
226
|
branchMailbox = [];
|
|
227
227
|
nextBranchMessageSequence = 1;
|
|
228
228
|
branchCommunicationEnabled = false;
|
|
229
|
+
/**
|
|
230
|
+
* Title/model availability is a hard gate for the first formal response.
|
|
231
|
+
* These values mirror the current conversation's persisted gate state so a
|
|
232
|
+
* failed first attempt cannot be bypassed by a second ordinary user send.
|
|
233
|
+
*/
|
|
234
|
+
titleRequestMessageId = '';
|
|
235
|
+
firstAgentResponseStarted = false;
|
|
229
236
|
compressionArchiveCountCache = null;
|
|
230
237
|
nextCompressionCacheId = 1;
|
|
231
238
|
compressionHistoryArchive;
|
|
@@ -1663,6 +1670,48 @@ class Agent {
|
|
|
1663
1670
|
return { runs: normalized, changed: false };
|
|
1664
1671
|
return { runs: normalized, changed };
|
|
1665
1672
|
}
|
|
1673
|
+
/** Close tool calls left without results by a hard worker exit. */
|
|
1674
|
+
repairDanglingToolCalls(messages) {
|
|
1675
|
+
const repaired = [];
|
|
1676
|
+
const pending = new Map();
|
|
1677
|
+
let changed = false;
|
|
1678
|
+
const settlePending = () => {
|
|
1679
|
+
for (const [id, name] of pending) {
|
|
1680
|
+
repaired.push({
|
|
1681
|
+
role: 'tool', tool_call_id: id, name,
|
|
1682
|
+
content: JSON.stringify({
|
|
1683
|
+
ok: false, code: 'runtime_interrupted', recoverable: true,
|
|
1684
|
+
error: 'The runtime exited before this tool returned. Retry the tool if it is still needed.',
|
|
1685
|
+
}),
|
|
1686
|
+
});
|
|
1687
|
+
changed = true;
|
|
1688
|
+
}
|
|
1689
|
+
pending.clear();
|
|
1690
|
+
};
|
|
1691
|
+
for (const message of messages) {
|
|
1692
|
+
const role = String(message.role || '');
|
|
1693
|
+
const toolCallId = String(message.tool_call_id || '');
|
|
1694
|
+
if (pending.size && role !== 'tool')
|
|
1695
|
+
settlePending();
|
|
1696
|
+
repaired.push({ ...message });
|
|
1697
|
+
if (role === 'assistant' && Array.isArray(message.tool_calls)) {
|
|
1698
|
+
for (const call of message.tool_calls) {
|
|
1699
|
+
const id = String(call.id || '');
|
|
1700
|
+
const fn = call.function && typeof call.function === 'object' ? call.function : {};
|
|
1701
|
+
if (id)
|
|
1702
|
+
pending.set(id, String(fn.name || 'tool'));
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
else if (role === 'tool' && toolCallId)
|
|
1706
|
+
pending.delete(toolCallId);
|
|
1707
|
+
}
|
|
1708
|
+
if (pending.size)
|
|
1709
|
+
settlePending();
|
|
1710
|
+
return { messages: repaired, changed };
|
|
1711
|
+
}
|
|
1712
|
+
repairDanglingToolCallsForTest(messages) {
|
|
1713
|
+
return this.repairDanglingToolCalls(messages);
|
|
1714
|
+
}
|
|
1666
1715
|
pauseFlowAfterUnexpectedExit() {
|
|
1667
1716
|
if (this.mode !== 'flow' || !this.flow?.name)
|
|
1668
1717
|
return false;
|
|
@@ -3575,10 +3624,10 @@ class Agent {
|
|
|
3575
3624
|
return { id, title: resolvedTitle };
|
|
3576
3625
|
}
|
|
3577
3626
|
/**
|
|
3578
|
-
*
|
|
3579
|
-
*
|
|
3580
|
-
*
|
|
3581
|
-
*
|
|
3627
|
+
* Compatibility query for callers that still expose the rename affordance.
|
|
3628
|
+
* Automatic naming itself is a hard pre-response gate: until the persisted
|
|
3629
|
+
* firstAgentResponseStarted flag is true, every ordinary send must finish a
|
|
3630
|
+
* title probe for the first persisted user message before formal execution.
|
|
3582
3631
|
*/
|
|
3583
3632
|
shouldPromptConversationRename() {
|
|
3584
3633
|
if (this.conversationBuildHistory(1).length > 0)
|
|
@@ -3588,14 +3637,16 @@ class Agent {
|
|
|
3588
3637
|
if (!stateKey)
|
|
3589
3638
|
return false;
|
|
3590
3639
|
const entry = this.readStoredConversationState().conversations?.[stateKey];
|
|
3640
|
+
if (this.restoredFirstAgentResponseStarted(entry))
|
|
3641
|
+
return false;
|
|
3591
3642
|
const priorTitle = entry?.title;
|
|
3592
3643
|
const messages = entry?.chatMessages || this.chatMessages;
|
|
3593
3644
|
return this.isGeneratedConversationTitle(priorTitle, conversationId, messages);
|
|
3594
3645
|
}
|
|
3595
3646
|
/**
|
|
3596
|
-
*
|
|
3597
|
-
*
|
|
3598
|
-
*
|
|
3647
|
+
* Legacy explicit-rename sanitizer retained for compatibility tests only.
|
|
3648
|
+
* Automatic naming must never use an Agent/Build summary as a fallback;
|
|
3649
|
+
* the independent first-input title probe is the sole automatic source.
|
|
3599
3650
|
*/
|
|
3600
3651
|
deriveConversationTitleFromSummary(summary) {
|
|
3601
3652
|
const clean = this.sanitizeAssistantOutput(summary || '').replace(/\r/g, '');
|
|
@@ -3638,27 +3689,28 @@ class Agent {
|
|
|
3638
3689
|
return title.length >= 2 ? title : '';
|
|
3639
3690
|
}
|
|
3640
3691
|
/**
|
|
3641
|
-
*
|
|
3642
|
-
*
|
|
3643
|
-
*
|
|
3692
|
+
* Run the independent, tool-free title/model-availability probe. It shares
|
|
3693
|
+
* the frozen deployment selected for this send, but no main Agent history or
|
|
3694
|
+
* tool schema. An empty result keeps the formal response hard-blocked.
|
|
3644
3695
|
*/
|
|
3645
|
-
async deriveConversationTitleFromProvider(
|
|
3646
|
-
const provider = this.engineModel();
|
|
3647
|
-
const modelName = this.activeModelName();
|
|
3648
|
-
if (!provider || !modelName)
|
|
3649
|
-
return '';
|
|
3696
|
+
async deriveConversationTitleFromProvider(firstUserInput, provider, modelName, intelligence, signal) {
|
|
3650
3697
|
const system = [
|
|
3651
3698
|
'You are a conversation title generator.',
|
|
3652
|
-
'
|
|
3699
|
+
'Summarize the user intent and return ONLY a short, concrete noun-phrase title for the conversation.',
|
|
3700
|
+
'Do not quote, repeat, or truncate the user input as the title.',
|
|
3653
3701
|
'No preamble, no explanation, no Markdown, no quotes, no trailing punctuation.',
|
|
3654
3702
|
].join('\n');
|
|
3655
|
-
const prompt = `
|
|
3703
|
+
const prompt = `First user input:\n${String(firstUserInput || '').slice(0, 4000)}\n\nConversation title (a few words):`;
|
|
3656
3704
|
const controller = new AbortController();
|
|
3657
3705
|
const timer = setTimeout(() => controller.abort(new Error('conversation rename timed out')), 15000);
|
|
3658
3706
|
try {
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3707
|
+
if (signal?.aborted)
|
|
3708
|
+
return '';
|
|
3709
|
+
const { temperature, reasoningEffort } = provider.intelligenceConfig(intelligence);
|
|
3710
|
+
const generated = await provider.chat(modelName, [{ role: 'user', content: prompt }], system, temperature, 64, controller.signal, reasoningEffort);
|
|
3711
|
+
const title = this.normalizeConversationRenameTitle(generated);
|
|
3712
|
+
const source = String(firstUserInput || '').replace(/\s+/g, ' ').trim();
|
|
3713
|
+
return title && title !== source ? title : '';
|
|
3662
3714
|
}
|
|
3663
3715
|
catch {
|
|
3664
3716
|
return '';
|
|
@@ -3667,34 +3719,134 @@ class Agent {
|
|
|
3667
3719
|
clearTimeout(timer);
|
|
3668
3720
|
}
|
|
3669
3721
|
}
|
|
3722
|
+
/** Legacy completion hook retained as a no-op; naming is pre-response only. */
|
|
3723
|
+
maybeAutoRenameConversationFromRun(_run) {
|
|
3724
|
+
// Compatibility no-op. Automatic naming is a hard gate on the first
|
|
3725
|
+
// persisted user input and never depends on Agent output or completion.
|
|
3726
|
+
}
|
|
3727
|
+
async startFirstInputConversationTitle(messageId, firstUserInput, provider, modelName, intelligence, signal) {
|
|
3728
|
+
const conversationId = this.activeConversationId || 'default';
|
|
3729
|
+
const workspace = this.workspace.current;
|
|
3730
|
+
const stateKey = this.workspaceConversationStateKeyFor(conversationId, workspace);
|
|
3731
|
+
// 标题探测失败(空响应、响应重复用户输入、临时传输错误)使用
|
|
3732
|
+
// 0s → 1s → 2s → 4s → 8s 的 5 级退避,全部失败才阻断正式首轮。
|
|
3733
|
+
const maxAttempts = 5;
|
|
3734
|
+
const retryDelaysMs = [0, 1000, 2000, 4000, 8000];
|
|
3735
|
+
if (!messageId || !firstUserInput.trim())
|
|
3736
|
+
return false;
|
|
3737
|
+
if (!stateKey) {
|
|
3738
|
+
// Pure Agent/CLI mode has no workspace conversation file to rename, but
|
|
3739
|
+
// still uses the title request as the required first model-availability
|
|
3740
|
+
// probe before starting the formal response.
|
|
3741
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
3742
|
+
if (await this.deriveConversationTitleFromProvider(firstUserInput, provider, modelName, intelligence, signal))
|
|
3743
|
+
return true;
|
|
3744
|
+
if (signal?.aborted)
|
|
3745
|
+
return false;
|
|
3746
|
+
if (attempt < maxAttempts - 1)
|
|
3747
|
+
await new Promise(resolve => setTimeout(resolve, retryDelaysMs[attempt]));
|
|
3748
|
+
}
|
|
3749
|
+
return false;
|
|
3750
|
+
}
|
|
3751
|
+
const stored = this.readStoredConversationState(workspace);
|
|
3752
|
+
const entry = stored.conversations?.[stateKey];
|
|
3753
|
+
if (!entry || (entry.titleRequestMessageId && entry.titleRequestMessageId !== messageId))
|
|
3754
|
+
return false;
|
|
3755
|
+
const firstUser = (entry.chatMessages || []).find(message => message.role === 'user');
|
|
3756
|
+
if (!firstUser || String(firstUser.messageId || '') !== messageId)
|
|
3757
|
+
return false;
|
|
3758
|
+
entry.titleRequestMessageId = messageId;
|
|
3759
|
+
this.writeStoredConversationState(stored, workspace);
|
|
3760
|
+
this.titleRequestMessageId = messageId;
|
|
3761
|
+
const memoryKey = this.workspaceConversationKey();
|
|
3762
|
+
const memory = memoryKey ? this.workspaceConversations.get(memoryKey) : undefined;
|
|
3763
|
+
if (memory)
|
|
3764
|
+
memory.titleRequestMessageId = messageId;
|
|
3765
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
3766
|
+
const title = await this.deriveConversationTitleFromProvider(firstUserInput, provider, modelName, intelligence, signal);
|
|
3767
|
+
if (title) {
|
|
3768
|
+
const latest = this.readStoredConversationState(workspace);
|
|
3769
|
+
const current = latest.conversations?.[stateKey];
|
|
3770
|
+
const stillFirst = (current?.chatMessages || []).find(message => message.role === 'user');
|
|
3771
|
+
if (!current || current.titleRequestMessageId !== messageId || String(stillFirst?.messageId || '') !== messageId)
|
|
3772
|
+
return false;
|
|
3773
|
+
if (!this.isGeneratedConversationTitle(current.title, conversationId, current.chatMessages || []))
|
|
3774
|
+
return true;
|
|
3775
|
+
current.title = title;
|
|
3776
|
+
current.updatedAt = this.nowIso();
|
|
3777
|
+
this.writeStoredConversationState(latest, workspace);
|
|
3778
|
+
return true;
|
|
3779
|
+
}
|
|
3780
|
+
if (signal?.aborted)
|
|
3781
|
+
return false;
|
|
3782
|
+
if (attempt < maxAttempts - 1)
|
|
3783
|
+
await new Promise(resolve => setTimeout(resolve, retryDelaysMs[attempt]));
|
|
3784
|
+
}
|
|
3785
|
+
return false;
|
|
3786
|
+
}
|
|
3787
|
+
firstPersistedUserForTitleGate() {
|
|
3788
|
+
const firstUser = this.chatMessages.find(message => message.role === 'user');
|
|
3789
|
+
const messageId = String(firstUser?.messageId || '').trim();
|
|
3790
|
+
if (!firstUser || !messageId)
|
|
3791
|
+
return null;
|
|
3792
|
+
const attachmentCount = firstUser.attachments?.length || 0;
|
|
3793
|
+
const input = String(firstUser.content || '').trim()
|
|
3794
|
+
|| (attachmentCount ? `The user submitted ${attachmentCount} image attachment${attachmentCount === 1 ? '' : 's'}.` : '');
|
|
3795
|
+
return input ? { messageId, input } : null;
|
|
3796
|
+
}
|
|
3670
3797
|
/**
|
|
3671
|
-
*
|
|
3672
|
-
*
|
|
3673
|
-
*
|
|
3674
|
-
*
|
|
3675
|
-
* 在首轮 prompt 注入一次性 tool-call 指令。
|
|
3798
|
+
* Conversations saved before the durable title gate existed have no explicit
|
|
3799
|
+
* flag. Prior assistant history or any persisted WorkRun proves that their
|
|
3800
|
+
* first formal response already started. An explicit false is authoritative:
|
|
3801
|
+
* it represents a failed title probe that must remain blocked across reloads.
|
|
3676
3802
|
*/
|
|
3677
|
-
|
|
3678
|
-
if (
|
|
3679
|
-
return;
|
|
3680
|
-
if (
|
|
3681
|
-
return;
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
.
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3803
|
+
restoredFirstAgentResponseStarted(entry) {
|
|
3804
|
+
if (!entry)
|
|
3805
|
+
return false;
|
|
3806
|
+
if (typeof entry.firstAgentResponseStarted === 'boolean')
|
|
3807
|
+
return entry.firstAgentResponseStarted;
|
|
3808
|
+
return (entry.workRuns || []).length > 0
|
|
3809
|
+
|| (entry.chatMessages || []).some(message => message.role === 'assistant')
|
|
3810
|
+
|| (entry.history || []).some(message => String(message.role || '') === 'assistant');
|
|
3811
|
+
}
|
|
3812
|
+
getConversationTitleGateState() {
|
|
3813
|
+
return {
|
|
3814
|
+
titleRequestMessageId: this.titleRequestMessageId,
|
|
3815
|
+
firstAgentResponseStarted: this.firstAgentResponseStarted,
|
|
3816
|
+
};
|
|
3817
|
+
}
|
|
3818
|
+
markFirstAgentResponseStarted(messageId) {
|
|
3819
|
+
const normalizedMessageId = String(messageId || '').trim();
|
|
3820
|
+
if (!normalizedMessageId)
|
|
3821
|
+
return false;
|
|
3822
|
+
const workspace = this.workspace.current;
|
|
3823
|
+
const stateKey = this.workspaceConversationStateKeyFor(this.activeConversationId || 'default', workspace);
|
|
3824
|
+
if (!stateKey) {
|
|
3825
|
+
this.titleRequestMessageId = normalizedMessageId;
|
|
3826
|
+
this.firstAgentResponseStarted = true;
|
|
3827
|
+
return true;
|
|
3828
|
+
}
|
|
3829
|
+
const marked = this.mutateStoredConversationState(workspace, latest => {
|
|
3830
|
+
const current = latest.conversations?.[stateKey];
|
|
3831
|
+
const firstUser = (current?.chatMessages || []).find(message => message.role === 'user');
|
|
3832
|
+
if (!current || current.titleRequestMessageId !== normalizedMessageId
|
|
3833
|
+
|| String(firstUser?.messageId || '') !== normalizedMessageId)
|
|
3834
|
+
return latest;
|
|
3835
|
+
current.firstAgentResponseStarted = true;
|
|
3836
|
+
current.updatedAt = this.nowIso();
|
|
3837
|
+
return latest;
|
|
3838
|
+
}, latest => latest.conversations?.[stateKey]?.firstAgentResponseStarted === true);
|
|
3839
|
+
if (!marked)
|
|
3840
|
+
return false;
|
|
3841
|
+
this.titleRequestMessageId = normalizedMessageId;
|
|
3842
|
+
this.firstAgentResponseStarted = true;
|
|
3843
|
+
const key = this.workspaceConversationKey();
|
|
3844
|
+
const memory = key ? this.workspaceConversations.get(key) : undefined;
|
|
3845
|
+
if (memory) {
|
|
3846
|
+
memory.titleRequestMessageId = normalizedMessageId;
|
|
3847
|
+
memory.firstAgentResponseStarted = true;
|
|
3848
|
+
}
|
|
3849
|
+
return true;
|
|
3698
3850
|
}
|
|
3699
3851
|
reorderConversations(ids) {
|
|
3700
3852
|
const prefix = this.workspaceConversationPrefix() || '';
|
|
@@ -3764,6 +3916,8 @@ class Agent {
|
|
|
3764
3916
|
return true;
|
|
3765
3917
|
if (cleanTitle === 'Default conversation')
|
|
3766
3918
|
return true;
|
|
3919
|
+
if (/^(?:New (?:chat|conversation)|新对话)(?:\s+\d+)?$/i.test(cleanTitle))
|
|
3920
|
+
return true;
|
|
3767
3921
|
return false;
|
|
3768
3922
|
}
|
|
3769
3923
|
sanitizeAssistantStreamingOutput(text) {
|
|
@@ -3845,6 +3999,8 @@ class Agent {
|
|
|
3845
3999
|
inputMode: this.inputMode,
|
|
3846
4000
|
mode: this.mode,
|
|
3847
4001
|
goal: this.serializeGoal(),
|
|
4002
|
+
titleRequestMessageId: this.titleRequestMessageId || undefined,
|
|
4003
|
+
firstAgentResponseStarted: this.firstAgentResponseStarted,
|
|
3848
4004
|
runtimeOwnerId: runtimeOwner.runtimeOwnerId,
|
|
3849
4005
|
runtimeOwnerPid: runtimeOwner.runtimeOwnerPid,
|
|
3850
4006
|
runtimeLifecycleRole: runtimeOwner.runtimeLifecycleRole,
|
|
@@ -3859,9 +4015,7 @@ class Agent {
|
|
|
3859
4015
|
const priorTitle = stored.conversations[stateKey]?.title;
|
|
3860
4016
|
const conversationId = this.activeConversationId || 'default';
|
|
3861
4017
|
const derivedTitle = this.titleFromMessages(this.chatMessages, conversationId);
|
|
3862
|
-
const title =
|
|
3863
|
-
? derivedTitle
|
|
3864
|
-
: (priorTitle || derivedTitle);
|
|
4018
|
+
const title = priorTitle || derivedTitle;
|
|
3865
4019
|
const nextEntry = {
|
|
3866
4020
|
...(stored.conversations[stateKey] || {}),
|
|
3867
4021
|
title,
|
|
@@ -3880,6 +4034,8 @@ class Agent {
|
|
|
3880
4034
|
inputMode: this.inputMode,
|
|
3881
4035
|
mode: this.mode,
|
|
3882
4036
|
goal: this.serializeGoal(),
|
|
4037
|
+
titleRequestMessageId: this.titleRequestMessageId || undefined,
|
|
4038
|
+
firstAgentResponseStarted: this.firstAgentResponseStarted,
|
|
3883
4039
|
runtimeOwnerId: runtimeOwner.runtimeOwnerId,
|
|
3884
4040
|
runtimeOwnerPid: runtimeOwner.runtimeOwnerPid,
|
|
3885
4041
|
runtimeLifecycleRole: runtimeOwner.runtimeLifecycleRole,
|
|
@@ -3910,6 +4066,8 @@ class Agent {
|
|
|
3910
4066
|
this.continuations = [];
|
|
3911
4067
|
this.mode = 'build';
|
|
3912
4068
|
this.goal = null;
|
|
4069
|
+
this.titleRequestMessageId = '';
|
|
4070
|
+
this.firstAgentResponseStarted = false;
|
|
3913
4071
|
this.flow = null;
|
|
3914
4072
|
this.flowPc = 0;
|
|
3915
4073
|
this.status = 'idle';
|
|
@@ -3936,6 +4094,8 @@ class Agent {
|
|
|
3936
4094
|
this.inputMode = this.defaultInputMode();
|
|
3937
4095
|
this.mode = saved.mode || 'build';
|
|
3938
4096
|
this.goal = this.restoreGoal(saved.goal);
|
|
4097
|
+
this.titleRequestMessageId = String(saved.titleRequestMessageId || '');
|
|
4098
|
+
this.firstAgentResponseStarted = this.restoredFirstAgentResponseStarted(saved);
|
|
3939
4099
|
this.status = this.restoreStatusFromWorkRuns(saved.goal);
|
|
3940
4100
|
this.recoverMissingTerminalBuildOverviews();
|
|
3941
4101
|
this.activeWorkRunId = this.workRuns.find(run => run.status === 'running')?.runId || '';
|
|
@@ -3944,7 +4104,8 @@ class Agent {
|
|
|
3944
4104
|
const stored = this.readStoredConversationState();
|
|
3945
4105
|
const stateKey = this.workspaceConversationStateKey();
|
|
3946
4106
|
const persisted = stateKey && stored.conversations ? stored.conversations[stateKey] : null;
|
|
3947
|
-
|
|
4107
|
+
const repairedHistory = this.repairDanglingToolCalls(persisted?.history ? [...persisted.history] : []);
|
|
4108
|
+
this.history = repairedHistory.messages;
|
|
3948
4109
|
this.compressionCache = persisted?.compressionCache ? persisted.compressionCache.map(entry => ({ ...entry, messages: [...entry.messages] })) : [];
|
|
3949
4110
|
this.nextCompressionCacheId = Math.max(1, ...this.compressionCache.map(entry => Number(entry.id.replace(/^ctx-cache-/, '')) || 0)) + 1;
|
|
3950
4111
|
this.branchMailbox = (persisted?.branchMailbox || []).map(message => ({ ...message }));
|
|
@@ -3961,6 +4122,8 @@ class Agent {
|
|
|
3961
4122
|
this.inputMode = this.defaultInputMode();
|
|
3962
4123
|
this.mode = persisted?.mode || 'build';
|
|
3963
4124
|
this.goal = this.restoreGoal(persisted?.goal);
|
|
4125
|
+
this.titleRequestMessageId = String(persisted?.titleRequestMessageId || '');
|
|
4126
|
+
this.firstAgentResponseStarted = this.restoredFirstAgentResponseStarted(persisted);
|
|
3964
4127
|
const persistedRuntimeOwnerPid = Math.floor(Number(persisted?.runtimeOwnerPid) || 0);
|
|
3965
4128
|
const persistedRuntimeOwnerKnown = persistedRuntimeOwnerPid > 0;
|
|
3966
4129
|
const persistedRuntimeOwnerAlive = persistedRuntimeOwnerKnown && (0, runtimeLifecycle_1.isRuntimeProcessAlive)(persistedRuntimeOwnerPid);
|
|
@@ -3982,7 +4145,7 @@ class Agent {
|
|
|
3982
4145
|
}
|
|
3983
4146
|
}
|
|
3984
4147
|
this.recoverMissingTerminalBuildOverviews();
|
|
3985
|
-
const recoveryApplied = recoveredWorkRuns.changed || runtimeOwnerLost || goalPausedByRecovery || flowPausedByRecovery;
|
|
4148
|
+
const recoveryApplied = repairedHistory.changed || recoveredWorkRuns.changed || runtimeOwnerLost || goalPausedByRecovery || flowPausedByRecovery;
|
|
3986
4149
|
const recoveryAt = recoveryApplied ? this.nowIso() : persisted?.updatedAt;
|
|
3987
4150
|
this.workspaceConversations.set(key, {
|
|
3988
4151
|
chatMessages: [...this.chatMessages],
|
|
@@ -4000,6 +4163,8 @@ class Agent {
|
|
|
4000
4163
|
inputMode: this.defaultInputMode(),
|
|
4001
4164
|
mode: this.mode,
|
|
4002
4165
|
goal: this.serializeGoal(),
|
|
4166
|
+
titleRequestMessageId: this.titleRequestMessageId || undefined,
|
|
4167
|
+
firstAgentResponseStarted: this.firstAgentResponseStarted,
|
|
4003
4168
|
runtimeOwnerId: this.activeConversationRuntimeOwner().runtimeOwnerId,
|
|
4004
4169
|
runtimeOwnerPid: this.activeConversationRuntimeOwner().runtimeOwnerPid,
|
|
4005
4170
|
runtimeLifecycleRole: this.activeConversationRuntimeOwner().runtimeLifecycleRole,
|
|
@@ -4010,6 +4175,7 @@ class Agent {
|
|
|
4010
4175
|
recoveredStored.conversations = recoveredStored.conversations || {};
|
|
4011
4176
|
recoveredStored.conversations[stateKey] = {
|
|
4012
4177
|
...(recoveredStored.conversations[stateKey] || persisted),
|
|
4178
|
+
history: [...this.history],
|
|
4013
4179
|
workRuns: this.normalizeWorkRuns(this.workRuns),
|
|
4014
4180
|
flowSelection: this.currentConversationFlowSelection(),
|
|
4015
4181
|
mode: this.mode,
|
|
@@ -5221,6 +5387,17 @@ class Agent {
|
|
|
5221
5387
|
const continuations = this.normalizeContinuations(source.continuations || this.getConversationSnapshot(clean).continuations);
|
|
5222
5388
|
const normalizedChatMessages = this.normalizeConversationChatMessages(source.chatMessages, source.history);
|
|
5223
5389
|
const updatedAt = new Date().toISOString();
|
|
5390
|
+
const stateKey = this.workspaceConversationStateKey(clean);
|
|
5391
|
+
const stored = this.readStoredConversationState(ws);
|
|
5392
|
+
stored.conversations = stored.conversations || {};
|
|
5393
|
+
const previous = stateKey ? stored.conversations[stateKey] : undefined;
|
|
5394
|
+
const sourceTitleGate = source.getConversationTitleGateState?.() || source.titleGateState;
|
|
5395
|
+
const titleRequestMessageId = sourceTitleGate?.titleRequestMessageId === undefined
|
|
5396
|
+
? String(previous?.titleRequestMessageId || '')
|
|
5397
|
+
: String(sourceTitleGate.titleRequestMessageId || '');
|
|
5398
|
+
const firstAgentResponseStarted = sourceTitleGate?.firstAgentResponseStarted === undefined
|
|
5399
|
+
? this.restoredFirstAgentResponseStarted(previous)
|
|
5400
|
+
: sourceTitleGate.firstAgentResponseStarted === true;
|
|
5224
5401
|
if (key) {
|
|
5225
5402
|
this.workspaceConversations.set(key, {
|
|
5226
5403
|
chatMessages: normalizedChatMessages,
|
|
@@ -5234,15 +5411,13 @@ class Agent {
|
|
|
5234
5411
|
inputMode: this.inputMode,
|
|
5235
5412
|
mode: source.mode || this.mode,
|
|
5236
5413
|
goal: source.goal === undefined ? this.serializeGoal() : source.goal,
|
|
5414
|
+
titleRequestMessageId: titleRequestMessageId || undefined,
|
|
5415
|
+
firstAgentResponseStarted,
|
|
5237
5416
|
updatedAt,
|
|
5238
5417
|
});
|
|
5239
5418
|
}
|
|
5240
|
-
const stateKey = this.workspaceConversationStateKey(clean);
|
|
5241
5419
|
if (!stateKey)
|
|
5242
5420
|
return;
|
|
5243
|
-
const stored = this.readStoredConversationState(ws);
|
|
5244
|
-
stored.conversations = stored.conversations || {};
|
|
5245
|
-
const previous = stored.conversations[stateKey];
|
|
5246
5421
|
const derivedTitle = this.titleFromMessages(normalizedChatMessages, clean);
|
|
5247
5422
|
const title = this.hasUserConversationTitle(normalizedChatMessages) && this.isGeneratedConversationTitle(previous?.title, clean, previous?.chatMessages || [])
|
|
5248
5423
|
? derivedTitle
|
|
@@ -5261,6 +5436,8 @@ class Agent {
|
|
|
5261
5436
|
inputMode: this.inputMode,
|
|
5262
5437
|
mode: source.mode || previous?.mode || this.mode,
|
|
5263
5438
|
goal: source.goal === undefined ? (previous?.goal || this.serializeGoal()) : source.goal,
|
|
5439
|
+
titleRequestMessageId: titleRequestMessageId || undefined,
|
|
5440
|
+
firstAgentResponseStarted,
|
|
5264
5441
|
updatedAt,
|
|
5265
5442
|
};
|
|
5266
5443
|
if (nextEntry.tree || nextEntry.branches?.length) {
|
|
@@ -5281,6 +5458,8 @@ class Agent {
|
|
|
5281
5458
|
this.mode = source.mode || this.mode;
|
|
5282
5459
|
if (source.goal !== undefined)
|
|
5283
5460
|
this.goal = this.restoreGoal(source.goal);
|
|
5461
|
+
this.titleRequestMessageId = titleRequestMessageId;
|
|
5462
|
+
this.firstAgentResponseStarted = firstAgentResponseStarted;
|
|
5284
5463
|
this.activeWorkRunId = this.workRuns.find(run => run.status === 'running')?.runId || '';
|
|
5285
5464
|
}
|
|
5286
5465
|
}
|
|
@@ -7498,8 +7677,9 @@ class Agent {
|
|
|
7498
7677
|
}
|
|
7499
7678
|
}
|
|
7500
7679
|
else if (!hiddenUserInput) {
|
|
7680
|
+
const messageId = crypto.randomUUID();
|
|
7501
7681
|
this.chatMessages.push({
|
|
7502
|
-
messageId
|
|
7682
|
+
messageId,
|
|
7503
7683
|
branchNodeId: this.currentBranchNodeId(),
|
|
7504
7684
|
role: 'user',
|
|
7505
7685
|
content: displayText,
|
|
@@ -7520,13 +7700,21 @@ class Agent {
|
|
|
7520
7700
|
run_id: inputRunId || undefined,
|
|
7521
7701
|
});
|
|
7522
7702
|
}
|
|
7523
|
-
// Agent.process seeds the kernel from history, so its initial prompt does
|
|
7524
|
-
// not otherwise emit a kernel message_start event.
|
|
7525
|
-
this.notifyAgentKernelUserMessageStart(text, clientMessageId || undefined);
|
|
7526
7703
|
if (!hiddenUserInput)
|
|
7527
7704
|
this.recordWorkRunPrimaryPrompt(displayText);
|
|
7528
7705
|
this.saveWorkspaceConversationState(true);
|
|
7529
|
-
|
|
7706
|
+
let firstResponseGateMessageId = '';
|
|
7707
|
+
let firstResponseTitleInput = null;
|
|
7708
|
+
if (!hiddenUserInput && !clientMessageId && !this.firstAgentResponseStarted) {
|
|
7709
|
+
const firstInput = this.firstPersistedUserForTitleGate();
|
|
7710
|
+
if (!firstInput)
|
|
7711
|
+
throw new Error('Conversation title generation failed; the first persisted user input is unavailable.');
|
|
7712
|
+
if (this.model === 'auto' && !autoRouteEvaluated) {
|
|
7713
|
+
await this.evaluateAndSwitch(firstInput.input, inputEnvelope?.routePolicy);
|
|
7714
|
+
autoRouteEvaluated = true;
|
|
7715
|
+
}
|
|
7716
|
+
firstResponseTitleInput = firstInput;
|
|
7717
|
+
}
|
|
7530
7718
|
if (this.model === 'auto' && !autoRouteEvaluated) {
|
|
7531
7719
|
await this.evaluateAndSwitch(displayText, inputEnvelope?.routePolicy);
|
|
7532
7720
|
}
|
|
@@ -7542,6 +7730,30 @@ class Agent {
|
|
|
7542
7730
|
throw new Error(message);
|
|
7543
7731
|
}
|
|
7544
7732
|
}
|
|
7733
|
+
if (firstResponseTitleInput) {
|
|
7734
|
+
// ConversationKernel isolates each send in a runner. Capture the
|
|
7735
|
+
// resolved deployment only after routing/fallback, then use that same
|
|
7736
|
+
// runner-owned model and intelligence for the title availability gate.
|
|
7737
|
+
const titleProvider = this.engineModel();
|
|
7738
|
+
const titleModelName = this.activeModelName();
|
|
7739
|
+
const titleIntelligence = this.intelligence;
|
|
7740
|
+
if (!titleProvider || !titleModelName) {
|
|
7741
|
+
throw new Error('Conversation title generation failed; no resolved model deployment is available. No LLM configured. Add provider in Settings > Models.');
|
|
7742
|
+
}
|
|
7743
|
+
const titled = await this.startFirstInputConversationTitle(firstResponseTitleInput.messageId, firstResponseTitleInput.input, titleProvider, titleModelName, titleIntelligence, processSignal);
|
|
7744
|
+
if (!titled)
|
|
7745
|
+
throw new Error('Conversation title generation failed; the first Agent request was not started. Retry the first input.');
|
|
7746
|
+
firstResponseGateMessageId = firstResponseTitleInput.messageId;
|
|
7747
|
+
}
|
|
7748
|
+
if (firstResponseGateMessageId && !this.markFirstAgentResponseStarted(firstResponseGateMessageId)) {
|
|
7749
|
+
throw new Error('Conversation title generation succeeded, but the first Agent response gate could not be persisted. Retry the first input.');
|
|
7750
|
+
}
|
|
7751
|
+
// Agent.process seeds the kernel from history, so its initial prompt does
|
|
7752
|
+
// not otherwise emit a kernel message_start event. Keep this notification
|
|
7753
|
+
// after the title/model hard gate so a failed probe starts no formal
|
|
7754
|
+
// Agent provider request.
|
|
7755
|
+
this.notifyAgentKernelUserMessageStart(text, clientMessageId || undefined);
|
|
7756
|
+
this.emitWorkEvent({ type: 'start', content: 'Preparing request.' });
|
|
7545
7757
|
// Use external opencode CLI engine
|
|
7546
7758
|
if (this.engine === 'opencode') {
|
|
7547
7759
|
if (images.length)
|
|
@@ -8520,8 +8732,29 @@ class Agent {
|
|
|
8520
8732
|
}
|
|
8521
8733
|
catch { }
|
|
8522
8734
|
const action = String(input.action || '').trim();
|
|
8523
|
-
if (action !== 'source_info' && action !== 'crop')
|
|
8524
|
-
return '[Image inspect error] action must be source_info or
|
|
8735
|
+
if (action !== 'source_info' && action !== 'crop' && action !== 'inspect')
|
|
8736
|
+
return '[Image inspect error] action must be source_info, crop, or inspect.';
|
|
8737
|
+
if (action === 'inspect') {
|
|
8738
|
+
try {
|
|
8739
|
+
const workspacePath = this.workspace.current?.path || this.rootPath;
|
|
8740
|
+
const source = (0, displayImages_1.readWorkspaceImageForVision)(workspacePath, String(input.path || ''));
|
|
8741
|
+
return JSON.stringify({
|
|
8742
|
+
ok: true,
|
|
8743
|
+
action,
|
|
8744
|
+
source: 'workspace',
|
|
8745
|
+
path: source.path,
|
|
8746
|
+
name: source.name,
|
|
8747
|
+
format: source.mimeType,
|
|
8748
|
+
byte_length: source.byteLength,
|
|
8749
|
+
width: source.width,
|
|
8750
|
+
height: source.height,
|
|
8751
|
+
image_data_url: source.dataUrl,
|
|
8752
|
+
}, null, 2);
|
|
8753
|
+
}
|
|
8754
|
+
catch (error) {
|
|
8755
|
+
return `[Image inspect error] ${error instanceof Error ? error.message : String(error)}`;
|
|
8756
|
+
}
|
|
8757
|
+
}
|
|
8525
8758
|
const attachmentId = String(input.attachment_id || '').trim();
|
|
8526
8759
|
const images = this.latestSubmittedImages(attachmentId);
|
|
8527
8760
|
const imageIndex = Math.max(1, Math.floor(Number(input.image_index || 1)));
|
|
@@ -18,6 +18,13 @@ export declare function isPublicBrowserUseAttribute(input: string): boolean;
|
|
|
18
18
|
export interface BrowserUseScope {
|
|
19
19
|
owner: string;
|
|
20
20
|
runtimeKey: string;
|
|
21
|
+
/**
|
|
22
|
+
* Selects the user-visible right-sidebar browser surface. `false` keeps the
|
|
23
|
+
* same Browser-Use protocol on a host-owned background page that is never
|
|
24
|
+
* attached to the renderer. Omission is normalized to `true` for backwards
|
|
25
|
+
* compatibility.
|
|
26
|
+
*/
|
|
27
|
+
visible?: boolean;
|
|
21
28
|
}
|
|
22
29
|
export interface BrowserUseRequest extends BrowserUseScope {
|
|
23
30
|
action: BrowserUseAction;
|