newmark-agent 0.4.7 → 0.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversation-utility-host.bundle.cjs +407 -36
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +3 -0
- package/dist/core/agent.js +122 -10
- package/dist/core/agentKernelRunner.js +28 -11
- package/dist/core/autoRouter.d.ts +1 -1
- package/dist/core/autoRouter.js +16 -7
- package/dist/core/conversationKernel.d.ts +43 -0
- package/dist/core/conversationKernel.js +262 -8
- package/dist/core/electronUtilityAgentClient.d.ts +2 -0
- package/dist/core/electronUtilityAgentClient.js +5 -1
- package/dist/core/electronUtilityRuntimePool.d.ts +3 -0
- package/dist/core/electronUtilityRuntimePool.js +11 -0
- package/dist/core/toolPolicy.js +3 -0
- package/dist/core/utilityAgentProtocol.d.ts +23 -1
- package/dist/core/utilityHostToolRouter.js +18 -0
- package/dist/core/wslAgentClient.d.ts +2 -0
- package/dist/core/wslAgentClient.js +5 -1
- package/dist/core/wslAgentProtocol.d.ts +12 -1
- package/dist/core/wslAgentRuntimePool.d.ts +3 -0
- package/dist/core/wslAgentRuntimePool.js +11 -0
- package/dist/main.js +226 -13
- package/dist/preload.js +2 -0
- package/dist/server.d.ts +47 -1
- package/dist/server.js +304 -38
- package/dist/tools/index.js +46 -1
- package/dist/tools/nativeTools.js +1 -0
- package/dist/ui/index.html +150 -14
- package/dist/wsl-agent-host.bundle.cjs +407 -36
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +5 -2
package/dist/server.js
CHANGED
|
@@ -33,7 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.configureHostedServer = configureHostedServer;
|
|
37
|
+
exports.stopHostedServer = stopHostedServer;
|
|
38
|
+
exports.hostedServerStatus = hostedServerStatus;
|
|
39
|
+
exports.setHostedServerEnabled = setHostedServerEnabled;
|
|
36
40
|
exports.runServer = runServer;
|
|
41
|
+
exports.setHostedServerAutomation = setHostedServerAutomation;
|
|
37
42
|
const http = __importStar(require("http"));
|
|
38
43
|
const fs = __importStar(require("fs"));
|
|
39
44
|
const path = __importStar(require("path"));
|
|
@@ -56,6 +61,19 @@ let mobileToken = '';
|
|
|
56
61
|
let appRoot = '';
|
|
57
62
|
const mobileWorkEventSubscribers = new Set();
|
|
58
63
|
const mobileScopedRuntimes = new Map();
|
|
64
|
+
let hostedWorkEventSink = null;
|
|
65
|
+
let unsubscribeHostedWorkEvents = null;
|
|
66
|
+
let unsubscribeServerAgentWorkEvents = null;
|
|
67
|
+
let hostedServer = null;
|
|
68
|
+
let hostedServerRoot = '';
|
|
69
|
+
let hostedServerOptions = {};
|
|
70
|
+
let hostedServerError = '';
|
|
71
|
+
let hostedServerStartedAt = 0;
|
|
72
|
+
let hostedAccessHost = '';
|
|
73
|
+
let hostedAccessHostCheckedAt = 0;
|
|
74
|
+
let hostedConversationUiState;
|
|
75
|
+
let hostedConversationUiAction;
|
|
76
|
+
let hostedConversationPrompt;
|
|
59
77
|
function mobileRuntimeKey(workspaceId, conversationId) {
|
|
60
78
|
return `${String(workspaceId || '')}::${String(conversationId || '')}`;
|
|
61
79
|
}
|
|
@@ -67,6 +85,10 @@ function publishMobileWorkEvent(event) {
|
|
|
67
85
|
catch { /* ignore disconnected mobile listeners */ }
|
|
68
86
|
}
|
|
69
87
|
}
|
|
88
|
+
function publishServerWorkEvent(event) {
|
|
89
|
+
publishMobileWorkEvent(event);
|
|
90
|
+
hostedWorkEventSink?.(event);
|
|
91
|
+
}
|
|
70
92
|
function mobileAuthorized(req) {
|
|
71
93
|
if (!mobileToken)
|
|
72
94
|
return false;
|
|
@@ -837,6 +859,9 @@ async function handleApi(req, res, body) {
|
|
|
837
859
|
mode: agent.mode,
|
|
838
860
|
model: agent.modelSelectionValue(),
|
|
839
861
|
modelLabel: agent.modelLabel(),
|
|
862
|
+
models: agent.allModelNames(),
|
|
863
|
+
providers: (0, config_1.sanitizeProvidersForState)(agent.config.providers()),
|
|
864
|
+
intelligence: agent.intelligence,
|
|
840
865
|
status: agent.status,
|
|
841
866
|
activeConversationId: agent.activeConversationId,
|
|
842
867
|
conversations: agent.listConversationStates(),
|
|
@@ -876,9 +901,18 @@ async function handleApi(req, res, body) {
|
|
|
876
901
|
mobileJson(res, { error: 'Conversation not found in workspace' }, 404);
|
|
877
902
|
return;
|
|
878
903
|
}
|
|
879
|
-
|
|
904
|
+
// GUI conversations normally execute inside isolated runtime pools.
|
|
905
|
+
// Their durable target state can be newer than the hosted main Agent's
|
|
906
|
+
// foreground memory, so workspace-bound mobile reads must hydrate a
|
|
907
|
+
// fresh read-only target Agent from storage.
|
|
908
|
+
const snapshotAgent = ws ? mobileScopedAgent(ws, String(conversationId)) : current;
|
|
909
|
+
const snapshot = snapshotAgent.getConversationSnapshot(String(conversationId), {
|
|
910
|
+
window: windowSize,
|
|
911
|
+
before,
|
|
912
|
+
workspace: ws || undefined,
|
|
913
|
+
});
|
|
880
914
|
// 完整对话信息:workRuns 统一用持久化记录透出(含被中断的构建)
|
|
881
|
-
const persistedRuns =
|
|
915
|
+
const persistedRuns = snapshotAgent.getPersistedConversationWorkRuns(String(conversationId), ws || undefined);
|
|
882
916
|
if (persistedRuns.length)
|
|
883
917
|
snapshot.workRuns = persistedRuns;
|
|
884
918
|
mobileJson(res, snapshot);
|
|
@@ -915,6 +949,36 @@ async function handleApi(req, res, body) {
|
|
|
915
949
|
mobileJson(res, mobileRightSidebarState(agent, ws, conversationId));
|
|
916
950
|
return;
|
|
917
951
|
}
|
|
952
|
+
case '/api/mobile/conversation-ui-state': {
|
|
953
|
+
const workspaceId = url.searchParams.get('workspaceId') || '';
|
|
954
|
+
const conversationId = url.searchParams.get('conversationId') || '';
|
|
955
|
+
const ws = resolveMobileWorkspace(agent, workspaceId);
|
|
956
|
+
if (!ws) {
|
|
957
|
+
mobileJson(res, { error: 'Unknown workspace' }, 404);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (!conversationId || !agent.hasConversationInWorkspace(conversationId, ws)) {
|
|
961
|
+
mobileJson(res, { error: 'Conversation not found in workspace' }, 404);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
if (hostedConversationUiState) {
|
|
965
|
+
mobileJson(res, await hostedConversationUiState({ workspaceId, conversationId }));
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
const scoped = mobileScopedAgent(ws, conversationId);
|
|
969
|
+
const snapshot = scoped.getConversationSnapshot(conversationId, { workspace: ws, window: 1 });
|
|
970
|
+
mobileJson(res, {
|
|
971
|
+
goal: snapshot.goal,
|
|
972
|
+
flowSelection: snapshot.flowSelection,
|
|
973
|
+
flow: null,
|
|
974
|
+
queued: { steering: [], followUp: [] },
|
|
975
|
+
runtime: null,
|
|
976
|
+
mode: scoped.mode,
|
|
977
|
+
status: scoped.status,
|
|
978
|
+
inputMode: scoped.inputMode,
|
|
979
|
+
});
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
918
982
|
case '/api/mobile/workspace-files': {
|
|
919
983
|
const workspaceId = url.searchParams.get('workspaceId') || '';
|
|
920
984
|
const relativePath = url.searchParams.get('path') || '';
|
|
@@ -1030,6 +1094,52 @@ async function handleApi(req, res, body) {
|
|
|
1030
1094
|
});
|
|
1031
1095
|
return;
|
|
1032
1096
|
}
|
|
1097
|
+
case '/api/mobile/model': {
|
|
1098
|
+
const next = String(JSON.parse(body || '{}').model || '');
|
|
1099
|
+
if (!next) {
|
|
1100
|
+
mobileJson(res, { error: 'Model is required' }, 400);
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
agent.setModel(next);
|
|
1104
|
+
mobileJson(res, {
|
|
1105
|
+
ok: true,
|
|
1106
|
+
model: agent.modelSelectionValue(),
|
|
1107
|
+
modelLabel: agent.modelLabel(),
|
|
1108
|
+
});
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
case '/api/mobile/intelligence': {
|
|
1112
|
+
const tier = String(JSON.parse(body || '{}').tier || 'medium');
|
|
1113
|
+
agent.setIntelligence(tier, true);
|
|
1114
|
+
mobileJson(res, { ok: true, intelligence: agent.intelligence });
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
case '/api/mobile/conversation-ui-action': {
|
|
1118
|
+
const params = JSON.parse(body || '{}');
|
|
1119
|
+
const workspaceId = String(params.workspaceId || '');
|
|
1120
|
+
const conversationId = String(params.conversationId || '');
|
|
1121
|
+
const action = String(params.action || '');
|
|
1122
|
+
const allowed = new Set(['goal_update', 'goal_guide', 'conversation_guide', 'goal_toggle_pause', 'goal_clear', 'flow_pause', 'flow_resume', 'flow_guide', 'conversation_stop', 'input_mode', 'queue_enqueue', 'queue_update', 'queue_delete', 'queue_reorder', 'queue_toggle_pause', 'queue_guide']);
|
|
1123
|
+
if (!workspaceId || !conversationId || !allowed.has(action)) {
|
|
1124
|
+
mobileJson(res, { error: 'workspaceId, conversationId, and a valid action are required' }, 400);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
const ws = resolveMobileWorkspace(agent, workspaceId);
|
|
1128
|
+
if (!ws) {
|
|
1129
|
+
mobileJson(res, { error: 'Unknown workspace' }, 404);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
if (!agent.hasConversationInWorkspace(conversationId, ws)) {
|
|
1133
|
+
mobileJson(res, { error: 'Conversation not found in workspace' }, 404);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
if (!hostedConversationUiAction) {
|
|
1137
|
+
mobileJson(res, { error: 'This action requires the GUI-hosted mobile server' }, 409);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
mobileJson(res, await hostedConversationUiAction({ workspaceId, conversationId }, action, String(params.value || ''), params));
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1033
1143
|
case '/api/mobile/conversation-rename': {
|
|
1034
1144
|
const params = JSON.parse(body || '{}');
|
|
1035
1145
|
const workspaceId = String(params.workspaceId || '');
|
|
@@ -1144,6 +1254,15 @@ async function handleApi(req, res, body) {
|
|
|
1144
1254
|
requestAgent = mobileScopedAgent(ws, conversationId);
|
|
1145
1255
|
}
|
|
1146
1256
|
}
|
|
1257
|
+
if (hostedConversationPrompt && resolvedWorkspace) {
|
|
1258
|
+
const result = await hostedConversationPrompt({ workspaceId, conversationId }, message, {
|
|
1259
|
+
requestedMode: String(params.requestedMode || ''),
|
|
1260
|
+
goalObjective: String(params.goalObjective || ''),
|
|
1261
|
+
inputMode: String(params.inputMode || ''),
|
|
1262
|
+
});
|
|
1263
|
+
mobileJson(res, { ok: true, conversationId, ...result });
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1147
1266
|
if (requestAgent === agent && params.conversationId)
|
|
1148
1267
|
requestAgent.setConversation(conversationId);
|
|
1149
1268
|
let scopedRuntimeKey = '';
|
|
@@ -1151,7 +1270,7 @@ async function handleApi(req, res, body) {
|
|
|
1151
1270
|
let unsubscribeScoped = null;
|
|
1152
1271
|
if (requestAgent !== agent && requestWorkspace) {
|
|
1153
1272
|
scopedRuntimeKey = mobileRuntimeKey(String(requestWorkspace.id || ''), conversationId);
|
|
1154
|
-
unsubscribeScoped = requestAgent.subscribeWorkEvents(event =>
|
|
1273
|
+
unsubscribeScoped = requestAgent.subscribeWorkEvents(event => publishServerWorkEvent({
|
|
1155
1274
|
...event,
|
|
1156
1275
|
workspaceId: String(requestWorkspace?.id || ''),
|
|
1157
1276
|
conversationId,
|
|
@@ -1302,40 +1421,52 @@ async function handleApi(req, res, body) {
|
|
|
1302
1421
|
jsonResponse(res, { error: e.message }, 500);
|
|
1303
1422
|
}
|
|
1304
1423
|
}
|
|
1305
|
-
function startServer(root) {
|
|
1424
|
+
function startServer(root, options = {}) {
|
|
1306
1425
|
mobileToken = (0, mobilePairing_1.ensureMobileToken)(root);
|
|
1307
1426
|
appRoot = root;
|
|
1308
|
-
agent = new agent_1.Agent(root);
|
|
1427
|
+
agent = options.agent || new agent_1.Agent(root);
|
|
1428
|
+
hostedWorkEventSink = options.onWorkEvent || null;
|
|
1429
|
+
hostedConversationUiState = options.conversationUiState;
|
|
1430
|
+
hostedConversationUiAction = options.conversationUiAction;
|
|
1431
|
+
hostedConversationPrompt = options.conversationPrompt;
|
|
1432
|
+
unsubscribeHostedWorkEvents?.();
|
|
1433
|
+
unsubscribeHostedWorkEvents = options.subscribeWorkEvents?.(publishMobileWorkEvent) || null;
|
|
1309
1434
|
workspaceFileRouter = new workspaceFileRouter_1.WorkspaceFileRouter(() => path.resolve(agent?.workspace.current?.path || root));
|
|
1310
|
-
automation =
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
try {
|
|
1325
|
-
const tokens = await agent.process(prompt);
|
|
1326
|
-
return tokens.map(t => t.text).join('');
|
|
1327
|
-
}
|
|
1328
|
-
finally {
|
|
1435
|
+
automation = options.automation || null;
|
|
1436
|
+
if (!automation && !options.agent)
|
|
1437
|
+
automation = new automation_1.AutomationManager(agent.config, async (prompt, model, item) => {
|
|
1438
|
+
if (!agent)
|
|
1439
|
+
return '';
|
|
1440
|
+
const previousModel = agent.model;
|
|
1441
|
+
const previousWorkspace = agent.workspace.current?.id || agent.workspace.current?.path || '';
|
|
1442
|
+
const previousConversation = agent.activeConversationId;
|
|
1443
|
+
agent.selectWorkspaceFromStorage(item.workspaceId);
|
|
1444
|
+
const conversationId = item.conversationMode === 'existing'
|
|
1445
|
+
? item.conversationId
|
|
1446
|
+
: `automation-${item.id}-${Date.now().toString(36)}`;
|
|
1447
|
+
agent.setConversationFromStorage(conversationId);
|
|
1448
|
+
automation?.update(item.id, { lastConversationId: conversationId });
|
|
1329
1449
|
if (model)
|
|
1330
|
-
agent.setModel(
|
|
1331
|
-
|
|
1332
|
-
agent.
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1450
|
+
agent.setModel(model);
|
|
1451
|
+
try {
|
|
1452
|
+
const tokens = await agent.process(prompt);
|
|
1453
|
+
return tokens.map(t => t.text).join('');
|
|
1454
|
+
}
|
|
1455
|
+
finally {
|
|
1456
|
+
if (model)
|
|
1457
|
+
agent.setModel(previousModel);
|
|
1458
|
+
if (previousWorkspace)
|
|
1459
|
+
agent.selectWorkspaceFromStorage(previousWorkspace);
|
|
1460
|
+
agent.setConversationFromStorage(previousConversation);
|
|
1461
|
+
}
|
|
1462
|
+
});
|
|
1463
|
+
unsubscribeServerAgentWorkEvents?.();
|
|
1464
|
+
unsubscribeServerAgentWorkEvents = agent.subscribeWorkEvents(publishServerWorkEvent);
|
|
1465
|
+
if (automation) {
|
|
1466
|
+
agent.setAutomationManager(automation);
|
|
1467
|
+
if (!options.automation)
|
|
1468
|
+
automation.start();
|
|
1469
|
+
}
|
|
1339
1470
|
const uiDir = path.join(__dirname, 'ui');
|
|
1340
1471
|
const server = http.createServer(async (req, res) => {
|
|
1341
1472
|
if (req.method === 'OPTIONS') {
|
|
@@ -1374,6 +1505,14 @@ function startServer(root) {
|
|
|
1374
1505
|
const lan = (0, mobilePairing_1.lanIpv4)();
|
|
1375
1506
|
const accessHost = tailscale || lan || '<lan-or-tailscale-ip>';
|
|
1376
1507
|
const tokenPath = path.join(root, '.newmark-mobile-token');
|
|
1508
|
+
hostedServer = server;
|
|
1509
|
+
hostedServerError = '';
|
|
1510
|
+
hostedServerStartedAt = Date.now();
|
|
1511
|
+
server.once('error', error => {
|
|
1512
|
+
hostedServerError = error instanceof Error ? error.message : String(error);
|
|
1513
|
+
if (hostedServer === server)
|
|
1514
|
+
hostedServer = null;
|
|
1515
|
+
});
|
|
1377
1516
|
server.listen(PORT, bindHost, () => {
|
|
1378
1517
|
console.log(`\n Newmark Agent v1.0 - Server Mode`);
|
|
1379
1518
|
console.log(` Bind: ${bindHost}:${PORT}`);
|
|
@@ -1385,13 +1524,140 @@ function startServer(root) {
|
|
|
1385
1524
|
console.log(` Mobile events (SSE): http://${accessHost}:${PORT}/api/mobile/events?token=<token>`);
|
|
1386
1525
|
console.log(` Press Ctrl+C to stop\n`);
|
|
1387
1526
|
});
|
|
1527
|
+
return server;
|
|
1528
|
+
}
|
|
1529
|
+
function configuredAccessHost() {
|
|
1530
|
+
const now = Date.now();
|
|
1531
|
+
if (now - hostedAccessHostCheckedAt < 30_000)
|
|
1532
|
+
return hostedAccessHost;
|
|
1533
|
+
hostedAccessHost = (0, mobilePairing_1.tailscaleIpv4)() || (0, mobilePairing_1.lanIpv4)() || '';
|
|
1534
|
+
hostedAccessHostCheckedAt = now;
|
|
1535
|
+
return hostedAccessHost;
|
|
1536
|
+
}
|
|
1537
|
+
function waitForHostedServerListening(timeoutMs = 5000) {
|
|
1538
|
+
if (hostedServer?.listening)
|
|
1539
|
+
return Promise.resolve();
|
|
1540
|
+
return new Promise((resolve, reject) => {
|
|
1541
|
+
const deadline = Date.now() + timeoutMs;
|
|
1542
|
+
const tick = () => {
|
|
1543
|
+
if (hostedServer?.listening)
|
|
1544
|
+
return resolve();
|
|
1545
|
+
if (hostedServerError)
|
|
1546
|
+
return reject(new Error(hostedServerError));
|
|
1547
|
+
if (Date.now() >= deadline)
|
|
1548
|
+
return reject(new Error('Mobile server listen timeout'));
|
|
1549
|
+
setTimeout(tick, 50);
|
|
1550
|
+
};
|
|
1551
|
+
tick();
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
async function probeHostedServer(host) {
|
|
1555
|
+
if (!hostedServer?.listening || !mobileToken)
|
|
1556
|
+
return { ok: false, error: hostedServerError || 'Mobile server is not listening' };
|
|
1557
|
+
return await new Promise(resolve => {
|
|
1558
|
+
const request = http.get({
|
|
1559
|
+
hostname: host,
|
|
1560
|
+
port: PORT,
|
|
1561
|
+
path: `/api/mobile/hello?token=${encodeURIComponent(mobileToken)}`,
|
|
1562
|
+
timeout: 1800,
|
|
1563
|
+
}, response => {
|
|
1564
|
+
let body = '';
|
|
1565
|
+
response.setEncoding('utf8');
|
|
1566
|
+
response.on('data', chunk => { body += chunk; });
|
|
1567
|
+
response.on('end', () => {
|
|
1568
|
+
try {
|
|
1569
|
+
const parsed = JSON.parse(body || '{}');
|
|
1570
|
+
const ok = response.statusCode === 200 && parsed.ok === true;
|
|
1571
|
+
resolve({ ok, error: ok ? '' : String(parsed.error || `HTTP ${response.statusCode || 0}`) });
|
|
1572
|
+
}
|
|
1573
|
+
catch (error) {
|
|
1574
|
+
resolve({ ok: false, error: error instanceof Error ? error.message : String(error) });
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
});
|
|
1578
|
+
request.on('timeout', () => request.destroy(new Error('Mobile server probe timeout')));
|
|
1579
|
+
request.on('error', error => resolve({ ok: false, error: error.message }));
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
function configureHostedServer(root, options = {}) {
|
|
1583
|
+
hostedServerRoot = root;
|
|
1584
|
+
hostedServerOptions = options;
|
|
1585
|
+
}
|
|
1586
|
+
async function stopHostedServer() {
|
|
1587
|
+
const server = hostedServer;
|
|
1588
|
+
hostedServer = null;
|
|
1589
|
+
if (server) {
|
|
1590
|
+
await new Promise(resolve => {
|
|
1591
|
+
const timer = setTimeout(resolve, 2500);
|
|
1592
|
+
server.close(() => {
|
|
1593
|
+
clearTimeout(timer);
|
|
1594
|
+
resolve();
|
|
1595
|
+
});
|
|
1596
|
+
server.closeAllConnections?.();
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
unsubscribeHostedWorkEvents?.();
|
|
1600
|
+
unsubscribeHostedWorkEvents = null;
|
|
1601
|
+
unsubscribeServerAgentWorkEvents?.();
|
|
1602
|
+
unsubscribeServerAgentWorkEvents = null;
|
|
1603
|
+
hostedServerStartedAt = 0;
|
|
1604
|
+
}
|
|
1605
|
+
async function hostedServerStatus(enabled = true, probeHost = '') {
|
|
1606
|
+
const listening = !!hostedServer?.listening;
|
|
1607
|
+
const host = enabled ? (String(probeHost || '').trim() || configuredAccessHost()) : '';
|
|
1608
|
+
let reachable = false;
|
|
1609
|
+
let error = hostedServerError;
|
|
1610
|
+
if (enabled && listening) {
|
|
1611
|
+
if (!host)
|
|
1612
|
+
error = 'No LAN or Tailscale IPv4 address is available';
|
|
1613
|
+
else {
|
|
1614
|
+
const probe = await probeHostedServer(host);
|
|
1615
|
+
reachable = probe.ok;
|
|
1616
|
+
if (!probe.ok)
|
|
1617
|
+
error = probe.error;
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
const state = !enabled ? 'off' : listening && reachable ? 'listening' : 'error';
|
|
1621
|
+
return {
|
|
1622
|
+
enabled,
|
|
1623
|
+
listening,
|
|
1624
|
+
reachable,
|
|
1625
|
+
state,
|
|
1626
|
+
host,
|
|
1627
|
+
port: PORT,
|
|
1628
|
+
error: state === 'error' ? (error || 'Mobile server is not reachable') : '',
|
|
1629
|
+
checkedAt: new Date().toISOString(),
|
|
1630
|
+
startedAt: hostedServerStartedAt,
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
async function setHostedServerEnabled(enabled, probeHost = '') {
|
|
1634
|
+
await stopHostedServer();
|
|
1635
|
+
hostedServerError = '';
|
|
1636
|
+
if (enabled) {
|
|
1637
|
+
if (!hostedServerRoot)
|
|
1638
|
+
throw new Error('Hosted mobile server is not configured');
|
|
1639
|
+
startServer(hostedServerRoot, hostedServerOptions);
|
|
1640
|
+
try {
|
|
1641
|
+
await waitForHostedServerListening();
|
|
1642
|
+
}
|
|
1643
|
+
catch (error) {
|
|
1644
|
+
hostedServerError = error instanceof Error ? error.message : String(error);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return await hostedServerStatus(enabled, probeHost);
|
|
1388
1648
|
}
|
|
1389
|
-
let hostedServerStarted = false;
|
|
1390
1649
|
/** 托管启动 server(GUI/TUI 内嵌调用;幂等防重入,进程常驻即服务常驻) */
|
|
1391
|
-
function runServer(root) {
|
|
1392
|
-
if (
|
|
1650
|
+
function runServer(root, options = {}) {
|
|
1651
|
+
if (!hostedServerRoot || Object.keys(options).length > 0)
|
|
1652
|
+
configureHostedServer(root, options);
|
|
1653
|
+
else
|
|
1654
|
+
hostedServerRoot = root;
|
|
1655
|
+
if (hostedServer?.listening)
|
|
1393
1656
|
return;
|
|
1394
|
-
|
|
1395
|
-
|
|
1657
|
+
startServer(hostedServerRoot, hostedServerOptions);
|
|
1658
|
+
}
|
|
1659
|
+
/** Attach the GUI-owned automation manager after deferred startup. */
|
|
1660
|
+
function setHostedServerAutomation(manager) {
|
|
1661
|
+
automation = manager;
|
|
1396
1662
|
}
|
|
1397
1663
|
//# sourceMappingURL=server.js.map
|
package/dist/tools/index.js
CHANGED
|
@@ -294,6 +294,13 @@ class ToolExecutor {
|
|
|
294
294
|
max_refs: { type: 'number' },
|
|
295
295
|
attribute: { type: 'string' },
|
|
296
296
|
}, ['action']),
|
|
297
|
+
t('screen_capture', 'Read-only active Windows screenshot available without provisioning or starting full Computer Use. Capture the whole desktop or one visible application. The image is a one-use model input and is deleted immediately; semantic Windows UI objects are returned alongside it.', {
|
|
298
|
+
target: { type: 'string', enum: ['desktop', 'application'], description: 'Capture the whole desktop or one visible application. Defaults to desktop.' },
|
|
299
|
+
app_target: { type: 'string', description: 'For target=application: application title, process name, process id, or window handle.' },
|
|
300
|
+
capture_max_width: { type: 'number', minimum: 320, maximum: 2048, description: 'Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged.' },
|
|
301
|
+
capture_max_height: { type: 'number', minimum: 240, maximum: 2048, description: 'Maximum ephemeral screenshot height; defaults to 960. Aspect ratio is preserved and the source is never enlarged.' },
|
|
302
|
+
max_chars: { type: 'number', minimum: 1000, maximum: 100000, description: 'Maximum returned semantic UI text characters.' },
|
|
303
|
+
}, []),
|
|
297
304
|
t('computer_use', 'Native Windows desktop control with persistent observation/action helpers. Use observe/app_observe before acting. sequence may perform up to three stable low-risk move/click/scroll/wait/app_activate steps and stops when focus, window, menu, dialog, scene, or risk changes. Vision screenshots remain one-time inputs and are deleted immediately.', {
|
|
298
305
|
action: { type: 'string', enum: ['observe', 'app_list', 'app_observe', 'sequence', 'takeover_start', 'takeover_stop', 'move', 'click', 'scroll', 'type', 'key', 'wait', 'app_activate', 'app_click', 'app_scroll', 'app_type', 'app_key'] },
|
|
299
306
|
capture_max_width: { type: 'number', minimum: 320, maximum: 2048, description: 'For observe/app_observe only. Maximum ephemeral screenshot width; defaults to 1280. Aspect ratio is preserved and the source is never enlarged.' },
|
|
@@ -790,6 +797,44 @@ class ToolExecutor {
|
|
|
790
797
|
result: parsed,
|
|
791
798
|
}, null, 2);
|
|
792
799
|
}
|
|
800
|
+
case 'screen_capture': {
|
|
801
|
+
const target = g('target').toLowerCase() === 'application' ? 'application' : 'desktop';
|
|
802
|
+
if (process.env.NEWMARK_WSL_DISTRO) {
|
|
803
|
+
const result = await (0, wslHostToolBridge_1.requestWindowsHostTool)('screen_capture', args, {
|
|
804
|
+
conversationId: context.conversationId || 'default',
|
|
805
|
+
workspaceId: context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(context.workspacePath || wsPath),
|
|
806
|
+
actorId: context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID,
|
|
807
|
+
runtimeKey: browserUseScope(context, wsPath).runtimeKey,
|
|
808
|
+
allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
|
|
809
|
+
mode: context.mode || 'build',
|
|
810
|
+
}, 120_000, context.signal);
|
|
811
|
+
return typeof result === 'string' ? result : JSON.stringify(result);
|
|
812
|
+
}
|
|
813
|
+
if (process.env.NEWMARK_ISOLATED_RUNTIME === '1') {
|
|
814
|
+
const result = await (0, utilityHostToolBridge_1.requestUtilityHostTool)('screen_capture', args, {
|
|
815
|
+
conversationId: context.conversationId || 'default',
|
|
816
|
+
workspaceId: context.workspaceId || (0, terminalTakeover_1.terminalTakeoverWorkspaceId)(context.workspacePath || wsPath),
|
|
817
|
+
actorId: context.actorId || terminalTakeover_1.ROOT_TERMINAL_ACTOR_ID,
|
|
818
|
+
workspacePath: context.workspacePath || wsPath,
|
|
819
|
+
backend: 'utility',
|
|
820
|
+
mode: context.mode || 'build',
|
|
821
|
+
allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
|
|
822
|
+
}, 120_000, context.signal);
|
|
823
|
+
return typeof result === 'string' ? result : JSON.stringify(result);
|
|
824
|
+
}
|
|
825
|
+
return await (0, computerUse_1.runComputerUse)({
|
|
826
|
+
action: target === 'application' ? 'app_observe' : 'observe',
|
|
827
|
+
appTarget: g('app_target'),
|
|
828
|
+
windowHandle: target === 'application' ? g('app_target') : '',
|
|
829
|
+
maxChars: Number(args.max_chars || 30000),
|
|
830
|
+
workspacePath: wsPath,
|
|
831
|
+
allowEphemeralVisionImage: context.allowEphemeralVisionImage === true,
|
|
832
|
+
captureMaxWidth: Number(args.capture_max_width),
|
|
833
|
+
captureMaxHeight: Number(args.capture_max_height),
|
|
834
|
+
invocation: context.invocation,
|
|
835
|
+
ownerId: `screen-capture:${computerUseOwner(context, wsPath)}:${String(context.actorId || 'root')}`,
|
|
836
|
+
});
|
|
837
|
+
}
|
|
793
838
|
case 'computer_use': {
|
|
794
839
|
const action = normalizeComputerUseAction(g('action'));
|
|
795
840
|
const owner = `${computerUseOwner(context, wsPath)}:${String(context.actorId || 'root')}`;
|
|
@@ -976,7 +1021,7 @@ class ToolExecutor {
|
|
|
976
1021
|
hostSupportsTool(name) {
|
|
977
1022
|
if (name.startsWith('browser_') && !this.hostProfile.electronBrowser)
|
|
978
1023
|
return false;
|
|
979
|
-
if (name === 'computer_use' && !this.hostProfile.windowsComputerUse)
|
|
1024
|
+
if ((name === 'computer_use' || name === 'screen_capture') && !this.hostProfile.windowsComputerUse)
|
|
980
1025
|
return false;
|
|
981
1026
|
return true;
|
|
982
1027
|
}
|
|
@@ -26,6 +26,7 @@ exports.NATIVE_TOOL_CATALOG = [
|
|
|
26
26
|
{ name: 'browser_reload', label: 'Browser reload', description: 'Reload the built-in browser.', category: 'browser', defaultEnabled: true },
|
|
27
27
|
{ name: 'browser_cdp', label: 'Browser CDP', description: 'Run an advanced Chrome DevTools Protocol command.', category: 'browser', defaultEnabled: true },
|
|
28
28
|
{ name: 'browser_use', label: 'Browser Use', description: 'Observe and control the built-in browser through scoped opaque element refs and structured receipts.', category: 'browser', defaultEnabled: true },
|
|
29
|
+
{ name: 'screen_capture', label: 'Active screenshot', description: 'Capture the Windows desktop or one visible application without starting the full Computer Use control surface.', category: 'desktop', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
29
30
|
{ name: 'computer_use', label: 'Computer Use', description: 'Observe and control Windows desktop UI with screenshots and semantic objects.', category: 'desktop', defaultEnabled: true },
|
|
30
31
|
{ name: 'image_inspect', label: 'Image inspect', description: 'Crop and magnify images submitted in the current conversation for closer visual inspection.', category: 'core', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
31
32
|
{ name: 'image_display', label: 'Image display', description: 'Present a workspace PNG/JPEG inside the current Build Block.', category: 'core', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|