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.
- package/dist/cli-help.d.ts +2 -0
- package/dist/cli-help.js +22 -0
- package/dist/context/domain/types.d.ts +1 -1
- package/dist/conversation-utility-host.bundle.cjs +2060 -774
- package/dist/conversation-utility-host.js +4 -1
- package/dist/core/agent.d.ts +23 -1
- package/dist/core/agent.js +517 -37
- package/dist/core/agentKernelRunner.js +20 -3
- 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/compressionHistoryArchive.d.ts +28 -0
- package/dist/core/compressionHistoryArchive.js +131 -0
- package/dist/core/computerUseSession.d.ts +44 -0
- package/dist/core/computerUseSession.js +105 -0
- package/dist/core/config.js +1 -0
- package/dist/core/conversationKernel.d.ts +3 -1
- package/dist/core/conversationKernel.js +76 -7
- package/dist/core/electronBrowserUseHost.js +16 -0
- package/dist/core/electronUtilityAgentClient.js +84 -2
- package/dist/core/electronUtilityRuntimePool.js +6 -7
- package/dist/core/runtimeLifecycle.d.ts +23 -0
- package/dist/core/runtimeLifecycle.js +146 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/utilityHostToolRouter.d.ts +7 -0
- package/dist/core/utilityHostToolRouter.js +25 -33
- package/dist/core/wslAgentRuntimePool.js +9 -10
- package/dist/launcher.js +13 -1
- package/dist/main.js +147 -18
- package/dist/preload.js +4 -2
- package/dist/tools/index.js +42 -52
- package/dist/tools/nativeTools.js +1 -1
- package/dist/ui/index.html +296 -40
- package/dist/wsl-agent-host.bundle.cjs +2063 -776
- package/dist/wsl-agent-host.js +4 -0
- package/package.json +3 -3
|
@@ -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
|
}
|
|
@@ -247,7 +247,7 @@ class ElectronUtilityRuntimePool {
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
async toggleGoalPause(target) {
|
|
250
|
-
const entry = await this.
|
|
250
|
+
const entry = await this.acquire((0, conversationTarget_1.normalizeConversationTarget)(target));
|
|
251
251
|
if (!entry?.client.toggleGoalPause)
|
|
252
252
|
return null;
|
|
253
253
|
try {
|
|
@@ -630,12 +630,11 @@ class ElectronUtilityRuntimePool {
|
|
|
630
630
|
}
|
|
631
631
|
const finalized = !!kernelForce && kernelForce.action === 'force';
|
|
632
632
|
const checkpointed = finalized ? (kernelForce.checkpointed || intent.checkpointed) : intent.checkpointed;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
633
|
+
// The acknowledgement only gives the kernel a chance to persist its
|
|
634
|
+
// force_interrupted settlement. A second Stop still owns a process-level
|
|
635
|
+
// termination boundary and must leave the target runtime down until the
|
|
636
|
+
// next prompt, even when the worker acknowledged the force request.
|
|
637
|
+
await entry.client.forceStop();
|
|
639
638
|
entry.lastSnapshot = null;
|
|
640
639
|
entry.workEvents = [];
|
|
641
640
|
entry.lastRunId = '';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type RuntimeLifecycleRole = 'main' | 'utility' | 'wsl';
|
|
2
|
+
export interface RuntimeLifecycleState {
|
|
3
|
+
role: RuntimeLifecycleRole;
|
|
4
|
+
ownerId: string;
|
|
5
|
+
pid: number;
|
|
6
|
+
startedAt: string;
|
|
7
|
+
previousOwnerAlive: boolean;
|
|
8
|
+
unexpectedExit: boolean;
|
|
9
|
+
active: true;
|
|
10
|
+
}
|
|
11
|
+
export declare function isRuntimeProcessAlive(pid: number): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Claim this process/role as the current runtime owner.
|
|
14
|
+
*
|
|
15
|
+
* A frontend cold runner in the same process gets the same in-memory claim and
|
|
16
|
+
* therefore does not look like a restart. A genuinely new process sees the
|
|
17
|
+
* previous active claim and can pause orphaned Goal/Flow state once.
|
|
18
|
+
*/
|
|
19
|
+
export declare function beginRuntimeLifecycle(root: string, role?: RuntimeLifecycleRole): RuntimeLifecycleState;
|
|
20
|
+
/** Mark only this owner clean; a hard kill leaves the active marker intact. */
|
|
21
|
+
export declare function markRuntimeLifecycleClean(root: string, role?: RuntimeLifecycleRole): void;
|
|
22
|
+
export declare function runtimeLifecycleState(root: string, role?: RuntimeLifecycleRole): RuntimeLifecycleState | null;
|
|
23
|
+
//# sourceMappingURL=runtimeLifecycle.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
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.isRuntimeProcessAlive = isRuntimeProcessAlive;
|
|
37
|
+
exports.beginRuntimeLifecycle = beginRuntimeLifecycle;
|
|
38
|
+
exports.markRuntimeLifecycleClean = markRuntimeLifecycleClean;
|
|
39
|
+
exports.runtimeLifecycleState = runtimeLifecycleState;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const crypto_1 = require("crypto");
|
|
43
|
+
const processStates = new Map();
|
|
44
|
+
function stateKey(root, role) {
|
|
45
|
+
return `${path.resolve(root)}\u0000${role}`;
|
|
46
|
+
}
|
|
47
|
+
function statePath(root, role, ownerId = '') {
|
|
48
|
+
return path.join(root, '.newmark-runtime', ownerId ? `lifecycle-${role}-${ownerId}.json` : `lifecycle-${role}.json`);
|
|
49
|
+
}
|
|
50
|
+
function readState(file) {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
53
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isRuntimeProcessAlive(pid) {
|
|
60
|
+
const candidate = Math.floor(Number(pid) || 0);
|
|
61
|
+
if (candidate <= 0)
|
|
62
|
+
return false;
|
|
63
|
+
try {
|
|
64
|
+
process.kill(candidate, 0);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function writeState(root, role, state) {
|
|
72
|
+
const ownerId = typeof state.ownerId === 'string' ? state.ownerId : '';
|
|
73
|
+
const file = statePath(root, role, ownerId);
|
|
74
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
75
|
+
const temporary = `${file}.${process.pid}.${(0, crypto_1.randomUUID)()}.tmp`;
|
|
76
|
+
fs.writeFileSync(temporary, JSON.stringify(state, null, 2), 'utf-8');
|
|
77
|
+
fs.renameSync(temporary, file);
|
|
78
|
+
}
|
|
79
|
+
function readActiveStates(root, role) {
|
|
80
|
+
const directory = path.join(root, '.newmark-runtime');
|
|
81
|
+
try {
|
|
82
|
+
const prefix = `lifecycle-${role}`;
|
|
83
|
+
return fs.readdirSync(directory)
|
|
84
|
+
.filter(file => file.startsWith(prefix) && file.endsWith('.json'))
|
|
85
|
+
.map(file => readState(path.join(directory, file)))
|
|
86
|
+
.filter((state) => !!state && state.active === true);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Claim this process/role as the current runtime owner.
|
|
94
|
+
*
|
|
95
|
+
* A frontend cold runner in the same process gets the same in-memory claim and
|
|
96
|
+
* therefore does not look like a restart. A genuinely new process sees the
|
|
97
|
+
* previous active claim and can pause orphaned Goal/Flow state once.
|
|
98
|
+
*/
|
|
99
|
+
function beginRuntimeLifecycle(root, role = 'main') {
|
|
100
|
+
const key = stateKey(root, role);
|
|
101
|
+
const existingProcessState = processStates.get(key);
|
|
102
|
+
if (existingProcessState)
|
|
103
|
+
return existingProcessState;
|
|
104
|
+
const previousStates = readActiveStates(root, role);
|
|
105
|
+
const previousOwnerAlive = previousStates.some(previous => isRuntimeProcessAlive(Number(previous.pid)));
|
|
106
|
+
const state = {
|
|
107
|
+
role,
|
|
108
|
+
ownerId: (0, crypto_1.randomUUID)(),
|
|
109
|
+
pid: process.pid,
|
|
110
|
+
startedAt: new Date().toISOString(),
|
|
111
|
+
previousOwnerAlive,
|
|
112
|
+
unexpectedExit: previousStates.length > 0 && !previousOwnerAlive,
|
|
113
|
+
active: true,
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
writeState(root, role, state);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
// Recovery remains conservative when the marker cannot be written. The
|
|
120
|
+
// persisted WorkRun timestamp still protects a live backend cold read.
|
|
121
|
+
}
|
|
122
|
+
processStates.set(key, state);
|
|
123
|
+
return state;
|
|
124
|
+
}
|
|
125
|
+
/** Mark only this owner clean; a hard kill leaves the active marker intact. */
|
|
126
|
+
function markRuntimeLifecycleClean(root, role = 'main') {
|
|
127
|
+
const key = stateKey(root, role);
|
|
128
|
+
const current = processStates.get(key);
|
|
129
|
+
if (!current)
|
|
130
|
+
return;
|
|
131
|
+
try {
|
|
132
|
+
writeState(root, role, {
|
|
133
|
+
...current,
|
|
134
|
+
active: false,
|
|
135
|
+
cleanExitAt: new Date().toISOString(),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Leaving the marker active is safer than claiming a clean exit after a
|
|
140
|
+
// failed durable write.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function runtimeLifecycleState(root, role = 'main') {
|
|
144
|
+
return processStates.get(stateKey(root, role)) || null;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=runtimeLifecycle.js.map
|
package/dist/core/types.d.ts
CHANGED
|
@@ -129,6 +129,9 @@ export interface ConversationWorkRun {
|
|
|
129
129
|
runId: string;
|
|
130
130
|
target: ConversationTarget;
|
|
131
131
|
runtimeKey: string;
|
|
132
|
+
runtimeOwnerId?: string;
|
|
133
|
+
runtimeOwnerPid?: number;
|
|
134
|
+
runtimeLifecycleRole?: 'main' | 'utility' | 'wsl';
|
|
132
135
|
status: ConversationWorkRunStatus;
|
|
133
136
|
startedAt: string;
|
|
134
137
|
endedAt?: string;
|
|
@@ -2,6 +2,7 @@ import { BrowserControl } from './browserControl';
|
|
|
2
2
|
import { BrowserUseReceipt, BrowserUseRequest } from './browserUse';
|
|
3
3
|
import { UtilityHostToolRequest } from './utilityAgentProtocol';
|
|
4
4
|
import { runComputerUse } from '../tools/computerUse';
|
|
5
|
+
import { ComputerUseSessionState } from './computerUseSession';
|
|
5
6
|
import { runTerminalTakeover } from '../tools/terminalTakeover';
|
|
6
7
|
export interface UtilityHostToolRouterOptions {
|
|
7
8
|
persistenceRoot: string;
|
|
@@ -15,6 +16,12 @@ export interface UtilityHostToolRouterOptions {
|
|
|
15
16
|
}
|
|
16
17
|
export type RoutedUtilityHostToolHandler = ((request: UtilityHostToolRequest, signal?: AbortSignal) => Promise<unknown>) & {
|
|
17
18
|
cancelTarget(runtimeKey: string): void;
|
|
19
|
+
computerUseState(runtimeKey: string): ComputerUseSessionState;
|
|
20
|
+
setComputerUseEnabled(runtimeKey: string, enabled: boolean, ownerLabel?: string): {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
state: ComputerUseSessionState;
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
|
18
25
|
};
|
|
19
26
|
/**
|
|
20
27
|
* Routes every desktop-global capability in the Electron main process.
|
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const browserUse_1 = require("./browserUse");
|
|
40
40
|
const toolPolicy_1 = require("./toolPolicy");
|
|
41
41
|
const computerUse_1 = require("../tools/computerUse");
|
|
42
|
+
const computerUseSession_1 = require("./computerUseSession");
|
|
42
43
|
const terminalTakeover_1 = require("../tools/terminalTakeover");
|
|
43
44
|
const ROOT_AGENT_ACTOR_ID = '00000000-0000-4000-8000-000000000001';
|
|
44
45
|
const AUTOMATION_TOOLS = new Set([
|
|
@@ -54,10 +55,8 @@ const AUTOMATION_TOOLS = new Set([
|
|
|
54
55
|
* one authoritative owner lock rather than one lock per child process.
|
|
55
56
|
*/
|
|
56
57
|
function createUtilityHostToolHandler(options) {
|
|
57
|
-
let computerUseLease = null;
|
|
58
58
|
const terminalOwners = new Map();
|
|
59
59
|
const ephemeralScreenshots = new Map();
|
|
60
|
-
const lockTtlMs = 10 * 60 * 1000;
|
|
61
60
|
const handler = async (request, signal) => {
|
|
62
61
|
throwIfAborted(signal);
|
|
63
62
|
validateTargetContext(request);
|
|
@@ -73,7 +72,14 @@ function createUtilityHostToolHandler(options) {
|
|
|
73
72
|
if (request.tool === 'browser_control') {
|
|
74
73
|
if (request.args.action === 'use')
|
|
75
74
|
throw new Error('Isolated Browser-Use must use the target-bound browser_use host RPC');
|
|
76
|
-
const result = await (options.runBrowser || browserControl_1.BrowserControl.run.bind(browserControl_1.BrowserControl))(
|
|
75
|
+
const result = await (options.runBrowser || browserControl_1.BrowserControl.run.bind(browserControl_1.BrowserControl))({
|
|
76
|
+
...request.args,
|
|
77
|
+
target: {
|
|
78
|
+
workspaceId: request.target.workspaceId,
|
|
79
|
+
conversationId: request.target.conversationId,
|
|
80
|
+
runtimeKey: request.target.runtimeKey,
|
|
81
|
+
},
|
|
82
|
+
}, signal);
|
|
77
83
|
throwIfAborted(signal);
|
|
78
84
|
return result;
|
|
79
85
|
}
|
|
@@ -124,20 +130,14 @@ function createUtilityHostToolHandler(options) {
|
|
|
124
130
|
const action = String(args.action || '').trim().toLowerCase();
|
|
125
131
|
const trustedComputerUseContext = request.context;
|
|
126
132
|
const owner = `${request.target.runtimeKey}:${String(request.context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID)}`;
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else if (computerUseLease && computerUseLease.owner !== owner) {
|
|
136
|
-
return computerUseLockError(action, owner, computerUseLease.owner);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
computerUseLease = { owner, runtimeKey: request.target.runtimeKey, workspacePath: request.target.workspacePath, updatedAt: now };
|
|
140
|
-
}
|
|
133
|
+
const sessionScope = {
|
|
134
|
+
runtimeKey: request.target.runtimeKey,
|
|
135
|
+
ownerLabel: owner,
|
|
136
|
+
workspacePath: request.target.workspacePath,
|
|
137
|
+
};
|
|
138
|
+
const lockGuard = computerUseSession_1.defaultComputerUseSessionRegistry.authorize(action, sessionScope, args.dry_run === true || args.dryRun === true);
|
|
139
|
+
if (lockGuard)
|
|
140
|
+
return lockGuard;
|
|
141
141
|
let retainedScreenshotPath = '';
|
|
142
142
|
try {
|
|
143
143
|
const result = await (options.runComputer || computerUse_1.runComputerUse)({
|
|
@@ -214,10 +214,7 @@ function createUtilityHostToolHandler(options) {
|
|
|
214
214
|
}
|
|
215
215
|
catch { }
|
|
216
216
|
}
|
|
217
|
-
|
|
218
|
-
computerUseLease = null;
|
|
219
|
-
else if (computerUseLease?.owner === owner)
|
|
220
|
-
computerUseLease.updatedAt = Date.now();
|
|
217
|
+
computerUseSession_1.defaultComputerUseSessionRegistry.complete(action, sessionScope);
|
|
221
218
|
}
|
|
222
219
|
};
|
|
223
220
|
handler.cancelTarget = (runtimeKey) => {
|
|
@@ -238,9 +235,9 @@ function createUtilityHostToolHandler(options) {
|
|
|
238
235
|
(0, terminalTakeover_1.stopTerminalTakeoverSession)(session.id, terminalOwner, 'runtime-force-restart');
|
|
239
236
|
}
|
|
240
237
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
238
|
+
const hadComputerUseLease = computerUseSession_1.defaultComputerUseSessionRegistry.cancelTarget(runtimeKey);
|
|
239
|
+
if (hadComputerUseLease) {
|
|
240
|
+
const lease = { workspacePath: options.persistenceRoot, owner: runtimeKey };
|
|
244
241
|
void (options.runComputer || computerUse_1.runComputerUse)({
|
|
245
242
|
action: 'takeover_stop',
|
|
246
243
|
workspacePath: lease.workspacePath,
|
|
@@ -249,6 +246,10 @@ function createUtilityHostToolHandler(options) {
|
|
|
249
246
|
}).catch(() => undefined);
|
|
250
247
|
}
|
|
251
248
|
};
|
|
249
|
+
handler.computerUseState = (runtimeKey) => computerUseSession_1.defaultComputerUseSessionRegistry.state(runtimeKey);
|
|
250
|
+
handler.setComputerUseEnabled = (runtimeKey, enabled, ownerLabel = `conversation:${runtimeKey}`) => {
|
|
251
|
+
return computerUseSession_1.defaultComputerUseSessionRegistry.setEnabled({ runtimeKey, ownerLabel, workspacePath: options.persistenceRoot }, enabled);
|
|
252
|
+
};
|
|
252
253
|
return handler;
|
|
253
254
|
}
|
|
254
255
|
function throwIfAborted(signal) {
|
|
@@ -307,13 +308,4 @@ function evaluateUtilityHostToolPolicy(request) {
|
|
|
307
308
|
args,
|
|
308
309
|
});
|
|
309
310
|
}
|
|
310
|
-
function computerUseLockError(action, requestedOwner, activeOwner) {
|
|
311
|
-
return JSON.stringify({
|
|
312
|
-
ok: false,
|
|
313
|
-
action,
|
|
314
|
-
error: `Computer Use is already active in ${activeOwner}. Stop it with computer_use takeover_stop or wait before another conversation takes control.`,
|
|
315
|
-
lock_owner: activeOwner,
|
|
316
|
-
requested_owner: requestedOwner,
|
|
317
|
-
}, null, 2);
|
|
318
|
-
}
|
|
319
311
|
//# sourceMappingURL=utilityHostToolRouter.js.map
|
|
@@ -262,7 +262,7 @@ class WslAgentRuntimePool {
|
|
|
262
262
|
}
|
|
263
263
|
async toggleGoalPause(target) {
|
|
264
264
|
const normalized = (0, conversationTarget_1.normalizeConversationTarget)(target);
|
|
265
|
-
const entry = await this.
|
|
265
|
+
const entry = await this.acquire(normalized);
|
|
266
266
|
if (!entry?.client.toggleGoalPause)
|
|
267
267
|
return null;
|
|
268
268
|
try {
|
|
@@ -616,15 +616,14 @@ class WslAgentRuntimePool {
|
|
|
616
616
|
}
|
|
617
617
|
const finalized = !!kernelForce && kernelForce.action === 'force';
|
|
618
618
|
const checkpointed = finalized ? (kernelForce.checkpointed || intent.checkpointed) : intent.checkpointed;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
}
|
|
619
|
+
// The acknowledgement only gives the kernel a chance to persist its
|
|
620
|
+
// force_interrupted settlement. A second Stop still owns a process-group
|
|
621
|
+
// termination boundary and must leave the target runtime down until the
|
|
622
|
+
// next prompt, even when the worker acknowledged the force request.
|
|
623
|
+
if (entry.client.forceStopRuntimeGroup)
|
|
624
|
+
await entry.client.forceStopRuntimeGroup();
|
|
625
|
+
else
|
|
626
|
+
await entry.client.forceRestartRuntimeGroup();
|
|
628
627
|
entry.lastSnapshot = null;
|
|
629
628
|
entry.workEvents = [];
|
|
630
629
|
entry.lastRunId = '';
|
package/dist/launcher.js
CHANGED
|
@@ -43,7 +43,12 @@ const agent_1 = require("./core/agent");
|
|
|
43
43
|
const flow_1 = require("./core/flow");
|
|
44
44
|
const flow_runner_1 = require("./core/flow-runner");
|
|
45
45
|
const cli_commands_1 = require("./cli-commands");
|
|
46
|
+
const installUpdate_1 = require("./core/installUpdate");
|
|
47
|
+
const cli_help_1 = require("./cli-help");
|
|
46
48
|
const args = process.argv.slice(2);
|
|
49
|
+
const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
50
|
+
const isHelpArg = !hasCliCommand && (args.some(arg => ['--help', '-h'].includes(arg.toLowerCase())) || args[0]?.toLowerCase() === 'help');
|
|
51
|
+
const isVersionArg = !hasCliCommand && args.some(arg => ['--version', '-v'].includes(arg.toLowerCase()));
|
|
47
52
|
const isTui = args.some(arg => arg.toLowerCase() === '--tui');
|
|
48
53
|
const isGui = args.some(arg => arg.toLowerCase() === '--gui');
|
|
49
54
|
const isCli = args.includes('--cli');
|
|
@@ -51,7 +56,6 @@ const isServer = args.includes('--server');
|
|
|
51
56
|
const isEdit = args[0] === 'edit';
|
|
52
57
|
const editFile = isEdit ? args[1] : '';
|
|
53
58
|
const isFlow = args[0] === 'flow';
|
|
54
|
-
const hasCliCommand = args.some(a => cli_commands_1.CLI_COMMANDS.includes(a));
|
|
55
59
|
function pathArgValue(values, key) {
|
|
56
60
|
const prefix = `${key}=`;
|
|
57
61
|
const inlineIdx = values.findIndex(a => a.startsWith(prefix));
|
|
@@ -169,6 +173,14 @@ function writableRuntimeRoot(candidate) {
|
|
|
169
173
|
}
|
|
170
174
|
const explicitRoot = pathArgValue(args, '--root');
|
|
171
175
|
const root = explicitRoot ? writableRuntimeRoot(explicitRoot) : userRuntimeRoot();
|
|
176
|
+
if (isHelpArg) {
|
|
177
|
+
console.log((0, cli_help_1.newmarkHelpText)((0, installUpdate_1.currentAppVersion)()));
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
if (isVersionArg) {
|
|
181
|
+
console.log((0, installUpdate_1.currentAppVersion)());
|
|
182
|
+
process.exit(0);
|
|
183
|
+
}
|
|
172
184
|
function firstRunInit(r) {
|
|
173
185
|
fs.mkdirSync(r, { recursive: true });
|
|
174
186
|
migrateLegacyRuntimeRoot(r);
|