newmark-agent 0.3.8 → 0.3.11

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.
Files changed (36) hide show
  1. package/dist/cli-help.d.ts +2 -0
  2. package/dist/cli-help.js +22 -0
  3. package/dist/context/domain/types.d.ts +1 -1
  4. package/dist/conversation-utility-host.bundle.cjs +2060 -774
  5. package/dist/conversation-utility-host.js +4 -1
  6. package/dist/core/agent.d.ts +23 -1
  7. package/dist/core/agent.js +517 -37
  8. package/dist/core/agentKernelRunner.js +20 -3
  9. package/dist/core/browserControl.d.ts +8 -0
  10. package/dist/core/browserUsePageAdapter.d.ts +3 -0
  11. package/dist/core/browserUsePageAdapter.js +19 -2
  12. package/dist/core/compressionHistoryArchive.d.ts +28 -0
  13. package/dist/core/compressionHistoryArchive.js +131 -0
  14. package/dist/core/computerUseSession.d.ts +44 -0
  15. package/dist/core/computerUseSession.js +105 -0
  16. package/dist/core/config.js +1 -0
  17. package/dist/core/conversationKernel.d.ts +3 -1
  18. package/dist/core/conversationKernel.js +76 -7
  19. package/dist/core/electronBrowserUseHost.js +16 -0
  20. package/dist/core/electronUtilityAgentClient.js +84 -2
  21. package/dist/core/electronUtilityRuntimePool.js +6 -7
  22. package/dist/core/runtimeLifecycle.d.ts +23 -0
  23. package/dist/core/runtimeLifecycle.js +146 -0
  24. package/dist/core/types.d.ts +3 -0
  25. package/dist/core/utilityHostToolRouter.d.ts +7 -0
  26. package/dist/core/utilityHostToolRouter.js +25 -33
  27. package/dist/core/wslAgentRuntimePool.js +9 -10
  28. package/dist/launcher.js +13 -1
  29. package/dist/main.js +147 -18
  30. package/dist/preload.js +4 -2
  31. package/dist/tools/index.js +42 -52
  32. package/dist/tools/nativeTools.js +1 -1
  33. package/dist/ui/index.html +296 -40
  34. package/dist/wsl-agent-host.bundle.cjs +2063 -776
  35. package/dist/wsl-agent-host.js +4 -0
  36. package/package.json +3 -3
