newmark-agent 0.5.3 → 0.5.7
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 +147 -29
- package/dist/core/agent.d.ts +7 -0
- package/dist/core/agent.js +111 -26
- package/dist/core/agentKernelRunner.js +30 -2
- package/dist/core/autoRouter.d.ts +1 -0
- package/dist/core/autoRouter.js +9 -0
- package/dist/core/conversationKernel.d.ts +12 -0
- package/dist/core/conversationKernel.js +32 -1
- package/dist/core/electronUtilityRuntimePool.js +7 -2
- package/dist/core/types.d.ts +21 -0
- package/dist/core/wslAgentRuntimePool.js +6 -2
- package/dist/main.js +34 -0
- package/dist/preload.js +1 -0
- package/dist/providers/provider-events.d.ts +6 -0
- package/dist/providers/provider-events.js +15 -4
- package/dist/server.js +21 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +4 -0
- package/dist/ui/index.html +2345 -197
- package/dist/ui/lucide-sprite.svg +4 -0
- package/dist/wsl-agent-host.bundle.cjs +147 -29
- package/package.json +8 -3
|
@@ -60,6 +60,33 @@ class ConversationKernel {
|
|
|
60
60
|
followUp: queued?.followUp.slice() || [],
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* dev-0.5.6: 排队 followUp 消息 drain 时转成普通用户消息载荷。
|
|
65
|
+
*
|
|
66
|
+
* 入队时(enqueueSameSession)为去重/编辑保留了 clientMessageId,并把
|
|
67
|
+
* runId 固定为入队时运行的 runId;若原样传给 process,写入 chatMessages
|
|
68
|
+
* 的用户消息会携带旧 runId + clientMessageId,渲染端(PC renderTranscript /
|
|
69
|
+
* 移动端 projectRemoteConversationItems)会把「clientMessageId + runId 命中
|
|
70
|
+
* workRuns」的消息误判为 guide 消息而跳过气泡,导致排队自动发送的消息不
|
|
71
|
+
* 显示在用户输入时间线。清除这两个字段后 process 走普通用户消息分支
|
|
72
|
+
* (chatMessages.push,mode 取 visibleMode,runId 取当前 run)。
|
|
73
|
+
*/
|
|
74
|
+
drainQueuedFollowUpMessage(message) {
|
|
75
|
+
if (typeof message === 'string')
|
|
76
|
+
return message;
|
|
77
|
+
const text = String(message.text || '');
|
|
78
|
+
const images = message.images;
|
|
79
|
+
const attachments = message.attachments;
|
|
80
|
+
const visible = message.visibleUserInput;
|
|
81
|
+
const visibleMode = message.visibleMode;
|
|
82
|
+
return {
|
|
83
|
+
text,
|
|
84
|
+
...(images?.length ? { images } : {}),
|
|
85
|
+
...(attachments?.length ? { attachments } : {}),
|
|
86
|
+
...(visible ? { visibleUserInput: visible } : {}),
|
|
87
|
+
...(visibleMode ? { visibleMode } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
63
90
|
queueItems(target) {
|
|
64
91
|
const runtime = this.findRuntime(target);
|
|
65
92
|
if (!runtime)
|
|
@@ -888,7 +915,7 @@ class ConversationKernel {
|
|
|
888
915
|
lastTokens = await this.runSingle(runtime, batchMessage, 'steer');
|
|
889
916
|
}
|
|
890
917
|
else {
|
|
891
|
-
lastTokens = await this.runSingle(runtime, next.message, next.queueMode);
|
|
918
|
+
lastTokens = await this.runSingle(runtime, this.drainQueuedFollowUpMessage(next.message), next.queueMode);
|
|
892
919
|
}
|
|
893
920
|
}
|
|
894
921
|
const rootMessage = runtime.runner.subagents.readRootInbox()[0];
|
|
@@ -1414,6 +1441,10 @@ class ConversationKernel {
|
|
|
1414
1441
|
content: 'Conversation queue updated.',
|
|
1415
1442
|
conversationId: runtime.id,
|
|
1416
1443
|
queue: this.queued(runtime.target),
|
|
1444
|
+
// Structured rows with stable kernel ids so every consumer (PC UI and
|
|
1445
|
+
// the paired mobile client) can render/edit/delete the same items.
|
|
1446
|
+
queueItems: this.queueItems(runtime.target),
|
|
1447
|
+
queuePaused: runtime.queuePaused === true,
|
|
1417
1448
|
});
|
|
1418
1449
|
}
|
|
1419
1450
|
clearQueued(runtime) {
|
|
@@ -601,8 +601,13 @@ class ElectronUtilityRuntimePool {
|
|
|
601
601
|
return this.accessSequence;
|
|
602
602
|
}
|
|
603
603
|
maxResidentRuntimes() {
|
|
604
|
-
|
|
605
|
-
|
|
604
|
+
// The pool hosts one utility runtime per active conversation target.
|
|
605
|
+
// A small default (2) silently blocked users from running more than two
|
|
606
|
+
// conversations at once with "utility runtime pool capacity reached".
|
|
607
|
+
// Idle runtimes are still evicted on LRU after the idle TTL, so a higher
|
|
608
|
+
// default bounds memory by activity, not by an arbitrary conversation cap.
|
|
609
|
+
const configured = Number(this.options.maxResidentRuntimes ?? 8);
|
|
610
|
+
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 8;
|
|
606
611
|
}
|
|
607
612
|
async serializeCapacity(operation) {
|
|
608
613
|
const previous = this.capacityTail;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -112,6 +112,17 @@ export interface AgentWorkEvent {
|
|
|
112
112
|
steering: string[];
|
|
113
113
|
followUp: string[];
|
|
114
114
|
};
|
|
115
|
+
/** Structured queue rows with stable kernel ids (mobile/PC queue unification export). */
|
|
116
|
+
queueItems?: Array<{
|
|
117
|
+
id: string;
|
|
118
|
+
text: string;
|
|
119
|
+
queueMode: string;
|
|
120
|
+
requestedMode?: string;
|
|
121
|
+
goalObjective?: string;
|
|
122
|
+
runId?: string;
|
|
123
|
+
createdAt: string;
|
|
124
|
+
}>;
|
|
125
|
+
queuePaused?: boolean;
|
|
115
126
|
workspaceId?: string;
|
|
116
127
|
workspaceKey?: string;
|
|
117
128
|
runtimeKey?: string;
|
|
@@ -124,6 +135,16 @@ export interface AgentWorkEvent {
|
|
|
124
135
|
status?: GuideReceiptStatus | ConversationWorkRunStatus | 'stopping' | 'force_restarting';
|
|
125
136
|
guide?: GuideReceipt;
|
|
126
137
|
displayImage?: DisplayImageAttachment;
|
|
138
|
+
/**
|
|
139
|
+
* 结构化模型回退信号:from 为回退前的模型名,to 为实际使用的模型名。
|
|
140
|
+
* 前端据此把输入框下方的模型选择区同步为实际生效的模型,而不是隐藏的
|
|
141
|
+
* 参数回退。
|
|
142
|
+
*/
|
|
143
|
+
fallback?: {
|
|
144
|
+
from: string;
|
|
145
|
+
to: string;
|
|
146
|
+
providerId?: string;
|
|
147
|
+
};
|
|
127
148
|
}
|
|
128
149
|
export interface ConversationWorkRun {
|
|
129
150
|
runId: string;
|
|
@@ -569,8 +569,12 @@ class WslAgentRuntimePool {
|
|
|
569
569
|
return this.accessSequence;
|
|
570
570
|
}
|
|
571
571
|
maxResidentRuntimes() {
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
// Same rationale as the Electron utility pool: one runtime per active
|
|
573
|
+
// conversation target. The previous default of 2 blocked parallel
|
|
574
|
+
// conversations with a capacity error; idle LRU eviction still bounds
|
|
575
|
+
// resident memory.
|
|
576
|
+
const configured = Number(this.options.maxResidentRuntimes ?? 8);
|
|
577
|
+
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 8;
|
|
574
578
|
}
|
|
575
579
|
async serializeCapacity(operation) {
|
|
576
580
|
const previous = this.capacityTail;
|
package/dist/main.js
CHANGED
|
@@ -3020,6 +3020,10 @@ else {
|
|
|
3020
3020
|
activePromptLeases.set(promptLeaseKey, (activePromptLeases.get(promptLeaseKey) || 0) + 1);
|
|
3021
3021
|
activePromptWorkspaces.set(promptLeaseWorkspaceKey, (activePromptWorkspaces.get(promptLeaseWorkspaceKey) || 0) + 1);
|
|
3022
3022
|
const targetConversation = target.conversationId;
|
|
3023
|
+
// 发送命令时锁定输入框选择的模型:接受命令后的整个运行过程都以该
|
|
3024
|
+
// 模型为准(唯一例外是显式的不可用回退,且回退会以结构化事件同步
|
|
3025
|
+
// 到前端输入框下方的选择区,而不是隐藏的参数回退)。
|
|
3026
|
+
const requestedModel = agent.model;
|
|
3023
3027
|
const options = {
|
|
3024
3028
|
mode: agent.mode,
|
|
3025
3029
|
model: agent.ensureUsableModelSelection(),
|
|
@@ -3027,6 +3031,21 @@ else {
|
|
|
3027
3031
|
inputMode: agent.inputMode,
|
|
3028
3032
|
engine: agent.engine,
|
|
3029
3033
|
};
|
|
3034
|
+
if (options.model && requestedModel && options.model !== requestedModel) {
|
|
3035
|
+
const usableConfig = agent.activeModelConfig();
|
|
3036
|
+
broadcastAgentWorkEvent({
|
|
3037
|
+
id: `model-fallback-${Date.now()}-${Math.random().toString(16).slice(2)}`,
|
|
3038
|
+
conversationId: target.conversationId,
|
|
3039
|
+
type: 'status',
|
|
3040
|
+
content: `[Model fallback] ${requestedModel} unavailable; switched to ${options.model}.`,
|
|
3041
|
+
mode: agent.modeName(),
|
|
3042
|
+
model: options.model,
|
|
3043
|
+
timestamp: new Date().toISOString(),
|
|
3044
|
+
workspaceId: target.workspaceId,
|
|
3045
|
+
workspaceKey: target.workspaceKey,
|
|
3046
|
+
fallback: { from: requestedModel, to: options.model, providerId: usableConfig?.provider_id || agent.activeDeployment()?.providerId },
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3030
3049
|
const queueMode = agent.inputMode === 'guide' ? 'steer' : 'followUp';
|
|
3031
3050
|
let result;
|
|
3032
3051
|
if (wslBackendEnabled()) {
|
|
@@ -5206,6 +5225,21 @@ else {
|
|
|
5206
5225
|
// make an ordinary minimize click disappear from the taskbar.
|
|
5207
5226
|
win?.minimize();
|
|
5208
5227
|
});
|
|
5228
|
+
electron_1.ipcMain.handle('glass:captureBackdrop', async (event, requestedSize) => {
|
|
5229
|
+
const image = await event.sender.capturePage();
|
|
5230
|
+
const sourceSize = image.getSize();
|
|
5231
|
+
const width = Math.max(1, Math.min(sourceSize.width, Math.round(Number(requestedSize?.width) || sourceSize.width)));
|
|
5232
|
+
const height = Math.max(1, Math.min(sourceSize.height, Math.round(Number(requestedSize?.height) || sourceSize.height)));
|
|
5233
|
+
const resized = sourceSize.width === width && sourceSize.height === height
|
|
5234
|
+
? image
|
|
5235
|
+
: image.resize({ width, height, quality: 'good' });
|
|
5236
|
+
return {
|
|
5237
|
+
bytes: resized.toJPEG(82),
|
|
5238
|
+
mimeType: 'image/jpeg',
|
|
5239
|
+
width,
|
|
5240
|
+
height,
|
|
5241
|
+
};
|
|
5242
|
+
});
|
|
5209
5243
|
electron_1.ipcMain.handle('app:maximize', () => {
|
|
5210
5244
|
const win = electron_1.BrowserWindow.getFocusedWindow() || mainWindow;
|
|
5211
5245
|
if (win?.isMaximized())
|
package/dist/preload.js
CHANGED
|
@@ -6,6 +6,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
6
6
|
startupWaitForBackend: () => ipcRenderer.invoke('startup:waitForBackend'),
|
|
7
7
|
startupUiReady: (payload) => ipcRenderer.invoke('startup:uiReady', payload),
|
|
8
8
|
startupUiFailed: (payload) => ipcRenderer.invoke('startup:uiFailed', payload),
|
|
9
|
+
captureLiquidBackdrop: (size) => ipcRenderer.invoke('glass:captureBackdrop', size),
|
|
9
10
|
onStartupStatus: (callback) => {
|
|
10
11
|
ipcRenderer.on('startup:status', (_event, payload) => callback(payload));
|
|
11
12
|
},
|
|
@@ -17,6 +17,12 @@ export declare function providerStreamTimeoutError(timeoutMs: number): Error;
|
|
|
17
17
|
* Read one SSE chunk with both user cancellation and an inactivity deadline.
|
|
18
18
|
* Cancelling the reader is important: rejecting the race alone leaves the
|
|
19
19
|
* provider socket alive and lets later requests accumulate behind it.
|
|
20
|
+
*
|
|
21
|
+
* timeoutMs defaults to 0 (no stream idle deadline), matching the request-
|
|
22
|
+
* level DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 0 and the Android client's
|
|
23
|
+
* readTimeout(0) / SSE_IDLE_TIMEOUT_MS = 0L. A caller that still wants an
|
|
24
|
+
* inactivity cap passes an explicit positive value (the recovery verify
|
|
25
|
+
* passes 50ms to prove reader cancellation).
|
|
20
26
|
*/
|
|
21
27
|
export declare function readProviderStreamChunk(reader: ReadableStreamDefaultReader<Uint8Array>, signal: AbortSignal, timeoutMs?: number): Promise<ReadableStreamReadResult<Uint8Array>>;
|
|
22
28
|
export declare function parseProviderSse(raw: string): Array<{
|
|
@@ -46,8 +46,14 @@ function providerStreamTimeoutError(timeoutMs) {
|
|
|
46
46
|
* Read one SSE chunk with both user cancellation and an inactivity deadline.
|
|
47
47
|
* Cancelling the reader is important: rejecting the race alone leaves the
|
|
48
48
|
* provider socket alive and lets later requests accumulate behind it.
|
|
49
|
+
*
|
|
50
|
+
* timeoutMs defaults to 0 (no stream idle deadline), matching the request-
|
|
51
|
+
* level DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS = 0 and the Android client's
|
|
52
|
+
* readTimeout(0) / SSE_IDLE_TIMEOUT_MS = 0L. A caller that still wants an
|
|
53
|
+
* inactivity cap passes an explicit positive value (the recovery verify
|
|
54
|
+
* passes 50ms to prove reader cancellation).
|
|
49
55
|
*/
|
|
50
|
-
async function readProviderStreamChunk(reader, signal, timeoutMs =
|
|
56
|
+
async function readProviderStreamChunk(reader, signal, timeoutMs = 0) {
|
|
51
57
|
if (signal.aborted)
|
|
52
58
|
throw providerAbortError(signal);
|
|
53
59
|
let timer;
|
|
@@ -56,9 +62,14 @@ async function readProviderStreamChunk(reader, signal, timeoutMs = 30_000) {
|
|
|
56
62
|
onAbort = () => reject(providerAbortError(signal));
|
|
57
63
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
58
64
|
});
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
// timeoutMs <= 0 means no inactivity deadline (unlimited). setTimeout with
|
|
66
|
+
// 0 would fire on the next tick, so we only arm the timer for positive
|
|
67
|
+
// values and race a never-settling promise otherwise.
|
|
68
|
+
const timeoutPromise = timeoutMs > 0
|
|
69
|
+
? new Promise((_, reject) => {
|
|
70
|
+
timer = setTimeout(() => reject(providerStreamTimeoutError(timeoutMs)), timeoutMs);
|
|
71
|
+
})
|
|
72
|
+
: new Promise(() => undefined);
|
|
62
73
|
try {
|
|
63
74
|
return await Promise.race([reader.read(), abortPromise, timeoutPromise]);
|
|
64
75
|
}
|
package/dist/server.js
CHANGED
|
@@ -1298,6 +1298,27 @@ async function handleApi(req, res, body) {
|
|
|
1298
1298
|
mobileJson(res, await hostedConversationUiAction({ workspaceId, conversationId }, action, String(params.value || ''), params));
|
|
1299
1299
|
return;
|
|
1300
1300
|
}
|
|
1301
|
+
case '/api/queue-action': {
|
|
1302
|
+
// Desktop-renderer queue mutations. The mobile endpoint requires a
|
|
1303
|
+
// pairing token; the renderer shares the same process and is trusted,
|
|
1304
|
+
// so it gets a dedicated unauthenticated route that reuses the same
|
|
1305
|
+
// GUI runtime-pool queueAction path (update/delete/reorder/guide).
|
|
1306
|
+
const params = JSON.parse(body || '{}');
|
|
1307
|
+
const workspaceId = String(params.workspaceId || '');
|
|
1308
|
+
const conversationId = String(params.conversationId || '');
|
|
1309
|
+
const action = String(params.action || '');
|
|
1310
|
+
const allowed = new Set(['queue_enqueue', 'queue_update', 'queue_delete', 'queue_reorder', 'queue_toggle_pause', 'queue_guide']);
|
|
1311
|
+
if (!workspaceId || !conversationId || !allowed.has(action)) {
|
|
1312
|
+
jsonResponse(res, { error: 'workspaceId, conversationId, and a valid queue action are required' }, 400);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
if (!hostedConversationUiAction) {
|
|
1316
|
+
jsonResponse(res, { error: 'This action requires the GUI-hosted runtime pool' }, 409);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
jsonResponse(res, await hostedConversationUiAction({ workspaceId, conversationId }, action, String(params.value || ''), params));
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1301
1322
|
case '/api/mobile/conversation-rename': {
|
|
1302
1323
|
const params = JSON.parse(body || '{}');
|
|
1303
1324
|
const workspaceId = String(params.workspaceId || '');
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ConfigManager } from '../core/config';
|
|
|
2
2
|
import { NewmarkToolDefinition, NewmarkToolResult } from '../core/compat';
|
|
3
3
|
import { SshManager } from '../core/ssh';
|
|
4
4
|
import { WorkspaceManager } from '../core/workspace';
|
|
5
|
+
import { LocalOcrResult } from '../core/localOcr';
|
|
5
6
|
export interface ToolExecutionContext {
|
|
6
7
|
mode?: string;
|
|
7
8
|
workspacePath?: string;
|
|
@@ -30,6 +31,8 @@ export declare class ToolExecutor {
|
|
|
30
31
|
private hostProfile;
|
|
31
32
|
constructor(root: string, config: ConfigManager, ssh?: SshManager | undefined, workspace?: WorkspaceManager | undefined);
|
|
32
33
|
webSearch(query: string): Promise<string>;
|
|
34
|
+
/** OCR entry point for the runtime's final visual fallback. */
|
|
35
|
+
finalVisualFallbackOcr(dataUrl: string, signal?: AbortSignal): Promise<LocalOcrResult>;
|
|
33
36
|
setHostProfile(profile: ToolHostProfile): void;
|
|
34
37
|
definitions(mode?: string): unknown[];
|
|
35
38
|
canonicalDefinitions(mode?: string): NewmarkToolDefinition[];
|
package/dist/tools/index.js
CHANGED
|
@@ -234,6 +234,10 @@ class ToolExecutor {
|
|
|
234
234
|
async webSearch(query) {
|
|
235
235
|
return this.wsearch(query);
|
|
236
236
|
}
|
|
237
|
+
/** OCR entry point for the runtime's final visual fallback. */
|
|
238
|
+
async finalVisualFallbackOcr(dataUrl, signal) {
|
|
239
|
+
return await this.localOcr.recognizeDataUrl(dataUrl, signal, 'sparse-ui');
|
|
240
|
+
}
|
|
237
241
|
setHostProfile(profile) {
|
|
238
242
|
this.hostProfile = { ...profile };
|
|
239
243
|
}
|