newmark-agent 0.6.4 → 0.6.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/config.example.json +13 -3
- package/dist/cli-commands.d.ts +1 -1
- package/dist/cli-commands.js +53 -2
- package/dist/cli-discovery.js +3 -0
- package/dist/conversation-utility-host.bundle.cjs +2880 -1069
- package/dist/conversation-utility-host.js +94 -7
- package/dist/core/agent.d.ts +126 -12
- package/dist/core/agent.js +783 -172
- package/dist/core/agentKernelRunner.d.ts +7 -1
- package/dist/core/agentKernelRunner.js +156 -20
- package/dist/core/autoRouter.d.ts +49 -44
- package/dist/core/autoRouter.js +117 -302
- package/dist/core/config.js +27 -11
- package/dist/core/continuation/contracts.d.ts +1 -1
- package/dist/core/continuation/store.d.ts +91 -1
- package/dist/core/continuation/store.js +291 -61
- package/dist/core/conversationKernel.d.ts +49 -7
- package/dist/core/conversationKernel.js +303 -51
- package/dist/core/conversationStateDocument.d.ts +18 -0
- package/dist/core/conversationStateDocument.js +88 -0
- package/dist/core/conversationVisualBudget.d.ts +74 -0
- package/dist/core/conversationVisualBudget.js +153 -0
- package/dist/core/electronUtilityAgentClient.d.ts +3 -2
- package/dist/core/electronUtilityAgentClient.js +32 -8
- package/dist/core/electronUtilityRuntimePool.d.ts +28 -3
- package/dist/core/electronUtilityRuntimePool.js +93 -24
- package/dist/core/hostRuntimeHooks.d.ts +23 -0
- package/dist/core/hostRuntimeHooks.js +17 -0
- package/dist/core/jevDecision.d.ts +144 -0
- package/dist/core/jevDecision.js +226 -0
- package/dist/core/memoryProbe.d.ts +24 -0
- package/dist/core/memoryProbe.js +104 -0
- package/dist/core/mobilePairing.d.ts +7 -1
- package/dist/core/mobilePairing.js +9 -1
- package/dist/core/performanceDiagnostics.d.ts +1 -1
- package/dist/core/routeDecisionValidator.d.ts +86 -0
- package/dist/core/routeDecisionValidator.js +249 -0
- package/dist/core/routeEligibility.d.ts +52 -0
- package/dist/core/routeEligibility.js +108 -0
- package/dist/core/runtimeMemoryBudget.d.ts +6 -0
- package/dist/core/runtimeMemoryBudget.js +12 -0
- package/dist/core/utilityAgentProtocol.d.ts +8 -10
- package/dist/core/utilityHostToolRouter.js +2 -0
- package/dist/core/visualDownscale.d.ts +6 -0
- package/dist/core/visualDownscale.js +137 -0
- package/dist/core/wslAgentClient.d.ts +1 -2
- package/dist/core/wslAgentClient.js +0 -8
- package/dist/core/wslAgentProtocol.d.ts +1 -10
- package/dist/core/wslAgentRuntimePool.d.ts +1 -3
- package/dist/core/wslAgentRuntimePool.js +17 -22
- package/dist/llm/provider.d.ts +3 -1
- package/dist/llm/provider.js +53 -9
- package/dist/main.js +107 -30
- package/dist/preload.js +3 -1
- package/dist/server.js +30 -18
- package/dist/tools/computerUse.d.ts +4 -0
- package/dist/tools/computerUse.js +202 -4
- package/dist/tools/computerUsePowerShellHost.d.ts +6 -1
- package/dist/tools/computerUsePowerShellHost.js +105 -31
- package/dist/tools/index.js +4 -1
- package/dist/tui/src/adapters/core-runtime-adapter.js +13 -1
- package/dist/tui/src/i18n.js +12 -1
- package/dist/tui/src/render.js +9 -2
- package/dist/tui/src/settings-schema.js +19 -0
- package/dist/tui/src/state.js +69 -1
- package/dist/ui/index.html +651 -233
- package/dist/ui/lucide-sprite.svg +0 -8
- package/dist/wsl-agent-host.bundle.cjs +2685 -1044
- package/dist/wsl-agent-host.js +0 -3
- package/package.json +36 -9
|
@@ -4,7 +4,7 @@ import { ConversationRuntimeTarget } from './conversationTarget';
|
|
|
4
4
|
import { TerminalTakeoverEvent, TerminalTakeoverOwnerFilter, TerminalTakeoverState } from '../tools/terminalTakeover';
|
|
5
5
|
import { BrowserUseRequest } from './browserUse';
|
|
6
6
|
import { BrowserControlRequest } from './browserControl';
|
|
7
|
-
import type {
|
|
7
|
+
import type { ConversationSnapshot } from './agent';
|
|
8
8
|
export interface WslAgentWorkspace {
|
|
9
9
|
id?: string;
|
|
10
10
|
name: string;
|
|
@@ -136,14 +136,6 @@ export type WslAgentRequest = {
|
|
|
136
136
|
target: ConversationRuntimeTarget;
|
|
137
137
|
options?: ConversationContextCompressOptions;
|
|
138
138
|
};
|
|
139
|
-
} | {
|
|
140
|
-
id: string;
|
|
141
|
-
method: 'rate_auto_route';
|
|
142
|
-
params: {
|
|
143
|
-
target: ConversationRuntimeTarget;
|
|
144
|
-
score: number;
|
|
145
|
-
routeId?: string;
|
|
146
|
-
};
|
|
147
139
|
} | {
|
|
148
140
|
id: string;
|
|
149
141
|
method: 'set_work_run_expanded';
|
|
@@ -272,7 +264,6 @@ export type WslAgentStopResult = ConversationStopResult & {
|
|
|
272
264
|
distro: string;
|
|
273
265
|
};
|
|
274
266
|
export type WslGuideResult = GuideReceipt;
|
|
275
|
-
export type WslAutoRouteRatingResult = AutoRouteRatingResult;
|
|
276
267
|
export type WslConversationRewindResult = ConversationSnapshot;
|
|
277
268
|
export {};
|
|
278
269
|
//# sourceMappingURL=wslAgentProtocol.d.ts.map
|
|
@@ -2,7 +2,7 @@ import { AgentMode, AgentWorkEvent, ConversationInputEnvelope, GuideReceipt } fr
|
|
|
2
2
|
import { ConversationQueueAction, ConversationQueueActionInput } from './conversationKernel';
|
|
3
3
|
import { ConversationRuntimeTarget, NormalizedConversationTarget } from './conversationTarget';
|
|
4
4
|
import { WslHostToolHandler } from './wslAgentClient';
|
|
5
|
-
import { WslAgentPromptRequest, WslAgentPromptResult,
|
|
5
|
+
import { WslAgentPromptRequest, WslAgentPromptResult, WslAgentStopResult, WslConversationRewindResult } from './wslAgentProtocol';
|
|
6
6
|
export interface WslTargetRuntimeClient {
|
|
7
7
|
subscribe(listener: (event: AgentWorkEvent) => void): () => void;
|
|
8
8
|
setHostToolHandler(handler: WslHostToolHandler | null): void;
|
|
@@ -20,7 +20,6 @@ export interface WslTargetRuntimeClient {
|
|
|
20
20
|
keepRecent?: number;
|
|
21
21
|
force?: boolean;
|
|
22
22
|
}): Promise<Record<string, unknown>>;
|
|
23
|
-
rateAutoRoute?(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
24
23
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
25
24
|
setMode?(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode>;
|
|
26
25
|
setModel?(target: ConversationRuntimeTarget, model: string): Promise<string>;
|
|
@@ -87,7 +86,6 @@ export declare class WslAgentRuntimePool {
|
|
|
87
86
|
keepRecent?: number;
|
|
88
87
|
force?: boolean;
|
|
89
88
|
}): Promise<Record<string, unknown>>;
|
|
90
|
-
rateAutoRoute(target: ConversationRuntimeTarget, score: number, routeId?: string): Promise<WslAutoRouteRatingResult>;
|
|
91
89
|
setWorkRunExpanded(target: ConversationRuntimeTarget, runId: string, expanded: boolean): Promise<boolean>;
|
|
92
90
|
setInputMode(target: ConversationRuntimeTarget, mode: string): Promise<'guide' | 'next' | null>;
|
|
93
91
|
setMode(target: ConversationRuntimeTarget, mode: AgentMode): Promise<AgentMode | null>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WslAgentRuntimePool = void 0;
|
|
4
|
+
const runtimeMemoryBudget_1 = require("./runtimeMemoryBudget");
|
|
4
5
|
const conversationTarget_1 = require("./conversationTarget");
|
|
5
6
|
const wslAgentClient_1 = require("./wslAgentClient");
|
|
6
7
|
const runtimePoolCapacity_1 = require("./runtimePoolCapacity");
|
|
@@ -68,6 +69,15 @@ class WslAgentRuntimePool {
|
|
|
68
69
|
}
|
|
69
70
|
async snapshot(target, options = {}) {
|
|
70
71
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
72
|
+
const wantsDefaultWindow = options.before == null && (options.window == null || options.window === 200);
|
|
73
|
+
// Same contract as the Electron pool: a timed status poll must not 500
|
|
74
|
+
// while the target's runtime is being force-stopped by an archive.
|
|
75
|
+
if (wantsDefaultWindow) {
|
|
76
|
+
const pending = this.entries.get(normalized.runtimeKey);
|
|
77
|
+
if (pending && (this.disposing.has(normalized.runtimeKey) || this.restarting.has(normalized.runtimeKey))) {
|
|
78
|
+
return this.supervisorSnapshot(pending);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
71
81
|
const entry = await this.acquire(normalized);
|
|
72
82
|
let scheduleIdle = false;
|
|
73
83
|
try {
|
|
@@ -246,19 +256,6 @@ class WslAgentRuntimePool {
|
|
|
246
256
|
this.release(entry, true);
|
|
247
257
|
}
|
|
248
258
|
}
|
|
249
|
-
async rateAutoRoute(target, score, routeId = '') {
|
|
250
|
-
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
251
|
-
const entry = await this.acquireExisting(normalized);
|
|
252
|
-
if (!entry || !entry.client.rateAutoRoute) {
|
|
253
|
-
return { ok: false, reason: 'no_active_auto_route' };
|
|
254
|
-
}
|
|
255
|
-
try {
|
|
256
|
-
return await entry.client.rateAutoRoute(normalized, score, routeId);
|
|
257
|
-
}
|
|
258
|
-
finally {
|
|
259
|
-
this.release(entry, true);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
259
|
async setWorkRunExpanded(target, runId, expanded) {
|
|
263
260
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
264
261
|
const entry = await this.acquire(normalized);
|
|
@@ -575,12 +572,9 @@ class WslAgentRuntimePool {
|
|
|
575
572
|
return this.accessSequence;
|
|
576
573
|
}
|
|
577
574
|
maxResidentRuntimes() {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
// resident memory.
|
|
582
|
-
const configured = Number(this.options.maxResidentRuntimes ?? 8);
|
|
583
|
-
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : 8;
|
|
575
|
+
const fallback = (0, runtimeMemoryBudget_1.runtimeMemoryBudget)().maxResidentRuntimes;
|
|
576
|
+
const configured = Number(this.options.maxResidentRuntimes ?? fallback);
|
|
577
|
+
return Number.isFinite(configured) ? Math.max(1, Math.floor(configured)) : fallback;
|
|
584
578
|
}
|
|
585
579
|
async serializeCapacity(operation) {
|
|
586
580
|
const previous = this.capacityTail;
|
|
@@ -628,7 +622,7 @@ class WslAgentRuntimePool {
|
|
|
628
622
|
}
|
|
629
623
|
scheduleIdle(entry) {
|
|
630
624
|
this.touch(entry);
|
|
631
|
-
const ttl = Math.max(10, Number(this.options.idleTtlMs ??
|
|
625
|
+
const ttl = Math.max(10, Number(this.options.idleTtlMs ?? (0, runtimeMemoryBudget_1.runtimeMemoryBudget)().idleTtlMs));
|
|
632
626
|
const timer = setTimeout(() => {
|
|
633
627
|
void this.evictIfIdle(entry.target.runtimeKey, entry.lastUsedAt).catch(() => {
|
|
634
628
|
// Failed cleanup retains the entry and its client's explicit error /
|
|
@@ -771,11 +765,12 @@ class WslAgentRuntimePool {
|
|
|
771
765
|
...cached,
|
|
772
766
|
target: entry.target,
|
|
773
767
|
runtime: {
|
|
768
|
+
...(cached.runtime || {}),
|
|
774
769
|
target: entry.target,
|
|
775
770
|
workspaceKey: entry.target.workspaceKey,
|
|
776
771
|
runtimeKey: entry.target.runtimeKey,
|
|
777
|
-
runId: intent.
|
|
778
|
-
generation: intent.
|
|
772
|
+
runId: intent?.runId || entry.lastRunId,
|
|
773
|
+
generation: intent?.generation || entry.lastGeneration,
|
|
779
774
|
running: true,
|
|
780
775
|
stopRequested: true,
|
|
781
776
|
workRuns: cachedRuntime?.workRuns || [],
|
package/dist/llm/provider.d.ts
CHANGED
|
@@ -144,7 +144,9 @@ export declare class LLMProvider {
|
|
|
144
144
|
* protocol field. Prompt-only JSON requests must not be treated as evidence
|
|
145
145
|
* that a deployment supports structured output.
|
|
146
146
|
*/
|
|
147
|
-
chatStrictJson(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, schema: Readonly<Record<string, unknown>>, signal?: AbortSignal): Promise<string>;
|
|
147
|
+
chatStrictJson(model: string, messages: Array<Record<string, unknown>>, systemPrompt: string | null, temperature: number, maxTokens: number, schema: Readonly<Record<string, unknown>>, signal?: AbortSignal, reasoningTier?: string, schemaName?: string, onUsage?: (usage: NonNullable<StreamToken['usage']>) => void, reasoningControlRequired?: boolean): Promise<string>;
|
|
148
|
+
private requiresNativeReasoningControl;
|
|
149
|
+
private supportsNativeNoReasoningEffort;
|
|
148
150
|
/**
|
|
149
151
|
* Small streaming probe with explicit terminal-event evidence. Reading until
|
|
150
152
|
* the socket closes is insufficient: truncated SSE must not validate.
|
package/dist/llm/provider.js
CHANGED
|
@@ -238,6 +238,12 @@ class LLMProvider {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
reasoningEffort(model, tier) {
|
|
241
|
+
// JEV route decisions explicitly request the lowest native reasoning mode.
|
|
242
|
+
// This bypasses per-model tier maps so a configured map cannot silently
|
|
243
|
+
// turn a "no thinking" decision request back into medium effort.
|
|
244
|
+
if (tier === 'none') {
|
|
245
|
+
return this.supportsNativeNoReasoningEffort(model) ? 'none' : undefined;
|
|
246
|
+
}
|
|
241
247
|
const mapped = this.mappedNativeEffort(model, tier);
|
|
242
248
|
if (mapped !== undefined)
|
|
243
249
|
return mapped;
|
|
@@ -410,6 +416,20 @@ class LLMProvider {
|
|
|
410
416
|
// several isolated workers concurrently call a plain-HTTP local provider.
|
|
411
417
|
// Node's HTTP client owns the full body lifecycle and is deterministic for
|
|
412
418
|
// loopback transports, while remote HTTPS providers retain fetch semantics.
|
|
419
|
+
/**
|
|
420
|
+
* dev-0.6.6: serialize the request body **once** per attempt and reuse that
|
|
421
|
+
* same string for the loopback transport and for the Node-HTTP fallback.
|
|
422
|
+
* Re-serializing the whole context on the fallback path held a second full
|
|
423
|
+
* copy of a potentially very large payload (the heap snapshot showed an
|
|
424
|
+
* 84 MiB serialized request body) for no behavioural difference — the bytes
|
|
425
|
+
* are identical.
|
|
426
|
+
*/
|
|
427
|
+
let serializedBody = '';
|
|
428
|
+
const bodyText = () => {
|
|
429
|
+
if (!serializedBody)
|
|
430
|
+
serializedBody = JSON.stringify(body);
|
|
431
|
+
return serializedBody;
|
|
432
|
+
};
|
|
413
433
|
if (this.isPlainHttpLoopback(url)) {
|
|
414
434
|
const pathname = (() => { try {
|
|
415
435
|
return new URL(url).pathname;
|
|
@@ -418,7 +438,7 @@ class LLMProvider {
|
|
|
418
438
|
return '';
|
|
419
439
|
} })();
|
|
420
440
|
this.transportDiagnostic('loopback:start', pathname);
|
|
421
|
-
const local = await this.nodeHttpJson('POST', url, headers,
|
|
441
|
+
const local = await this.nodeHttpJson('POST', url, headers, bodyText(), signal, effectiveTimeout);
|
|
422
442
|
this.transportDiagnostic('loopback:complete', `status=${local.status} bytes=${Buffer.byteLength(local.body || '')}`);
|
|
423
443
|
return {
|
|
424
444
|
ok: local.status >= 200 && local.status < 300,
|
|
@@ -441,7 +461,7 @@ class LLMProvider {
|
|
|
441
461
|
const response = await this.providerFetch(url, {
|
|
442
462
|
method: 'POST',
|
|
443
463
|
headers,
|
|
444
|
-
body:
|
|
464
|
+
body: bodyText(),
|
|
445
465
|
signal: abort.signal,
|
|
446
466
|
}, true);
|
|
447
467
|
// Keep the caller's cancellation and explicit deadline attached through
|
|
@@ -462,7 +482,7 @@ class LLMProvider {
|
|
|
462
482
|
throw abortFailure(abort.signal);
|
|
463
483
|
if (!this.shouldUseNodeHttpFallback(e))
|
|
464
484
|
throw e;
|
|
465
|
-
const fallback = await this.nodeHttpJson('POST', url, headers,
|
|
485
|
+
const fallback = await this.nodeHttpJson('POST', url, headers, bodyText(), signal, effectiveTimeout);
|
|
466
486
|
return {
|
|
467
487
|
ok: fallback.status >= 200 && fallback.status < 300,
|
|
468
488
|
status: fallback.status,
|
|
@@ -890,7 +910,7 @@ class LLMProvider {
|
|
|
890
910
|
};
|
|
891
911
|
const effort = this.reasoningEffort(model, reasoningTier);
|
|
892
912
|
if (effort)
|
|
893
|
-
body.reasoning = { effort, summary: 'auto' };
|
|
913
|
+
body.reasoning = effort === 'none' ? { effort } : { effort, summary: 'auto' };
|
|
894
914
|
if (systemPrompt)
|
|
895
915
|
body.instructions = systemPrompt;
|
|
896
916
|
const convertedTools = this.responsesTools(tools);
|
|
@@ -1449,8 +1469,14 @@ class LLMProvider {
|
|
|
1449
1469
|
* protocol field. Prompt-only JSON requests must not be treated as evidence
|
|
1450
1470
|
* that a deployment supports structured output.
|
|
1451
1471
|
*/
|
|
1452
|
-
async chatStrictJson(model, messages, systemPrompt, temperature, maxTokens, schema, signal) {
|
|
1453
|
-
|
|
1472
|
+
async chatStrictJson(model, messages, systemPrompt, temperature, maxTokens, schema, signal, reasoningTier, schemaName = 'newmark_validation_probe', onUsage, reasoningControlRequired = false) {
|
|
1473
|
+
if (reasoningTier === 'none' && this.protocol() !== 'anthropic'
|
|
1474
|
+
&& (reasoningControlRequired || this.requiresNativeReasoningControl(model))) {
|
|
1475
|
+
const supportsNone = this.protocol() !== 'github_models' && this.supportsNativeNoReasoningEffort(model);
|
|
1476
|
+
if (!supportsNone) {
|
|
1477
|
+
throw new Error('The current model does not expose a native no-reasoning mode; JEV requires thinking off.');
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1454
1480
|
if (this.protocol() === 'anthropic') {
|
|
1455
1481
|
const { system, messages: anthropicMessages } = this.anthropicMessages(messages, systemPrompt);
|
|
1456
1482
|
const body = {
|
|
@@ -1462,26 +1488,34 @@ class LLMProvider {
|
|
|
1462
1488
|
format: { type: 'json_schema', schema },
|
|
1463
1489
|
},
|
|
1464
1490
|
};
|
|
1491
|
+
if (reasoningTier === 'none') {
|
|
1492
|
+
// Fail closed if the provider rejects disabled thinking for this
|
|
1493
|
+
// request; Auto routing has no alternate decision selector.
|
|
1494
|
+
body.thinking = { type: 'disabled' };
|
|
1495
|
+
}
|
|
1465
1496
|
if (system)
|
|
1466
1497
|
body.system = system;
|
|
1467
1498
|
const response = await this.postJsonWithFetchFallback((0, provider_request_compat_1.providerEndpoint)(this.cleanBaseUrl(), '/messages'), this.anthropicHeaders(), body, 120000, signal);
|
|
1468
1499
|
if (!response.ok)
|
|
1469
1500
|
throw new Error(this.llmErrorText(response, await response.text()));
|
|
1470
1501
|
const json = await response.json();
|
|
1502
|
+
onUsage?.((0, agentKernelDiagnostics_1.extractProviderUsage)(json, { protocol: 'anthropic' }));
|
|
1471
1503
|
return (json.content || [])
|
|
1472
1504
|
.filter(block => block.type === 'text' && block.text)
|
|
1473
1505
|
.map(block => this.extractTextValue(block.text))
|
|
1474
1506
|
.join('');
|
|
1475
1507
|
}
|
|
1476
1508
|
if (this.protocol() !== 'github_models' && this.openAITransportMode() === 'responses') {
|
|
1477
|
-
const body = this.responsesBody(model, messages, systemPrompt, temperature, maxTokens);
|
|
1509
|
+
const body = this.responsesBody(model, messages, systemPrompt, temperature, maxTokens, [], reasoningTier);
|
|
1478
1510
|
body.text = {
|
|
1479
1511
|
format: { type: 'json_schema', name: schemaName, strict: true, schema },
|
|
1480
1512
|
};
|
|
1481
1513
|
const response = await this.postJsonWithFetchFallback((0, provider_request_compat_1.providerEndpoint)(this.cleanBaseUrl(), '/responses'), this.openAIHeaders(), body, 120000, signal);
|
|
1482
1514
|
if (!response.ok)
|
|
1483
1515
|
throw new Error(this.llmErrorText(response, await response.text()));
|
|
1484
|
-
|
|
1516
|
+
const json = this.normalizeResponsesPayload(await response.json());
|
|
1517
|
+
onUsage?.((0, agentKernelDiagnostics_1.extractProviderUsage)(json));
|
|
1518
|
+
return this.extractResponsesText(json);
|
|
1485
1519
|
}
|
|
1486
1520
|
const isGitHubModels = this.protocol() === 'github_models';
|
|
1487
1521
|
const url = isGitHubModels
|
|
@@ -1500,10 +1534,20 @@ class LLMProvider {
|
|
|
1500
1534
|
json_schema: { name: schemaName, strict: true, schema },
|
|
1501
1535
|
},
|
|
1502
1536
|
};
|
|
1537
|
+
if (!isGitHubModels)
|
|
1538
|
+
this.applyChatReasoningEffort(body, model, reasoningTier);
|
|
1503
1539
|
const response = await this.postJsonWithFetchFallback(url, isGitHubModels ? this.githubModelsHeaders() : this.openAIHeaders(), body, 120000, signal);
|
|
1504
1540
|
if (!response.ok)
|
|
1505
1541
|
throw new Error(this.llmErrorText(response, await response.text()));
|
|
1506
|
-
|
|
1542
|
+
const json = await response.json();
|
|
1543
|
+
onUsage?.((0, agentKernelDiagnostics_1.extractProviderUsage)(json));
|
|
1544
|
+
return this.extractChatCompletionText(json);
|
|
1545
|
+
}
|
|
1546
|
+
requiresNativeReasoningControl(model) {
|
|
1547
|
+
return /(?:^|[/:])(?:gpt-5(?:[-.]|$)|o[134](?:[-.]|$))|(?:reasoner|reasoning|deepseek-r1|deepseek-reasoner|\br1\b)/i.test(model);
|
|
1548
|
+
}
|
|
1549
|
+
supportsNativeNoReasoningEffort(model) {
|
|
1550
|
+
return /(?:^|[/:])gpt-5\.(?:[1-9]\d*)(?:[-.]|$)/i.test(model);
|
|
1507
1551
|
}
|
|
1508
1552
|
/**
|
|
1509
1553
|
* Small streaming probe with explicit terminal-event evidence. Reading until
|
package/dist/main.js
CHANGED
|
@@ -361,6 +361,13 @@ function dispatchAgentWorkEvent(event, mirrorToMobile = true) {
|
|
|
361
361
|
}
|
|
362
362
|
const workEventCoalescer = new workEventCoalescer_1.WorkEventCoalescer(event => dispatchAgentWorkEvent(event, true));
|
|
363
363
|
const workEventNoMobileCoalescer = new workEventCoalescer_1.WorkEventCoalescer(event => dispatchAgentWorkEvent(event, false));
|
|
364
|
+
/**
|
|
365
|
+
* dev-0.6.6: transcript revisions a renderer has actually painted/cached, keyed
|
|
366
|
+
* by `webContentsId::workspaceId::conversationId`. Only what the renderer
|
|
367
|
+
* itself acknowledged is ever attached to a state request, so a slim snapshot
|
|
368
|
+
* can never be handed to a renderer that does not hold that exact revision.
|
|
369
|
+
*/
|
|
370
|
+
const transcriptRevisionAcks = new Map();
|
|
364
371
|
let projectConversationEvent = null;
|
|
365
372
|
function publishConversationList(workspace = agent?.workspace.current || null) {
|
|
366
373
|
if (agent && workspace)
|
|
@@ -2153,9 +2160,7 @@ else {
|
|
|
2153
2160
|
conversationLocked: false,
|
|
2154
2161
|
routeDecision: startupAgent.lastRouteDecision,
|
|
2155
2162
|
resolvedDeployment: startupAgent.activeDeployment(),
|
|
2156
|
-
|
|
2157
|
-
&& startupAgent.lastRouteDecision?.requestedSelection.kind === 'auto'
|
|
2158
|
-
&& !!startupAgent.lastRouteDecision.resolvedDeployment,
|
|
2163
|
+
jevDecisionModel: startupAgent.jevDecisionModelState(),
|
|
2159
2164
|
};
|
|
2160
2165
|
};
|
|
2161
2166
|
const ensureStartupAutomation = async () => {
|
|
@@ -2804,7 +2809,22 @@ else {
|
|
|
2804
2809
|
});
|
|
2805
2810
|
const ensureElectronUtilityPool = () => {
|
|
2806
2811
|
if (!electronUtilityRuntimePool) {
|
|
2807
|
-
electronUtilityRuntimePool = new electronUtilityRuntimePool_1.ElectronUtilityRuntimePool(root, ensureElectronUtilityRuntimeHost()
|
|
2812
|
+
electronUtilityRuntimePool = new electronUtilityRuntimePool_1.ElectronUtilityRuntimePool(root, ensureElectronUtilityRuntimeHost(), undefined, {
|
|
2813
|
+
// dev-0.6.6: per-conversation visual budget. The pool recycles an idle
|
|
2814
|
+
// host whose resident memory grew past the machine-derived watermark
|
|
2815
|
+
// (long ComputerUse/BrowserControl conversations) instead of reusing a
|
|
2816
|
+
// process that is already close to a V8 abort.
|
|
2817
|
+
sampleProcessMemoryBytes: pid => {
|
|
2818
|
+
try {
|
|
2819
|
+
const metric = electron_1.app.getAppMetrics().find(item => Number(item.pid) === Number(pid));
|
|
2820
|
+
// Electron reports workingSetSize in kilobytes.
|
|
2821
|
+
return metric && metric.memory ? Math.round(Number(metric.memory.workingSetSize || 0) * 1024) : 0;
|
|
2822
|
+
}
|
|
2823
|
+
catch {
|
|
2824
|
+
return 0;
|
|
2825
|
+
}
|
|
2826
|
+
},
|
|
2827
|
+
});
|
|
2808
2828
|
electronUtilityRuntimePool.subscribe(event => broadcastAgentWorkEvent(event));
|
|
2809
2829
|
electronUtilityRuntimePool.setHostToolHandler(utilityHostToolHandler);
|
|
2810
2830
|
}
|
|
@@ -3972,6 +3992,18 @@ else {
|
|
|
3972
3992
|
const target = conversationRuntimeTarget(targetInput || agent.activeConversationId || 'default');
|
|
3973
3993
|
return (await applyConversationAction(target, 'goal_clear')).cleared;
|
|
3974
3994
|
});
|
|
3995
|
+
// dev-0.6.6: a renderer reports the transcript revision it just painted (or
|
|
3996
|
+
// `''` to clear, e.g. on reload). Nothing else may set this value.
|
|
3997
|
+
electron_1.ipcMain.handle('conversation:ackTranscript', (event, targetInput, revision) => {
|
|
3998
|
+
const target = conversationRuntimeTarget(targetInput || agent?.activeConversationId || 'default');
|
|
3999
|
+
const key = `${event.sender.id}::${String(target.workspaceId || '')}::${String(target.conversationId || '')}`;
|
|
4000
|
+
const value = typeof revision === 'string' ? revision.slice(0, 200) : '';
|
|
4001
|
+
if (value)
|
|
4002
|
+
transcriptRevisionAcks.set(key, value);
|
|
4003
|
+
else
|
|
4004
|
+
transcriptRevisionAcks.delete(key);
|
|
4005
|
+
return true;
|
|
4006
|
+
});
|
|
3975
4007
|
electron_1.ipcMain.handle('agent:getState', async (event, targetInput) => {
|
|
3976
4008
|
const startupPrewarmRequest = isStartupPrewarmSender(event);
|
|
3977
4009
|
if (startupPrewarmRequest && !agent && startupAgentReady)
|
|
@@ -3989,11 +4021,19 @@ else {
|
|
|
3989
4021
|
: {});
|
|
3990
4022
|
const requestedWindow = Math.max(1, Math.floor(Number(windowInput.window) || 0) || 200);
|
|
3991
4023
|
const requestedBefore = windowInput.before == null ? undefined : Math.max(0, Math.floor(Number(windowInput.before) || 0));
|
|
4024
|
+
// dev-0.6.6 long-run memory reclamation: attach the transcript revision
|
|
4025
|
+
// this exact renderer acknowledged, so the runtime can answer with a slim
|
|
4026
|
+
// snapshot instead of re-serializing the whole conversation. The revision
|
|
4027
|
+
// is only ever set from that renderer's own acknowledgement, and a caller
|
|
4028
|
+
// that holds nothing simply never matches.
|
|
4029
|
+
const acknowledgedRevision = transcriptRevisionAcks.get(`${event.sender.id}::${String(target.workspaceId || '')}::${String(target.conversationId || '')}`);
|
|
3992
4030
|
let conversationSnapshot;
|
|
3993
4031
|
try {
|
|
3994
4032
|
conversationSnapshot = startupPrewarmRequest
|
|
3995
4033
|
? localConversationSnapshotForStartup(target, { window: requestedWindow, before: requestedBefore })
|
|
3996
|
-
: await runtimeSnapshotForTarget(target,
|
|
4034
|
+
: await runtimeSnapshotForTarget(target, acknowledgedRevision
|
|
4035
|
+
? { window: requestedWindow, before: requestedBefore, transcript: 'auto', transcriptRevision: acknowledgedRevision }
|
|
4036
|
+
: { window: requestedWindow, before: requestedBefore });
|
|
3997
4037
|
}
|
|
3998
4038
|
catch (error) {
|
|
3999
4039
|
conversationSnapshot = {
|
|
@@ -4015,6 +4055,7 @@ else {
|
|
|
4015
4055
|
modelLabel: agent.modelLabel(),
|
|
4016
4056
|
resolvedDeployment: agent.activeDeployment(),
|
|
4017
4057
|
routeDecision: agent.lastRouteDecision,
|
|
4058
|
+
jevDecisionModel: agent.jevDecisionModelState(),
|
|
4018
4059
|
intelligence: agent.intelligence,
|
|
4019
4060
|
...conversationSnapshot,
|
|
4020
4061
|
chatMessages: conversationSnapshot.chatMessages || [],
|
|
@@ -4247,11 +4288,7 @@ else {
|
|
|
4247
4288
|
agent.config.set('models', 'fallback_on_unavailable', value === true || value === 'on');
|
|
4248
4289
|
break;
|
|
4249
4290
|
case 'switchTendency':
|
|
4250
|
-
agent.config.set('models', 'auto_switch_preference', value);
|
|
4251
|
-
break;
|
|
4252
|
-
case 'clearLearnedAutoPreferences':
|
|
4253
|
-
if (value === true)
|
|
4254
|
-
agent.clearLearnedModelPreferences();
|
|
4291
|
+
agent.config.set('models', 'auto_switch_preference', ['conservative', 'balanced', 'aggressive'].includes(String(value)) ? String(value) : 'balanced');
|
|
4255
4292
|
break;
|
|
4256
4293
|
case 'openAIApiMode':
|
|
4257
4294
|
agent.config.set('models', 'openai_api_mode', ['chat_stream', 'chat', 'responses'].includes(String(value)) ? value : 'chat_stream');
|
|
@@ -4482,17 +4519,38 @@ else {
|
|
|
4482
4519
|
? await ensureWslConversationPool().contextCompress(target, options)
|
|
4483
4520
|
: await ensureElectronUtilityPool().contextCompress(target, options);
|
|
4484
4521
|
});
|
|
4485
|
-
|
|
4522
|
+
/** Current-model JEV policy editor; the Agent remains authoritative. */
|
|
4523
|
+
electron_1.ipcMain.handle('agent:setJevDecisionModel', async (_event, request) => {
|
|
4486
4524
|
if (!agent)
|
|
4487
|
-
return { ok: false, reason: '
|
|
4488
|
-
const
|
|
4489
|
-
const
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4525
|
+
return { ok: false, reason: 'auto_disabled', state: null };
|
|
4526
|
+
const patch = request && typeof request === 'object' ? request : {};
|
|
4527
|
+
const result = agent.updateJevDecisionModel({
|
|
4528
|
+
...(patch.enabled === undefined ? {} : { enabled: patch.enabled === true }),
|
|
4529
|
+
...(patch.timeoutMs === undefined ? {} : { timeoutMs: Number(patch.timeoutMs) }),
|
|
4530
|
+
});
|
|
4531
|
+
if (result.ok) {
|
|
4532
|
+
resetConversationKernel();
|
|
4533
|
+
// Every runtime (main, Electron Utility, WSL) uses the same current
|
|
4534
|
+
// conversation-model decision policy.
|
|
4535
|
+
const propagated = [
|
|
4536
|
+
['jev_enabled', result.state.enabled],
|
|
4537
|
+
['jev_timeout_ms', result.state.timeoutMs],
|
|
4538
|
+
];
|
|
4539
|
+
for (const pool of [electronUtilityRuntimePool, wslAgentRuntimePool]) {
|
|
4540
|
+
if (!pool)
|
|
4541
|
+
continue;
|
|
4542
|
+
for (const [key, value] of propagated) {
|
|
4543
|
+
try {
|
|
4544
|
+
await pool.updateSetting('models', key, value);
|
|
4545
|
+
}
|
|
4546
|
+
catch {
|
|
4547
|
+
// A runtime that is not currently running picks the value up from
|
|
4548
|
+
// the shared config on its next start.
|
|
4549
|
+
}
|
|
4550
|
+
}
|
|
4551
|
+
}
|
|
4552
|
+
}
|
|
4553
|
+
return result;
|
|
4496
4554
|
});
|
|
4497
4555
|
electron_1.ipcMain.handle('agent:stopConversation', async (_event, request) => {
|
|
4498
4556
|
if (!agent)
|
|
@@ -4655,19 +4713,38 @@ else {
|
|
|
4655
4713
|
try {
|
|
4656
4714
|
// Archive is a destructive lifecycle command. It intentionally
|
|
4657
4715
|
// bypasses the normal mutation/active-prompt guard. Cancel the
|
|
4658
|
-
// conversation-local Flow synchronously, then
|
|
4659
|
-
//
|
|
4660
|
-
// the
|
|
4716
|
+
// conversation-local Flow synchronously, then release the occupancy
|
|
4717
|
+
// the conversation is holding -- the hard stop of its resident
|
|
4718
|
+
// runtime plus the kernel's own abort bookkeeping -- and let those
|
|
4719
|
+
// teardown paths run *in parallel* instead of one after the other.
|
|
4720
|
+
//
|
|
4721
|
+
// Releasing the occupancy is what ends the wait for a wedged run:
|
|
4722
|
+
// the hard stop invalidates the in-flight request, so the run really
|
|
4723
|
+
// settles instead of the archive giving up on it after some budget
|
|
4724
|
+
// (and abandoning its transcript to a later resurrecting flush).
|
|
4661
4725
|
const ownedFlow = activeFlowStateFor(normalized);
|
|
4662
|
-
const
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4726
|
+
const kernelOwned = mainConversationOwners.has(normalized.runtimeKey);
|
|
4727
|
+
const targetRuntime = kernelOwned
|
|
4728
|
+
? { resident: true, running: false, stopping: false, connected: true }
|
|
4729
|
+
: peekTargetRuntime(normalized);
|
|
4730
|
+
// An idle runtime has nothing to settle, so its stop stays off the
|
|
4731
|
+
// critical path; a running (or already stopping) one must be down and
|
|
4732
|
+
// checkpointed before the writer snapshots the store.
|
|
4733
|
+
const occupancyMustSettle = !kernelOwned && (targetRuntime.running === true || targetRuntime.stopping === true);
|
|
4734
|
+
const occupancyRelease = forceStopTargetRuntime(normalized).catch(error => {
|
|
4669
4735
|
console.error('[Newmark] archive runtime force-stop failed:', error instanceof Error ? error.message : String(error));
|
|
4670
4736
|
});
|
|
4737
|
+
const mainOwnerPreparation = kernelOwned
|
|
4738
|
+
? ensureConversationKernel(root).prepareForArchive(normalized, {
|
|
4739
|
+
releaseOccupancy: () => occupancyRelease,
|
|
4740
|
+
})
|
|
4741
|
+
: Promise.resolve(null);
|
|
4742
|
+
interruptActiveFlowForArchive(normalized);
|
|
4743
|
+
const [mainArchiveOwner] = await Promise.all([
|
|
4744
|
+
mainOwnerPreparation,
|
|
4745
|
+
occupancyMustSettle ? occupancyRelease : Promise.resolve(),
|
|
4746
|
+
ownedFlow?.settlement ?? Promise.resolve(),
|
|
4747
|
+
]);
|
|
4671
4748
|
const currentWorkspacePath = path.resolve(agent.workspace.current?.path || '');
|
|
4672
4749
|
const targetWorkspacePath = path.resolve(normalized.workspace?.path || '');
|
|
4673
4750
|
const ownsTargetWorkspace = !!normalized.workspace
|
package/dist/preload.js
CHANGED
|
@@ -15,7 +15,7 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
15
15
|
queueAction: (action, input, target) => ipcRenderer.invoke('agent:queueAction', action, input, target),
|
|
16
16
|
checkpointConversation: (request) => ipcRenderer.invoke('agent:checkpointConversation', request),
|
|
17
17
|
compressContext: (request) => ipcRenderer.invoke('agent:compressContext', request),
|
|
18
|
-
|
|
18
|
+
setJevDecisionModel: (request) => ipcRenderer.invoke('agent:setJevDecisionModel', request),
|
|
19
19
|
stopConversation: (request) => ipcRenderer.invoke('agent:stopConversation', request),
|
|
20
20
|
setWorkRunExpanded: (request) => ipcRenderer.invoke('agent:setWorkRunExpanded', request),
|
|
21
21
|
sendPrompt: (message, _model) => ipcRenderer.invoke('agent:sendPrompt', message),
|
|
@@ -30,6 +30,8 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
30
30
|
clearGoal: (target) => ipcRenderer.invoke('agent:clearGoal', target),
|
|
31
31
|
ensureConversation: (target) => ipcRenderer.invoke('agent:ensureConversation', target),
|
|
32
32
|
getState: (target) => ipcRenderer.invoke('agent:getState', target),
|
|
33
|
+
/** dev-0.6.6: report (or clear with '') the transcript revision this renderer holds. */
|
|
34
|
+
ackTranscript: (target, revision) => ipcRenderer.invoke('conversation:ackTranscript', target, revision),
|
|
33
35
|
getConversationDraft: (conversationId) => ipcRenderer.invoke('conversation:getDraft', conversationId),
|
|
34
36
|
saveConversationDraft: (conversationId, draft) => ipcRenderer.invoke('conversation:saveDraft', conversationId, draft),
|
|
35
37
|
getConversationPlan: (conversationId) => ipcRenderer.invoke('agent:getConversationPlan', conversationId),
|