@@ -205,6 +205,9 @@ class ProviderRunError extends Error {
205
205
  function kernelTurnFailed(agent, turn) {
206
206
  return turn.stopReason === 'error' || agent.isLlmErrorText(turn.text);
207
207
  }
208
+ function providerTurnIsEmpty(turn) {
209
+ return /provider returned an empty response/i.test(`${turn.errorMessage}\n${turn.text}`);
210
+ }
208
211
  function removeTrailingFailedAssistant(agent, messages) {
209
212
  const last = messages[messages.length - 1];
210
213
  if (last?.role !== 'assistant')
@@ -338,10 +341,14 @@ async function runAgentKernel(agent) {
338
341
  }
339
342
  await kernel.prompt(promptMessages);
340
343
  const assistant = lastAssistant;
344
+ const text = assistant ? KernelMessageText(assistant) : '';
345
+ const hasToolCall = !!assistant?.content?.some(content => content.type === 'toolCall');
346
+ const emptyResponse = !assistant
347
+ || (!text.trim() && !hasToolCall && String(assistant?.stopReason || '') !== 'aborted');
341
348
  return {
342
- text: assistant ? KernelMessageText(assistant) : '',
349
+ text: emptyResponse ? '[Error] Provider returned an empty response.' : text,
343
350
  stopReason: String(assistant?.stopReason || ''),
344
- errorMessage: String(assistant?.errorMessage || ''),
351
+ errorMessage: String(assistant?.errorMessage || (emptyResponse ? 'Provider returned an empty response.' : '')),
345
352
  };
346
353
  }
347
354
  finally {
@@ -380,6 +387,16 @@ async function runAgentKernel(agent) {
380
387
  if (modelBeforeKernelRun && modelBeforeKernelRun !== agent.model && !tokens.some(t => t.text?.includes('[Model fallback]'))) {
381
388
  tokens.unshift({ type: 'text', text: `[Model fallback] ${modelBeforeKernelRun} unavailable; switched to ${agent.model}.` });
382
389
  }
390
+ let emptyResponseRetries = 0;
391
+ while (providerTurnIsEmpty(lastTurn) && emptyResponseRetries < 2) {
392
+ removeTrailingFailedAssistant(agent, kernel.state.messages);
393
+ emptyResponseRetries += 1;
394
+ const notice = `[Model retry] Provider returned an empty response; retrying the same deployment (${emptyResponseRetries}/2).`;
395
+ tokens.push({ type: 'text', text: notice });
396
+ agent.recordWorkStatus(notice);
397
+ await agent.waitForPlannedRouteRetry();
398
+ lastTurn = await runWithCompressionResume([], false);
399
+ }
383
400
  let routeRetries = 0;
384
401
  while (kernelTurnFailed(agent, lastTurn) && routeRetries < 2) {
385
402
  const previous = agent.switchToFallbackModel(lastTurn.errorMessage || lastTurn.text);
@@ -401,7 +418,7 @@ async function runAgentKernel(agent) {
401
418
  throw new ProviderRunError(normalizePublicProviderError(lastTurn.errorMessage || lastTurn.text, [agent.activeModelConfig()?.api_key]));
402
419
  }
403
420
  const lastAssistant = lastTurn.text;
404
- if (agent.mode === 'goal' && agent.goal && agent.goal.checkComplete(lastAssistant)) {
421
+ if (agent.mode === 'goal' && agent.goal && !agent.goal.paused && agent.goal.checkComplete(lastAssistant)) {
405
422
  agent.markGoalComplete();
406
423
  if (!tokens.some(token => token.type === 'text' && /goal complete/i.test(token.text || ''))) {
407
424
  tokens.push({ type: 'text', text: '\n[Goal Complete]' });
@@ -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
- const rect = probe.rect;
303
- await page.clickAt(rect.x + rect.width / 2, rect.y + rect.height / 2, signal);
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,28 @@
1
+ export interface ArchivedCompressionEntry {
2
+ id: string;
3
+ at: string;
4
+ summary: string;
5
+ messages: Array<Record<string, unknown>>;
6
+ foldedEntries: number;
7
+ foldedChars: number;
8
+ model: string;
9
+ fallback: boolean;
10
+ }
11
+ /**
12
+ * Append-only cold storage for folded context segments evicted from the small
13
+ * in-state hot cache. The archive is never injected into a model request;
14
+ * callers must explicitly search/read/restore one bounded segment.
15
+ */
16
+ export declare class CompressionHistoryArchive {
17
+ private readonly rootPath;
18
+ constructor(rootPath: string);
19
+ private file;
20
+ private append;
21
+ archive(scopeKey: string, entry: ArchivedCompressionEntry): void;
22
+ markRestored(scopeKey: string, id: string): void;
23
+ /** Replays the append-only ledger and returns only currently restorable entries. */
24
+ activeEntries(scopeKey: string): ArchivedCompressionEntry[];
25
+ /** Includes restored/tombstoned ids so a restart never reuses an archive id. */
26
+ maxNumericId(scopeKey: string): number;
27
+ }
28
+ //# sourceMappingURL=compressionHistoryArchive.d.ts.map
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CompressionHistoryArchive = void 0;
37
+ const crypto = __importStar(require("crypto"));
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ /**
41
+ * Append-only cold storage for folded context segments evicted from the small
42
+ * in-state hot cache. The archive is never injected into a model request;
43
+ * callers must explicitly search/read/restore one bounded segment.
44
+ */
45
+ class CompressionHistoryArchive {
46
+ rootPath;
47
+ constructor(rootPath) {
48
+ this.rootPath = rootPath;
49
+ }
50
+ file(scopeKey) {
51
+ const digest = crypto.createHash('sha256').update(scopeKey).digest('hex');
52
+ return path.join(this.rootPath, '.newmark-context-v2', 'compression-history', `${digest}.jsonl`);
53
+ }
54
+ append(scopeKey, event) {
55
+ const file = this.file(scopeKey);
56
+ fs.mkdirSync(path.dirname(file), { recursive: true });
57
+ fs.appendFileSync(file, `${JSON.stringify(event)}\n`, 'utf-8');
58
+ }
59
+ archive(scopeKey, entry) {
60
+ this.append(scopeKey, {
61
+ version: 1,
62
+ type: 'fold',
63
+ at: new Date().toISOString(),
64
+ entry: { ...entry, messages: entry.messages.map(message => ({ ...message })) },
65
+ });
66
+ }
67
+ markRestored(scopeKey, id) {
68
+ this.append(scopeKey, { version: 1, type: 'restore', at: new Date().toISOString(), id });
69
+ }
70
+ /** Replays the append-only ledger and returns only currently restorable entries. */
71
+ activeEntries(scopeKey) {
72
+ const file = this.file(scopeKey);
73
+ if (!fs.existsSync(file))
74
+ return [];
75
+ const active = new Map();
76
+ const lines = fs.readFileSync(file, 'utf-8').split(/\r?\n/);
77
+ for (const line of lines) {
78
+ if (!line.trim())
79
+ continue;
80
+ try {
81
+ const event = JSON.parse(line);
82
+ if (event.version !== 1)
83
+ continue;
84
+ if (event.type === 'restore') {
85
+ active.delete(String(event.id || ''));
86
+ continue;
87
+ }
88
+ if (event.type !== 'fold' || !event.entry || typeof event.entry !== 'object')
89
+ continue;
90
+ const candidate = event.entry;
91
+ if (!candidate.id || !Array.isArray(candidate.messages) || typeof candidate.summary !== 'string')
92
+ continue;
93
+ active.set(candidate.id, {
94
+ ...candidate,
95
+ messages: candidate.messages.map(message => ({ ...message })),
96
+ foldedEntries: Math.max(0, Number(candidate.foldedEntries) || candidate.messages.length),
97
+ foldedChars: Math.max(0, Number(candidate.foldedChars) || 0),
98
+ model: String(candidate.model || 'unknown'),
99
+ fallback: Boolean(candidate.fallback),
100
+ });
101
+ }
102
+ catch {
103
+ // A partial/corrupt line must not hide earlier valid append-only events.
104
+ }
105
+ }
106
+ return [...active.values()];
107
+ }
108
+ /** Includes restored/tombstoned ids so a restart never reuses an archive id. */
109
+ maxNumericId(scopeKey) {
110
+ const file = this.file(scopeKey);
111
+ if (!fs.existsSync(file))
112
+ return 0;
113
+ let max = 0;
114
+ for (const line of fs.readFileSync(file, 'utf-8').split(/\r?\n/)) {
115
+ if (!line.trim())
116
+ continue;
117
+ try {
118
+ const event = JSON.parse(line);
119
+ const entry = event.entry && typeof event.entry === 'object' ? event.entry : null;
120
+ const id = String(entry?.id || event.id || '');
121
+ max = Math.max(max, Number(id.replace(/^ctx-cache-/, '')) || 0);
122
+ }
123
+ catch {
124
+ // Ignore only the malformed line; valid earlier ids remain authoritative.
125
+ }
126
+ }
127
+ return max;
128
+ }
129
+ }
130
+ exports.CompressionHistoryArchive = CompressionHistoryArchive;
131
+ //# sourceMappingURL=compressionHistoryArchive.js.map
@@ -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
@@ -913,6 +913,7 @@ function defaultConfig() {
913
913
  keep_recent_messages: { _description: "Keep recent messages", _type: "integer", value: 10 },
914
914
  preserve_recent_messages: { _description: "dev-0.3.8 protected recent-message zone for context_history_manage (0 disables)", _type: "integer", value: 5 },
915
915
  compression_cache_max: { _description: "dev-0.3.8 max folded-segment cache entries retained for restore/search", _type: "integer", value: 8 },
916
+ compression_archive_enabled: { _description: "dev-0.3.9 append-only cold archive for folded segments evicted from the hot cache", _type: "boolean", value: true },
916
917
  structured_context_v2: { _description: "dev-0.3.0 structured context v2 (orchestrator + fixed order + snapshot)", _type: "boolean", value: true },
917
918
  build_history_persistence: { _description: "dev-0.3.0 append-only Build History persistence", _type: "boolean", value: true },
918
919
  branch_log_v2: { _description: "dev-0.3.0 branch long-log v2 (epoch summaries)", _type: "boolean", value: true },
@@ -198,7 +198,7 @@ export declare class ConversationKernel {
198
198
  setWorkRunExpanded(target: ConversationTargetInput, runId: string, expanded: boolean): boolean;
199
199
  setInputMode(target: ConversationTargetInput, mode: string): 'guide' | 'next';
200
200
  setMode(target: ConversationTargetInput, mode: AgentMode): AgentMode;
201
- toggleGoalPause(target: ConversationTargetInput): boolean;
201
+ toggleGoalPause(target: ConversationTargetInput): Promise<boolean>;
202
202
  clearGoal(target: ConversationTargetInput): boolean;
203
203
  updateSetting(section: string, key: string, value: unknown): void;
204
204
  runtimeState(target: ConversationTargetInput): ConversationRuntimeState | null;
@@ -212,6 +212,8 @@ export declare class ConversationKernel {
212
212
  private processTimeoutMs;
213
213
  private runtime;
214
214
  private scheduleGoalContinuation;
215
+ private schedulePendingRuntimeContinuation;
216
+ private startGoalDrivenBuild;
215
217
  private createRunner;
216
218
  private enqueueRootInboxWake;
217
219
  private applyOptions;
@@ -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) {
@@ -333,11 +336,26 @@ class ConversationKernel {
333
336
  runtime.options.mode = mode;
334
337
  return runner.mode;
335
338
  }
336
- toggleGoalPause(target) {
339
+ async toggleGoalPause(target) {
337
340
  const normalized = this.normalizeTarget(target);
338
- const runtime = this.findRuntime(normalized);
339
- const runner = runtime?.runner || this.createRunner(normalized);
340
- return runner.toggleGoalPause();
341
+ let runtime = this.findRuntime(normalized);
342
+ if (!runtime) {
343
+ const runner = this.createRunner(normalized);
344
+ runtime = this.runtime(normalized, {
345
+ mode: runner.mode,
346
+ model: runner.model,
347
+ intelligence: runner.intelligence,
348
+ inputMode: runner.inputMode,
349
+ engine: runner.engine,
350
+ }, runner);
351
+ }
352
+ const wasPaused = runtime.runner.isGoalPaused();
353
+ const hadGoal = !!runtime.runner.goal;
354
+ const paused = runtime.runner.toggleGoalPause();
355
+ if (hadGoal && wasPaused && !paused)
356
+ this.startGoalDrivenBuild(runtime);
357
+ this.mirrorHostIfTargetActive(runtime);
358
+ return paused;
341
359
  }
342
360
  clearGoal(target) {
343
361
  const normalized = this.normalizeTarget(target);
@@ -401,6 +419,7 @@ class ConversationKernel {
401
419
  }
402
420
  runtime.stopRequestedRunId = runtime.runId;
403
421
  runtime.forceStopArmedRunId = runtime.runId;
422
+ runtime.runner.pauseGoalForUserInterrupt();
404
423
  this.retainUnconsumedKernelMessages(runtime);
405
424
  this.deferOutstandingGuides(runtime);
406
425
  const checkpointed = this.checkpoint(runtime.target).checkpointed;
@@ -491,6 +510,12 @@ class ConversationKernel {
491
510
  stopped = true;
492
511
  this.settleCooperativeStop(runtime, runId);
493
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
+ }
494
519
  }
495
520
  }
496
521
  if (!stopped && runtime.runId === runId)
@@ -610,7 +635,7 @@ class ConversationKernel {
610
635
  return 0;
611
636
  return Math.max(1000, Math.floor(raw));
612
637
  }
613
- runtime(target, options) {
638
+ runtime(target, options, runnerOverride) {
614
639
  const existing = this.findRuntime(target);
615
640
  if (existing) {
616
641
  this.ensureRuntimeMetadata(existing, target);
@@ -618,7 +643,7 @@ class ConversationKernel {
618
643
  return existing;
619
644
  }
620
645
  const id = target.conversationId;
621
- const runner = this.createRunner(target);
646
+ const runner = runnerOverride || this.createRunner(target);
622
647
  runner.setAutomationManager(this.automation);
623
648
  this.applyOptions(runner, options);
624
649
  const runtime = {
@@ -640,6 +665,7 @@ class ConversationKernel {
640
665
  guideReceipts: new Map(),
641
666
  guideEnvelopes: new Map(),
642
667
  goalContinuationTimer: undefined,
668
+ pendingContinuationRunId: undefined,
643
669
  };
644
670
  runner.setGoalContinuationGate(() => {
645
671
  this.queueState(runtime);
@@ -732,8 +758,51 @@ class ConversationKernel {
732
758
  });
733
759
  }, 250);
734
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
+ }
789
+ startGoalDrivenBuild(runtime) {
790
+ if (runtime.goalContinuationTimer) {
791
+ clearTimeout(runtime.goalContinuationTimer);
792
+ runtime.goalContinuationTimer = undefined;
793
+ }
794
+ const message = runtime.runner.claimGoalContinuationMessage({ force: true });
795
+ if (!message)
796
+ return;
797
+ void this.prompt(message, runtime.target, { ...runtime.options, mode: 'goal' }, 'followUp').catch(() => {
798
+ // Agent.process records and publishes the Build error.
799
+ });
800
+ }
735
801
  createRunner(target) {
736
- const runner = this.lifecycle.createRunner?.(target) || new agent_1.Agent(this.root, { actorId: this.host.runtimeActorId });
802
+ const runner = this.lifecycle.createRunner?.(target) || new agent_1.Agent(this.root, {
803
+ actorId: this.host.runtimeActorId,
804
+ runtimeLifecycleRole: this.host.runtimeLifecycleRole,
805
+ });
737
806
  if (target.workspace) {
738
807
  runner.workspace.current = {
739
808
  id: target.workspace.id,
@@ -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
  /**
@@ -94,12 +95,27 @@ class ElectronBrowserUseHost {
94
95
  evaluateFixed: async (script, signal) => await raceWithAbort(contents.executeJavaScriptInIsolatedWorld(BROWSER_USE_WORLD_ID, [{ code: script }], true), signal),
95
96
  clickAt: async (x, y, signal) => {
96
97
  throwIfAborted(signal);
98
+ // A webview guest can retain the DOM focus while its embedder is still
99
+ // committing a workspace/tab transition. Focus both sides and yield
100
+ // between native input events so Chromium receives a real click rather
101
+ // than a synchronously queued sequence that can be dropped by the guest.
102
+ contents.hostWebContents?.focus();
97
103
  contents.focus();
104
+ await abortableDelay(10, signal);
98
105
  const point = { x: Math.max(0, Math.round(x)), y: Math.max(0, Math.round(y)) };
99
106
  contents.sendInputEvent({ type: 'mouseMove', ...point });
107
+ await abortableDelay(10, signal);
100
108
  contents.sendInputEvent({ type: 'mouseDown', button: 'left', clickCount: 1, ...point });
109
+ await abortableDelay(10, signal);
101
110
  contents.sendInputEvent({ type: 'mouseUp', button: 'left', clickCount: 1, ...point });
111
+ await abortableDelay(10, signal);
102
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,
103
119
  replaceFocusedText: async (text, signal) => {
104
120
  throwIfAborted(signal);
105
121
  contents.focus();