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
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export type BrowserControlAction = 'open' | 'snapshot' | 'click' | 'type' | 'eval' | 'back' | 'forward' | 'reload' | 'cdp' | 'use';
|
|
2
|
+
/** Renderer/host routing identity. Browser controls must never fall back to the
|
|
3
|
+
* currently focused conversation when a caller has a concrete target. */
|
|
4
|
+
export interface BrowserControlTarget {
|
|
5
|
+
workspaceId?: string;
|
|
6
|
+
conversationId?: string;
|
|
7
|
+
runtimeKey?: string;
|
|
8
|
+
}
|
|
2
9
|
export interface BrowserControlRequest {
|
|
3
10
|
action: BrowserControlAction;
|
|
11
|
+
target?: BrowserControlTarget;
|
|
4
12
|
url?: string;
|
|
5
13
|
selector?: string;
|
|
6
14
|
text?: string;
|
|
@@ -22,6 +22,8 @@ export interface BrowserUseHostPage {
|
|
|
22
22
|
}>;
|
|
23
23
|
evaluateFixed<T>(script: string, signal?: AbortSignal): Promise<T>;
|
|
24
24
|
clickAt(x: number, y: number, signal?: AbortSignal): Promise<void>;
|
|
25
|
+
/** Optional deterministic DOM click for embedded guests where native input can be dropped while the host tab is settling. */
|
|
26
|
+
clickElement?(token: string, signal?: AbortSignal): Promise<void>;
|
|
25
27
|
replaceFocusedText(text: string, signal?: AbortSignal): Promise<void>;
|
|
26
28
|
pressKey(key: string, signal?: AbortSignal): Promise<void>;
|
|
27
29
|
navigate(url: string, signal?: AbortSignal): Promise<void>;
|
|
@@ -46,6 +48,7 @@ export type BrowserUseHostPageResolver = (scope: BrowserUseScope) => Promise<Bro
|
|
|
46
48
|
export declare function browserUseObservationScript(maxChars: number, maxRefs: number): string;
|
|
47
49
|
export declare function browserUseProbeScript(token: string, scrollIntoView?: boolean): string;
|
|
48
50
|
export declare function browserUseFocusScript(token: string): string;
|
|
51
|
+
export declare function browserUseClickScript(token: string): string;
|
|
49
52
|
export declare function browserUseSelectScript(token: string, value: string): string;
|
|
50
53
|
export declare function browserUseScrollScript(token: string | undefined, deltaX: number, deltaY: number): string;
|
|
51
54
|
export declare function browserUseExtractScript(token: string | undefined, attribute: string | undefined, maxChars: number): string;
|
|
@@ -4,6 +4,7 @@ exports.NativeBrowserUsePageAdapter = void 0;
|
|
|
4
4
|
exports.browserUseObservationScript = browserUseObservationScript;
|
|
5
5
|
exports.browserUseProbeScript = browserUseProbeScript;
|
|
6
6
|
exports.browserUseFocusScript = browserUseFocusScript;
|
|
7
|
+
exports.browserUseClickScript = browserUseClickScript;
|
|
7
8
|
exports.browserUseSelectScript = browserUseSelectScript;
|
|
8
9
|
exports.browserUseScrollScript = browserUseScrollScript;
|
|
9
10
|
exports.browserUseExtractScript = browserUseExtractScript;
|
|
@@ -186,6 +187,17 @@ function browserUseProbeScript(token, scrollIntoView = false) {
|
|
|
186
187
|
function browserUseFocusScript(token) {
|
|
187
188
|
return `(() => { const el = document.querySelector(${json(token)}); if (!el) return false; if (typeof el.scrollIntoView === 'function') el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'auto' }); el.focus({ preventScroll: true }); return document.activeElement === el || el.contains(document.activeElement); })()`;
|
|
188
189
|
}
|
|
190
|
+
function browserUseClickScript(token) {
|
|
191
|
+
return `(() => {
|
|
192
|
+
const el = document.querySelector(${json(token)});
|
|
193
|
+
if (!el) return { clicked: false, error: 'ref_not_found' };
|
|
194
|
+
if (typeof el.scrollIntoView === 'function') el.scrollIntoView({ block: 'center', inline: 'nearest', behavior: 'auto' });
|
|
195
|
+
if (typeof el.focus === 'function') el.focus({ preventScroll: true });
|
|
196
|
+
if (typeof el.click !== 'function') return { clicked: false, error: 'not_clickable' };
|
|
197
|
+
el.click();
|
|
198
|
+
return { clicked: true, tag: String(el.localName || 'element').toLowerCase() };
|
|
199
|
+
})()`;
|
|
200
|
+
}
|
|
189
201
|
function browserUseSelectScript(token, value) {
|
|
190
202
|
return `(() => {
|
|
191
203
|
const el = document.querySelector(${json(token)});
|
|
@@ -299,8 +311,13 @@ class NativeBrowserUsePageAdapter {
|
|
|
299
311
|
throw error;
|
|
300
312
|
}
|
|
301
313
|
if (request.action === 'click') {
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
if (page.clickElement) {
|
|
315
|
+
await page.clickElement(request.element.token, signal);
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const rect = probe.rect;
|
|
319
|
+
await page.clickAt(rect.x + rect.width / 2, rect.y + rect.height / 2, signal);
|
|
320
|
+
}
|
|
304
321
|
await page.waitForReady(signal);
|
|
305
322
|
return { clicked: true };
|
|
306
323
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide Computer-Use session state.
|
|
3
|
+
*
|
|
4
|
+
* The state is keyed by conversation runtime, not by Build/run id. A Build may finish
|
|
5
|
+
* or be interrupted/replaced while the conversation keeps its
|
|
6
|
+
* Computer-Use switch and lease. Only an explicit stop/toggle-off or runtime
|
|
7
|
+
* teardown releases it.
|
|
8
|
+
*/
|
|
9
|
+
export interface ComputerUseSessionScope {
|
|
10
|
+
runtimeKey: string;
|
|
11
|
+
ownerLabel: string;
|
|
12
|
+
workspacePath?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ComputerUseSessionState {
|
|
15
|
+
runtimeKey: string;
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
occupied: boolean;
|
|
18
|
+
ownerLabel?: string;
|
|
19
|
+
updatedAt?: number;
|
|
20
|
+
}
|
|
21
|
+
export declare const COMPUTER_USE_OCCUPIED_MARKER = "computerUse occupied";
|
|
22
|
+
export declare const COMPUTER_USE_LOCK_TTL_MS: number;
|
|
23
|
+
export declare class ComputerUseSessionRegistry {
|
|
24
|
+
private readonly ttlMs;
|
|
25
|
+
private readonly enabledByRuntime;
|
|
26
|
+
private activeLease;
|
|
27
|
+
constructor(ttlMs?: number);
|
|
28
|
+
authorize(action: string, scope: ComputerUseSessionScope, dryRun?: boolean): string | null;
|
|
29
|
+
complete(action: string, scope: ComputerUseSessionScope): void;
|
|
30
|
+
setEnabled(scope: ComputerUseSessionScope, enabled: boolean): {
|
|
31
|
+
ok: true;
|
|
32
|
+
state: ComputerUseSessionState;
|
|
33
|
+
} | {
|
|
34
|
+
ok: false;
|
|
35
|
+
error: string;
|
|
36
|
+
state: ComputerUseSessionState;
|
|
37
|
+
};
|
|
38
|
+
state(runtimeKey: string): ComputerUseSessionState;
|
|
39
|
+
cancelTarget(runtimeKey: string): boolean;
|
|
40
|
+
private clearExpired;
|
|
41
|
+
private occupiedError;
|
|
42
|
+
}
|
|
43
|
+
export declare const defaultComputerUseSessionRegistry: ComputerUseSessionRegistry;
|
|
44
|
+
//# sourceMappingURL=computerUseSession.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultComputerUseSessionRegistry = exports.ComputerUseSessionRegistry = exports.COMPUTER_USE_LOCK_TTL_MS = exports.COMPUTER_USE_OCCUPIED_MARKER = void 0;
|
|
4
|
+
exports.COMPUTER_USE_OCCUPIED_MARKER = 'computerUse occupied';
|
|
5
|
+
exports.COMPUTER_USE_LOCK_TTL_MS = 10 * 60 * 1000;
|
|
6
|
+
class ComputerUseSessionRegistry {
|
|
7
|
+
ttlMs;
|
|
8
|
+
enabledByRuntime = new Map();
|
|
9
|
+
activeLease = null;
|
|
10
|
+
constructor(ttlMs = exports.COMPUTER_USE_LOCK_TTL_MS) {
|
|
11
|
+
this.ttlMs = ttlMs;
|
|
12
|
+
}
|
|
13
|
+
authorize(action, scope, dryRun = false) {
|
|
14
|
+
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
15
|
+
const runtimeKey = String(scope.runtimeKey || '').trim() || 'conversation:default';
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
this.clearExpired(now);
|
|
18
|
+
if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
|
|
19
|
+
return this.occupiedError(normalizedAction, scope.ownerLabel, this.activeLease.ownerLabel);
|
|
20
|
+
}
|
|
21
|
+
if (normalizedAction === 'takeover_stop')
|
|
22
|
+
return null;
|
|
23
|
+
const enabled = this.enabledByRuntime.get(runtimeKey) !== false;
|
|
24
|
+
const readOnly = normalizedAction === 'observe' || normalizedAction === 'app_list' || normalizedAction === 'app_observe' || normalizedAction === 'wait';
|
|
25
|
+
if (!enabled && !readOnly && !dryRun && normalizedAction !== 'takeover_start') {
|
|
26
|
+
return JSON.stringify({
|
|
27
|
+
ok: false,
|
|
28
|
+
action: normalizedAction,
|
|
29
|
+
error: `ComputerUse is disabled for ${scope.ownerLabel}. Enable ComputerUse for this conversation before sending desktop operations.`,
|
|
30
|
+
computer_use_enabled: false,
|
|
31
|
+
requested_owner: scope.ownerLabel,
|
|
32
|
+
}, null, 2);
|
|
33
|
+
}
|
|
34
|
+
if (normalizedAction === 'takeover_start')
|
|
35
|
+
this.enabledByRuntime.set(runtimeKey, true);
|
|
36
|
+
if (!this.activeLease) {
|
|
37
|
+
this.activeLease = { ...scope, runtimeKey, updatedAt: now };
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.activeLease.updatedAt = now;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
complete(action, scope) {
|
|
45
|
+
const normalizedAction = String(action || '').trim().toLowerCase();
|
|
46
|
+
const runtimeKey = String(scope.runtimeKey || '').trim() || 'conversation:default';
|
|
47
|
+
if (normalizedAction === 'takeover_stop') {
|
|
48
|
+
if (!this.activeLease || this.activeLease.runtimeKey === runtimeKey)
|
|
49
|
+
this.activeLease = null;
|
|
50
|
+
this.enabledByRuntime.set(runtimeKey, false);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (this.activeLease?.runtimeKey === runtimeKey)
|
|
54
|
+
this.activeLease.updatedAt = Date.now();
|
|
55
|
+
}
|
|
56
|
+
setEnabled(scope, enabled) {
|
|
57
|
+
const runtimeKey = String(scope.runtimeKey || '').trim() || 'conversation:default';
|
|
58
|
+
this.clearExpired();
|
|
59
|
+
if (this.activeLease && this.activeLease.runtimeKey !== runtimeKey) {
|
|
60
|
+
return { ok: false, error: this.occupiedError('toggle', scope.ownerLabel, this.activeLease.ownerLabel), state: this.state(runtimeKey) };
|
|
61
|
+
}
|
|
62
|
+
this.enabledByRuntime.set(runtimeKey, enabled !== false);
|
|
63
|
+
if (enabled === false && this.activeLease?.runtimeKey === runtimeKey)
|
|
64
|
+
this.activeLease = null;
|
|
65
|
+
return { ok: true, state: this.state(runtimeKey) };
|
|
66
|
+
}
|
|
67
|
+
state(runtimeKey) {
|
|
68
|
+
const key = String(runtimeKey || '').trim() || 'conversation:default';
|
|
69
|
+
this.clearExpired();
|
|
70
|
+
const lease = this.activeLease;
|
|
71
|
+
return {
|
|
72
|
+
runtimeKey: key,
|
|
73
|
+
enabled: this.enabledByRuntime.get(key) !== false,
|
|
74
|
+
occupied: !!lease,
|
|
75
|
+
...(lease ? { ownerLabel: lease.ownerLabel, updatedAt: lease.updatedAt } : {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
cancelTarget(runtimeKey) {
|
|
79
|
+
const key = String(runtimeKey || '').trim();
|
|
80
|
+
if (!key)
|
|
81
|
+
return false;
|
|
82
|
+
const hadActiveLease = this.activeLease?.runtimeKey === key;
|
|
83
|
+
if (hadActiveLease)
|
|
84
|
+
this.activeLease = null;
|
|
85
|
+
this.enabledByRuntime.set(key, false);
|
|
86
|
+
return hadActiveLease;
|
|
87
|
+
}
|
|
88
|
+
clearExpired(now = Date.now()) {
|
|
89
|
+
if (this.activeLease && now - this.activeLease.updatedAt > this.ttlMs) {
|
|
90
|
+
this.activeLease = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
occupiedError(action, requestedOwner, activeOwner) {
|
|
94
|
+
return JSON.stringify({
|
|
95
|
+
ok: false,
|
|
96
|
+
action,
|
|
97
|
+
error: `${exports.COMPUTER_USE_OCCUPIED_MARKER}: ComputerUse is already active in ${activeOwner}. Stop it with computer_use takeover_stop or wait before another conversation takes control.`,
|
|
98
|
+
lock_owner: activeOwner,
|
|
99
|
+
requested_owner: requestedOwner,
|
|
100
|
+
}, null, 2);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.ComputerUseSessionRegistry = ComputerUseSessionRegistry;
|
|
104
|
+
exports.defaultComputerUseSessionRegistry = new ComputerUseSessionRegistry();
|
|
105
|
+
//# sourceMappingURL=computerUseSession.js.map
|
package/dist/core/config.d.ts
CHANGED
|
@@ -81,7 +81,10 @@ export declare class ConfigManager {
|
|
|
81
81
|
rootPath: string;
|
|
82
82
|
private config;
|
|
83
83
|
private workspaceOverrides;
|
|
84
|
-
|
|
84
|
+
private readonly readOnly;
|
|
85
|
+
constructor(rootPath: string, options?: {
|
|
86
|
+
readOnly?: boolean;
|
|
87
|
+
});
|
|
85
88
|
reload(): void;
|
|
86
89
|
private load;
|
|
87
90
|
get<T = unknown>(section: string, key: string): T | undefined;
|
|
@@ -153,7 +156,9 @@ export interface ModelValidationSummary {
|
|
|
153
156
|
message: string;
|
|
154
157
|
};
|
|
155
158
|
}
|
|
156
|
-
export declare function ensureRootConfig(rootPath: string
|
|
159
|
+
export declare function ensureRootConfig(rootPath: string, options?: {
|
|
160
|
+
readOnly?: boolean;
|
|
161
|
+
}): void;
|
|
157
162
|
export declare function inferProviderProtocol(name: string, baseUrl: string): ProviderProtocol;
|
|
158
163
|
export declare function normalizeProviderProtocol(value: unknown, name: string, baseUrl: string): ProviderProtocol;
|
|
159
164
|
export declare function defaultProviderBaseUrl(protocol: ProviderProtocol): string;
|
package/dist/core/config.js
CHANGED
|
@@ -64,8 +64,10 @@ class ConfigManager {
|
|
|
64
64
|
rootPath;
|
|
65
65
|
config;
|
|
66
66
|
workspaceOverrides;
|
|
67
|
-
|
|
67
|
+
readOnly;
|
|
68
|
+
constructor(rootPath, options = {}) {
|
|
68
69
|
this.rootPath = rootPath;
|
|
70
|
+
this.readOnly = options.readOnly === true;
|
|
69
71
|
this.workspaceOverrides = new Map();
|
|
70
72
|
this.config = this.load();
|
|
71
73
|
}
|
|
@@ -80,6 +82,8 @@ class ConfigManager {
|
|
|
80
82
|
const raw = JSON.parse(readJsonText(cp));
|
|
81
83
|
const normalized = normalizeConfigShape(raw, true);
|
|
82
84
|
if (isCorruptConfig(raw, normalized)) {
|
|
85
|
+
if (this.readOnly)
|
|
86
|
+
return defaultConfig();
|
|
83
87
|
this.backupConfig(cp, 'invalid-shape');
|
|
84
88
|
return this.writeRecoveredConfig(cp);
|
|
85
89
|
}
|
|
@@ -87,7 +91,8 @@ class ConfigManager {
|
|
|
87
91
|
// Provider ids are routing identities, so legacy/malformed catalogs must
|
|
88
92
|
// not wait for an unrelated settings save before becoming collision-safe.
|
|
89
93
|
try {
|
|
90
|
-
|
|
94
|
+
if (!this.readOnly)
|
|
95
|
+
fs.writeFileSync(cp, JSON.stringify(normalized, null, 2), 'utf-8');
|
|
91
96
|
}
|
|
92
97
|
catch {
|
|
93
98
|
// A read-only root may still be used for this process. The normalized
|
|
@@ -97,6 +102,8 @@ class ConfigManager {
|
|
|
97
102
|
return normalized;
|
|
98
103
|
}
|
|
99
104
|
catch {
|
|
105
|
+
if (this.readOnly)
|
|
106
|
+
return defaultConfig();
|
|
100
107
|
this.backupConfig(cp, 'invalid-json');
|
|
101
108
|
return this.writeRecoveredConfig(cp);
|
|
102
109
|
}
|
|
@@ -146,10 +153,14 @@ class ConfigManager {
|
|
|
146
153
|
this.config[section][key] = { value: normalizedValue };
|
|
147
154
|
}
|
|
148
155
|
save() {
|
|
156
|
+
if (this.readOnly)
|
|
157
|
+
return;
|
|
149
158
|
const j = JSON.stringify(this.config, null, 2);
|
|
150
159
|
fs.writeFileSync(path.join(this.rootPath, 'config.json'), j, 'utf-8');
|
|
151
160
|
}
|
|
152
161
|
saveTo(targetPath) {
|
|
162
|
+
if (this.readOnly)
|
|
163
|
+
return;
|
|
153
164
|
const j = JSON.stringify(this.config, null, 2);
|
|
154
165
|
fs.writeFileSync(targetPath, j, 'utf-8');
|
|
155
166
|
}
|
|
@@ -393,12 +404,16 @@ class ConfigManager {
|
|
|
393
404
|
return providers;
|
|
394
405
|
}
|
|
395
406
|
writeRecoveredConfig(configPath) {
|
|
407
|
+
if (this.readOnly)
|
|
408
|
+
return defaultConfig();
|
|
396
409
|
const config = loadExampleConfig();
|
|
397
410
|
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
398
411
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
399
412
|
return config;
|
|
400
413
|
}
|
|
401
414
|
backupConfig(configPath, reason) {
|
|
415
|
+
if (this.readOnly)
|
|
416
|
+
return;
|
|
402
417
|
try {
|
|
403
418
|
if (!fs.existsSync(configPath))
|
|
404
419
|
return;
|
|
@@ -412,13 +427,13 @@ class ConfigManager {
|
|
|
412
427
|
}
|
|
413
428
|
}
|
|
414
429
|
exports.ConfigManager = ConfigManager;
|
|
415
|
-
function ensureRootConfig(rootPath) {
|
|
430
|
+
function ensureRootConfig(rootPath, options = {}) {
|
|
416
431
|
const configPath = path.join(rootPath, 'config.json');
|
|
417
432
|
if (fs.existsSync(configPath)) {
|
|
418
|
-
new ConfigManager(rootPath);
|
|
433
|
+
new ConfigManager(rootPath, options);
|
|
419
434
|
return;
|
|
420
435
|
}
|
|
421
|
-
new ConfigManager(rootPath).save();
|
|
436
|
+
new ConfigManager(rootPath, options).save();
|
|
422
437
|
}
|
|
423
438
|
function normalizeConfigShape(raw, withDefaults) {
|
|
424
439
|
const base = withDefaults ? defaultConfig() : {};
|
|
@@ -827,7 +842,10 @@ function defaultConfig() {
|
|
|
827
842
|
general: {
|
|
828
843
|
tone: { _description: "Conversation style", _type: "choice", _values: ["strict_simple", "casual_friendly"], value: "strict_simple" },
|
|
829
844
|
language: { _description: "Default language", _type: "choice", _values: ["en", "zh", "auto"], value: "auto" },
|
|
830
|
-
|
|
845
|
+
// A first-run desktop window must have a deterministic close/exit
|
|
846
|
+
// contract. Users who explicitly choose minimize-to-tray keep that
|
|
847
|
+
// choice, but a fresh install must not hide the process on OS close.
|
|
848
|
+
close_behavior: { _description: "Close behavior", _type: "choice", _values: ["minimize", "exit"], value: "exit" },
|
|
831
849
|
default_input: { _description: "Default input mode", _type: "choice", _values: ["guide", "next"], value: "guide" },
|
|
832
850
|
auto_archive_on_close: { _description: "Auto archive on close", _type: "boolean", value: true },
|
|
833
851
|
},
|
|
@@ -212,6 +212,7 @@ export declare class ConversationKernel {
|
|
|
212
212
|
private processTimeoutMs;
|
|
213
213
|
private runtime;
|
|
214
214
|
private scheduleGoalContinuation;
|
|
215
|
+
private schedulePendingRuntimeContinuation;
|
|
215
216
|
private startGoalDrivenBuild;
|
|
216
217
|
private createRunner;
|
|
217
218
|
private enqueueRootInboxWake;
|
|
@@ -217,6 +217,7 @@ class ConversationKernel {
|
|
|
217
217
|
runtime.runner.recordGuideReceipt(deferred);
|
|
218
218
|
this.emitQueueUpdate(runtime);
|
|
219
219
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
220
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
220
221
|
return deferred;
|
|
221
222
|
}
|
|
222
223
|
if (runtime.guideAcceptanceClosedRunId === runtime.runId) {
|
|
@@ -248,6 +249,7 @@ class ConversationKernel {
|
|
|
248
249
|
createdAt: deferred.createdAt,
|
|
249
250
|
}]);
|
|
250
251
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
252
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
251
253
|
return deferred;
|
|
252
254
|
}
|
|
253
255
|
const queued = runtime.runner.queueActiveKernelMessage(safeEnvelope.text, 'steer', clientMessageId, runtime.runId, safeEnvelope.images);
|
|
@@ -278,6 +280,7 @@ class ConversationKernel {
|
|
|
278
280
|
createdAt: deferred.createdAt,
|
|
279
281
|
}]);
|
|
280
282
|
this.activateAcceptedGoal(runtime, envelope.goalObjective);
|
|
283
|
+
this.schedulePendingRuntimeContinuation(runtime, runtime.runId);
|
|
281
284
|
return deferred;
|
|
282
285
|
}
|
|
283
286
|
checkpoint(target) {
|
|
@@ -496,7 +499,7 @@ class ConversationKernel {
|
|
|
496
499
|
stopped = true;
|
|
497
500
|
}
|
|
498
501
|
else {
|
|
499
|
-
runtime.runner.finishConversationWorkRun(runId, 'error');
|
|
502
|
+
runtime.runner.finishConversationWorkRun(runId, 'error', undefined, error instanceof Error ? error.message : String(error));
|
|
500
503
|
throw error;
|
|
501
504
|
}
|
|
502
505
|
}
|
|
@@ -507,6 +510,12 @@ class ConversationKernel {
|
|
|
507
510
|
stopped = true;
|
|
508
511
|
this.settleCooperativeStop(runtime, runId);
|
|
509
512
|
}
|
|
513
|
+
else if (runtime.pendingNextTurn.length > 0) {
|
|
514
|
+
// A renderer/IPC Guide can arrive after the final-drain barrier's
|
|
515
|
+
// last check but before this promise settles. Do not leave the
|
|
516
|
+
// deferred continuation queued on an idle runtime.
|
|
517
|
+
this.schedulePendingRuntimeContinuation(runtime, runId);
|
|
518
|
+
}
|
|
510
519
|
}
|
|
511
520
|
}
|
|
512
521
|
if (!stopped && runtime.runId === runId)
|
|
@@ -656,6 +665,7 @@ class ConversationKernel {
|
|
|
656
665
|
guideReceipts: new Map(),
|
|
657
666
|
guideEnvelopes: new Map(),
|
|
658
667
|
goalContinuationTimer: undefined,
|
|
668
|
+
pendingContinuationRunId: undefined,
|
|
659
669
|
};
|
|
660
670
|
runner.setGoalContinuationGate(() => {
|
|
661
671
|
this.queueState(runtime);
|
|
@@ -748,6 +758,34 @@ class ConversationKernel {
|
|
|
748
758
|
});
|
|
749
759
|
}, 250);
|
|
750
760
|
}
|
|
761
|
+
schedulePendingRuntimeContinuation(runtime, runId) {
|
|
762
|
+
if (runtime.pendingContinuationRunId === runId)
|
|
763
|
+
return;
|
|
764
|
+
runtime.pendingContinuationRunId = runId;
|
|
765
|
+
const active = runtime.activePromise;
|
|
766
|
+
if (active) {
|
|
767
|
+
const continueAfterSettlement = () => {
|
|
768
|
+
runtime.pendingContinuationRunId = undefined;
|
|
769
|
+
this.schedulePendingRuntimeContinuation(runtime, runId);
|
|
770
|
+
};
|
|
771
|
+
void active.then(continueAfterSettlement, continueAfterSettlement);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
setImmediate(() => {
|
|
775
|
+
runtime.pendingContinuationRunId = undefined;
|
|
776
|
+
if (runtime.runId !== runId || runtime.activePromise || runtime.stopRequestedRunId === runId)
|
|
777
|
+
return;
|
|
778
|
+
const next = runtime.pendingNextTurn.shift();
|
|
779
|
+
if (!next)
|
|
780
|
+
return;
|
|
781
|
+
const message = typeof next.message === 'string'
|
|
782
|
+
? { text: next.message, runId }
|
|
783
|
+
: { ...next.message, runId: next.message.runId || runId };
|
|
784
|
+
void this.prompt(message, runtime.target, runtime.options, next.queueMode).catch(() => {
|
|
785
|
+
// Agent.process and the work-run finalizer already publish the error.
|
|
786
|
+
});
|
|
787
|
+
});
|
|
788
|
+
}
|
|
751
789
|
startGoalDrivenBuild(runtime) {
|
|
752
790
|
if (runtime.goalContinuationTimer) {
|
|
753
791
|
clearTimeout(runtime.goalContinuationTimer);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ElectronBrowserUseHost = void 0;
|
|
4
|
+
const browserUsePageAdapter_1 = require("./browserUsePageAdapter");
|
|
4
5
|
const SAFE_NAVIGATION = /^(?:https?:|about:blank|newmark-preview:)/i;
|
|
5
6
|
const BROWSER_USE_WORLD_ID = 999;
|
|
6
7
|
/**
|
|
@@ -109,6 +110,12 @@ class ElectronBrowserUseHost {
|
|
|
109
110
|
contents.sendInputEvent({ type: 'mouseUp', button: 'left', clickCount: 1, ...point });
|
|
110
111
|
await abortableDelay(10, signal);
|
|
111
112
|
},
|
|
113
|
+
clickElement: contents.getType() === 'webview' ? async (token, signal) => {
|
|
114
|
+
throwIfAborted(signal);
|
|
115
|
+
const result = await raceWithAbort(contents.executeJavaScriptInIsolatedWorld(BROWSER_USE_WORLD_ID, [{ code: (0, browserUsePageAdapter_1.browserUseClickScript)(token) }], true), signal);
|
|
116
|
+
if (!result?.clicked)
|
|
117
|
+
throw new Error(result?.error || 'Unable to click the observed Browser-Use element.');
|
|
118
|
+
} : undefined,
|
|
112
119
|
replaceFocusedText: async (text, signal) => {
|
|
113
120
|
throwIfAborted(signal);
|
|
114
121
|
contents.focus();
|
|
@@ -473,6 +473,70 @@ async function snapshotWindowsProcessTree(rootPid, timeoutMs = WINDOWS_TREE_SNAP
|
|
|
473
473
|
function delay(ms) {
|
|
474
474
|
return new Promise(resolve => setTimeout(resolve, Math.max(0, ms)));
|
|
475
475
|
}
|
|
476
|
+
function processRootIsAlive(pid) {
|
|
477
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
478
|
+
return false;
|
|
479
|
+
try {
|
|
480
|
+
process.kill(pid, 0);
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* A graceful utility shutdown can make Electron's UtilityProcess.kill() return
|
|
489
|
+
* false before the child exit event is delivered. On Windows the captured
|
|
490
|
+
* root creation identity lets us prove that the root and every owned
|
|
491
|
+
* descendant are gone without falling back to an unscoped PID kill.
|
|
492
|
+
*/
|
|
493
|
+
async function confirmWindowsUtilityProcessTreeStopped(pid, expectedRootCreationIdentity, options, ownerKey) {
|
|
494
|
+
if (process.platform !== 'win32' || !Number.isInteger(pid) || pid <= 0 || !/^\d+$/.test(expectedRootCreationIdentity))
|
|
495
|
+
return false;
|
|
496
|
+
const snapshotter = options.snapshot || snapshotWindowsProcessTree;
|
|
497
|
+
const deadline = Date.now() + Math.min(WINDOWS_TREE_FORCE_STOP_DEADLINE_MS, Math.max(1, Math.floor(options.forceStopDeadlineMs ?? WINDOWS_TREE_FORCE_STOP_DEADLINE_MS)));
|
|
498
|
+
const maxRescans = Math.max(2, Math.floor(options.maxRescans ?? WINDOWS_TREE_MAX_RESCANS));
|
|
499
|
+
const stableRequired = Math.max(2, Math.min(maxRescans, Math.floor(options.stableEmptyRescans ?? WINDOWS_TREE_STABLE_EMPTY_RESCANS)));
|
|
500
|
+
const syntheticRoot = {
|
|
501
|
+
pid,
|
|
502
|
+
parentPid: 0,
|
|
503
|
+
depth: 0,
|
|
504
|
+
creationIdentity: expectedRootCreationIdentity,
|
|
505
|
+
};
|
|
506
|
+
let stableEmpty = 0;
|
|
507
|
+
for (let scan = 0; scan < maxRescans; scan++) {
|
|
508
|
+
try {
|
|
509
|
+
if (scan > 0)
|
|
510
|
+
await withinWindowsTreeDeadline(delay(options.rescanDelayMs ?? WINDOWS_TREE_RESCAN_DELAY_MS), deadline, 'graceful-stop rescan delay');
|
|
511
|
+
const snapshot = await withinWindowsTreeDeadline(snapshotter(pid, remainingWindowsTreeBudget(deadline, options.rescanTimeoutMs ?? WINDOWS_TREE_RESCAN_TIMEOUT_MS, 'graceful-stop rescan', !options.snapshot), [pid], ownerKey), deadline, 'graceful-stop rescan');
|
|
512
|
+
const root = snapshot.entries.find(entry => entry.pid === pid);
|
|
513
|
+
if (root && root.creationIdentity !== expectedRootCreationIdentity)
|
|
514
|
+
return false;
|
|
515
|
+
if (!snapshot.entries.length) {
|
|
516
|
+
stableEmpty += 1;
|
|
517
|
+
if (stableEmpty >= stableRequired)
|
|
518
|
+
return true;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
// The root may have exited while a child was still observable through
|
|
522
|
+
// its parent PID. Add only the already-captured root identity so the
|
|
523
|
+
// normal identity/quiescence gate can safely terminate the survivor.
|
|
524
|
+
const captured = root
|
|
525
|
+
? snapshot
|
|
526
|
+
: { rootPid: pid, entries: [syntheticRoot, ...snapshot.entries] };
|
|
527
|
+
await terminateCapturedWindowsProcessTree(captured, {
|
|
528
|
+
...options,
|
|
529
|
+
helperOwnerKey: ownerKey,
|
|
530
|
+
forceStopDeadlineMs: Math.max(1, deadline - Date.now()),
|
|
531
|
+
});
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
476
540
|
function remainingWindowsTreeBudget(deadline, capMs, label, reserveHelperClose = false) {
|
|
477
541
|
const remaining = deadline - Date.now() - (reserveHelperClose ? WINDOWS_HELPER_CLOSE_GRACE_MS + 75 : 0);
|
|
478
542
|
if (remaining < 100)
|
|
@@ -1014,6 +1078,20 @@ class ElectronUtilityAgentClient {
|
|
|
1014
1078
|
if (exited && this.child === child)
|
|
1015
1079
|
this.detachChild(child, new Error('Electron utility runtime stopped'));
|
|
1016
1080
|
else if (!exited) {
|
|
1081
|
+
const rootIdentity = this.childRootIdentity;
|
|
1082
|
+
if (!this.restartQuarantine
|
|
1083
|
+
&& rootIdentity
|
|
1084
|
+
&& rootIdentity.generation === this.childGeneration
|
|
1085
|
+
&& rootIdentity.pid === Number(child.pid || 0)
|
|
1086
|
+
&& rootIdentity.creationIdentity) {
|
|
1087
|
+
const helperOwnerKey = this.windowsHelperOwnerKey(rootIdentity.generation);
|
|
1088
|
+
const quiescent = await confirmWindowsUtilityProcessTreeStopped(rootIdentity.pid, rootIdentity.creationIdentity, { ...this.options.windowsProcessTree, helperOwnerKey }, helperOwnerKey);
|
|
1089
|
+
if (quiescent) {
|
|
1090
|
+
if (this.child === child)
|
|
1091
|
+
this.detachChild(child, new Error('Electron utility runtime stopped'));
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1017
1095
|
this.enterRestartQuarantine(failure);
|
|
1018
1096
|
// Retain the UtilityProcess object and surface the failure. The pool
|
|
1019
1097
|
// must not evict the only identity-safe handle to a still-live child.
|
|
@@ -1071,8 +1149,12 @@ class ElectronUtilityAgentClient {
|
|
|
1071
1149
|
// killing through it is identity-safe even when PID-tree discovery is
|
|
1072
1150
|
// uncertain. Descendants remain unknown, hence quarantine is still
|
|
1073
1151
|
// permanent and replacement remains forbidden.
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1152
|
+
const rootAlreadyGone = process.platform === 'win32' && !processRootIsAlive(pid);
|
|
1153
|
+
const exited = await this.killChildHandleAndAwaitExit(child, 5_000);
|
|
1154
|
+
// The root handle can outlive the OS process after a tree proof fails.
|
|
1155
|
+
// Detach that dead root handle, but keep the sticky quarantine because
|
|
1156
|
+
// the failed tree proof still leaves descendant ownership unknown.
|
|
1157
|
+
if ((exited || rootAlreadyGone) && this.child === child)
|
|
1076
1158
|
this.detachChild(child, failure);
|
|
1077
1159
|
throw failure;
|
|
1078
1160
|
}
|
|
@@ -55,6 +55,7 @@ export declare class ElectronUtilityRuntimePool {
|
|
|
55
55
|
private restarting;
|
|
56
56
|
private quarantined;
|
|
57
57
|
private disposing;
|
|
58
|
+
private forceStopPromises;
|
|
58
59
|
private capacityTail;
|
|
59
60
|
private accessSequence;
|
|
60
61
|
constructor(root: string, hostScript: string, createClient?: ElectronTargetRuntimeClientFactory, options?: ElectronUtilityRuntimePoolOptions);
|
|
@@ -86,6 +87,16 @@ export declare class ElectronUtilityRuntimePool {
|
|
|
86
87
|
hasActiveWorkspace(target: ConversationRuntimeTarget): Promise<boolean>;
|
|
87
88
|
stopWorkspace(target: ConversationRuntimeTarget): Promise<void>;
|
|
88
89
|
stopTarget(target: ConversationRuntimeTarget): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Immediately terminate a target runtime for destructive lifecycle actions.
|
|
92
|
+
* Archive is deliberately stronger than the user-facing two-click Stop
|
|
93
|
+
* contract: the target may be running, already stopping, or holding an
|
|
94
|
+
* active prompt lease. Give the kernel only the short checkpoint window
|
|
95
|
+
* owned by forceTerminateEntry, then hard-stop and evict the client so no
|
|
96
|
+
* delayed runtime event can keep the archived target resident.
|
|
97
|
+
*/
|
|
98
|
+
forceStopTarget(target: ConversationRuntimeTarget): Promise<void>;
|
|
99
|
+
private forceStopTargetInternal;
|
|
89
100
|
stopAll(): Promise<void>;
|
|
90
101
|
private throwStopFailures;
|
|
91
102
|
private stopEntry;
|