newmark-agent 0.4.7 → 0.4.8

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/server.js CHANGED
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.runServer = runServer;
37
+ exports.setHostedServerAutomation = setHostedServerAutomation;
37
38
  const http = __importStar(require("http"));
38
39
  const fs = __importStar(require("fs"));
39
40
  const path = __importStar(require("path"));
@@ -56,6 +57,11 @@ let mobileToken = '';
56
57
  let appRoot = '';
57
58
  const mobileWorkEventSubscribers = new Set();
58
59
  const mobileScopedRuntimes = new Map();
60
+ let hostedWorkEventSink = null;
61
+ let unsubscribeHostedWorkEvents = null;
62
+ let hostedConversationUiState;
63
+ let hostedConversationUiAction;
64
+ let hostedConversationPrompt;
59
65
  function mobileRuntimeKey(workspaceId, conversationId) {
60
66
  return `${String(workspaceId || '')}::${String(conversationId || '')}`;
61
67
  }
@@ -67,6 +73,10 @@ function publishMobileWorkEvent(event) {
67
73
  catch { /* ignore disconnected mobile listeners */ }
68
74
  }
69
75
  }
76
+ function publishServerWorkEvent(event) {
77
+ publishMobileWorkEvent(event);
78
+ hostedWorkEventSink?.(event);
79
+ }
70
80
  function mobileAuthorized(req) {
71
81
  if (!mobileToken)
72
82
  return false;
@@ -837,6 +847,9 @@ async function handleApi(req, res, body) {
837
847
  mode: agent.mode,
838
848
  model: agent.modelSelectionValue(),
839
849
  modelLabel: agent.modelLabel(),
850
+ models: agent.allModelNames(),
851
+ providers: (0, config_1.sanitizeProvidersForState)(agent.config.providers()),
852
+ intelligence: agent.intelligence,
840
853
  status: agent.status,
841
854
  activeConversationId: agent.activeConversationId,
842
855
  conversations: agent.listConversationStates(),
@@ -876,9 +889,18 @@ async function handleApi(req, res, body) {
876
889
  mobileJson(res, { error: 'Conversation not found in workspace' }, 404);
877
890
  return;
878
891
  }
879
- const snapshot = current.getConversationSnapshot(String(conversationId), { window: windowSize, before, workspace: ws });
892
+ // GUI conversations normally execute inside isolated runtime pools.
893
+ // Their durable target state can be newer than the hosted main Agent's
894
+ // foreground memory, so workspace-bound mobile reads must hydrate a
895
+ // fresh read-only target Agent from storage.
896
+ const snapshotAgent = ws ? mobileScopedAgent(ws, String(conversationId)) : current;
897
+ const snapshot = snapshotAgent.getConversationSnapshot(String(conversationId), {
898
+ window: windowSize,
899
+ before,
900
+ workspace: ws || undefined,
901
+ });
880
902
  // 完整对话信息:workRuns 统一用持久化记录透出(含被中断的构建)
881
- const persistedRuns = current.getPersistedConversationWorkRuns(String(conversationId), ws || undefined);
903
+ const persistedRuns = snapshotAgent.getPersistedConversationWorkRuns(String(conversationId), ws || undefined);
882
904
  if (persistedRuns.length)
883
905
  snapshot.workRuns = persistedRuns;
884
906
  mobileJson(res, snapshot);
@@ -915,6 +937,36 @@ async function handleApi(req, res, body) {
915
937
  mobileJson(res, mobileRightSidebarState(agent, ws, conversationId));
916
938
  return;
917
939
  }
940
+ case '/api/mobile/conversation-ui-state': {
941
+ const workspaceId = url.searchParams.get('workspaceId') || '';
942
+ const conversationId = url.searchParams.get('conversationId') || '';
943
+ const ws = resolveMobileWorkspace(agent, workspaceId);
944
+ if (!ws) {
945
+ mobileJson(res, { error: 'Unknown workspace' }, 404);
946
+ return;
947
+ }
948
+ if (!conversationId || !agent.hasConversationInWorkspace(conversationId, ws)) {
949
+ mobileJson(res, { error: 'Conversation not found in workspace' }, 404);
950
+ return;
951
+ }
952
+ if (hostedConversationUiState) {
953
+ mobileJson(res, await hostedConversationUiState({ workspaceId, conversationId }));
954
+ return;
955
+ }
956
+ const scoped = mobileScopedAgent(ws, conversationId);
957
+ const snapshot = scoped.getConversationSnapshot(conversationId, { workspace: ws, window: 1 });
958
+ mobileJson(res, {
959
+ goal: snapshot.goal,
960
+ flowSelection: snapshot.flowSelection,
961
+ flow: null,
962
+ queued: { steering: [], followUp: [] },
963
+ runtime: null,
964
+ mode: scoped.mode,
965
+ status: scoped.status,
966
+ inputMode: scoped.inputMode,
967
+ });
968
+ return;
969
+ }
918
970
  case '/api/mobile/workspace-files': {
919
971
  const workspaceId = url.searchParams.get('workspaceId') || '';
920
972
  const relativePath = url.searchParams.get('path') || '';
@@ -1030,6 +1082,52 @@ async function handleApi(req, res, body) {
1030
1082
  });
1031
1083
  return;
1032
1084
  }
1085
+ case '/api/mobile/model': {
1086
+ const next = String(JSON.parse(body || '{}').model || '');
1087
+ if (!next) {
1088
+ mobileJson(res, { error: 'Model is required' }, 400);
1089
+ return;
1090
+ }
1091
+ agent.setModel(next);
1092
+ mobileJson(res, {
1093
+ ok: true,
1094
+ model: agent.modelSelectionValue(),
1095
+ modelLabel: agent.modelLabel(),
1096
+ });
1097
+ return;
1098
+ }
1099
+ case '/api/mobile/intelligence': {
1100
+ const tier = String(JSON.parse(body || '{}').tier || 'medium');
1101
+ agent.setIntelligence(tier, true);
1102
+ mobileJson(res, { ok: true, intelligence: agent.intelligence });
1103
+ return;
1104
+ }
1105
+ case '/api/mobile/conversation-ui-action': {
1106
+ const params = JSON.parse(body || '{}');
1107
+ const workspaceId = String(params.workspaceId || '');
1108
+ const conversationId = String(params.conversationId || '');
1109
+ const action = String(params.action || '');
1110
+ 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_toggle_pause', 'queue_guide']);
1111
+ if (!workspaceId || !conversationId || !allowed.has(action)) {
1112
+ mobileJson(res, { error: 'workspaceId, conversationId, and a valid action are required' }, 400);
1113
+ return;
1114
+ }
1115
+ const ws = resolveMobileWorkspace(agent, workspaceId);
1116
+ if (!ws) {
1117
+ mobileJson(res, { error: 'Unknown workspace' }, 404);
1118
+ return;
1119
+ }
1120
+ if (!agent.hasConversationInWorkspace(conversationId, ws)) {
1121
+ mobileJson(res, { error: 'Conversation not found in workspace' }, 404);
1122
+ return;
1123
+ }
1124
+ if (!hostedConversationUiAction) {
1125
+ mobileJson(res, { error: 'This action requires the GUI-hosted mobile server' }, 409);
1126
+ return;
1127
+ }
1128
+ mobileJson(res, await hostedConversationUiAction({ workspaceId, conversationId }, action, String(params.value || ''), params));
1129
+ return;
1130
+ }
1033
1131
  case '/api/mobile/conversation-rename': {
1034
1132
  const params = JSON.parse(body || '{}');
1035
1133
  const workspaceId = String(params.workspaceId || '');
@@ -1144,6 +1242,15 @@ async function handleApi(req, res, body) {
1144
1242
  requestAgent = mobileScopedAgent(ws, conversationId);
1145
1243
  }
1146
1244
  }
1245
+ if (hostedConversationPrompt && resolvedWorkspace) {
1246
+ const result = await hostedConversationPrompt({ workspaceId, conversationId }, message, {
1247
+ requestedMode: String(params.requestedMode || ''),
1248
+ goalObjective: String(params.goalObjective || ''),
1249
+ inputMode: String(params.inputMode || ''),
1250
+ });
1251
+ mobileJson(res, { ok: true, conversationId, ...result });
1252
+ return;
1253
+ }
1147
1254
  if (requestAgent === agent && params.conversationId)
1148
1255
  requestAgent.setConversation(conversationId);
1149
1256
  let scopedRuntimeKey = '';
@@ -1151,7 +1258,7 @@ async function handleApi(req, res, body) {
1151
1258
  let unsubscribeScoped = null;
1152
1259
  if (requestAgent !== agent && requestWorkspace) {
1153
1260
  scopedRuntimeKey = mobileRuntimeKey(String(requestWorkspace.id || ''), conversationId);
1154
- unsubscribeScoped = requestAgent.subscribeWorkEvents(event => publishMobileWorkEvent({
1261
+ unsubscribeScoped = requestAgent.subscribeWorkEvents(event => publishServerWorkEvent({
1155
1262
  ...event,
1156
1263
  workspaceId: String(requestWorkspace?.id || ''),
1157
1264
  conversationId,
@@ -1302,40 +1409,51 @@ async function handleApi(req, res, body) {
1302
1409
  jsonResponse(res, { error: e.message }, 500);
1303
1410
  }
1304
1411
  }
1305
- function startServer(root) {
1412
+ function startServer(root, options = {}) {
1306
1413
  mobileToken = (0, mobilePairing_1.ensureMobileToken)(root);
1307
1414
  appRoot = root;
1308
- agent = new agent_1.Agent(root);
1415
+ agent = options.agent || new agent_1.Agent(root);
1416
+ hostedWorkEventSink = options.onWorkEvent || null;
1417
+ hostedConversationUiState = options.conversationUiState;
1418
+ hostedConversationUiAction = options.conversationUiAction;
1419
+ hostedConversationPrompt = options.conversationPrompt;
1420
+ unsubscribeHostedWorkEvents?.();
1421
+ unsubscribeHostedWorkEvents = options.subscribeWorkEvents?.(publishMobileWorkEvent) || null;
1309
1422
  workspaceFileRouter = new workspaceFileRouter_1.WorkspaceFileRouter(() => path.resolve(agent?.workspace.current?.path || root));
1310
- automation = new automation_1.AutomationManager(agent.config, async (prompt, model, item) => {
1311
- if (!agent)
1312
- return '';
1313
- const previousModel = agent.model;
1314
- const previousWorkspace = agent.workspace.current?.id || agent.workspace.current?.path || '';
1315
- const previousConversation = agent.activeConversationId;
1316
- agent.selectWorkspaceFromStorage(item.workspaceId);
1317
- const conversationId = item.conversationMode === 'existing'
1318
- ? item.conversationId
1319
- : `automation-${item.id}-${Date.now().toString(36)}`;
1320
- agent.setConversationFromStorage(conversationId);
1321
- automation?.update(item.id, { lastConversationId: conversationId });
1322
- if (model)
1323
- agent.setModel(model);
1324
- try {
1325
- const tokens = await agent.process(prompt);
1326
- return tokens.map(t => t.text).join('');
1327
- }
1328
- finally {
1423
+ automation = options.automation || null;
1424
+ if (!automation && !options.agent)
1425
+ automation = new automation_1.AutomationManager(agent.config, async (prompt, model, item) => {
1426
+ if (!agent)
1427
+ return '';
1428
+ const previousModel = agent.model;
1429
+ const previousWorkspace = agent.workspace.current?.id || agent.workspace.current?.path || '';
1430
+ const previousConversation = agent.activeConversationId;
1431
+ agent.selectWorkspaceFromStorage(item.workspaceId);
1432
+ const conversationId = item.conversationMode === 'existing'
1433
+ ? item.conversationId
1434
+ : `automation-${item.id}-${Date.now().toString(36)}`;
1435
+ agent.setConversationFromStorage(conversationId);
1436
+ automation?.update(item.id, { lastConversationId: conversationId });
1329
1437
  if (model)
1330
- agent.setModel(previousModel);
1331
- if (previousWorkspace)
1332
- agent.selectWorkspaceFromStorage(previousWorkspace);
1333
- agent.setConversationFromStorage(previousConversation);
1334
- }
1335
- });
1336
- agent.subscribeWorkEvents(publishMobileWorkEvent);
1337
- agent.setAutomationManager(automation);
1338
- automation.start();
1438
+ agent.setModel(model);
1439
+ try {
1440
+ const tokens = await agent.process(prompt);
1441
+ return tokens.map(t => t.text).join('');
1442
+ }
1443
+ finally {
1444
+ if (model)
1445
+ agent.setModel(previousModel);
1446
+ if (previousWorkspace)
1447
+ agent.selectWorkspaceFromStorage(previousWorkspace);
1448
+ agent.setConversationFromStorage(previousConversation);
1449
+ }
1450
+ });
1451
+ agent.subscribeWorkEvents(publishServerWorkEvent);
1452
+ if (automation) {
1453
+ agent.setAutomationManager(automation);
1454
+ if (!options.automation)
1455
+ automation.start();
1456
+ }
1339
1457
  const uiDir = path.join(__dirname, 'ui');
1340
1458
  const server = http.createServer(async (req, res) => {
1341
1459
  if (req.method === 'OPTIONS') {
@@ -1388,10 +1506,14 @@ function startServer(root) {
1388
1506
  }
1389
1507
  let hostedServerStarted = false;
1390
1508
  /** 托管启动 server(GUI/TUI 内嵌调用;幂等防重入,进程常驻即服务常驻) */
1391
- function runServer(root) {
1509
+ function runServer(root, options = {}) {
1392
1510
  if (hostedServerStarted)
1393
1511
  return;
1394
1512
  hostedServerStarted = true;
1395
- startServer(root);
1513
+ startServer(root, options);
1514
+ }
1515
+ /** Attach the GUI-owned automation manager after deferred startup. */
1516
+ function setHostedServerAutomation(manager) {
1517
+ automation = manager;
1396
1518
  }
1397
1519
  //# sourceMappingURL=server.js.map
@@ -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' },