newmark-agent 0.4.4 → 0.4.6

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/main.js CHANGED
@@ -55,6 +55,7 @@ const cli_discovery_1 = require("./cli-discovery");
55
55
  const config_1 = require("./core/config");
56
56
  const memoryLab_1 = require("./core/memoryLab");
57
57
  const installUpdate_1 = require("./core/installUpdate");
58
+ const mobilePairing_1 = require("./core/mobilePairing");
58
59
  const terminalTakeover_1 = require("./tools/terminalTakeover");
59
60
  const nativeBash_1 = require("./core/nativeBash");
60
61
  const nativeTools_1 = require("./tools/nativeTools");
@@ -3472,6 +3473,7 @@ else {
3472
3473
  },
3473
3474
  configuredAgentBackend: agent.config.getBool('agent', 'run_in_wsl') ? 'wsl' : 'windows',
3474
3475
  agentBackendRestartRequired: (agent.config.getBool('agent', 'run_in_wsl') ? 'wsl' : 'windows') !== activeAgentBackendMode,
3476
+ remoteTouchEnabled: agent.config.getBool('remote', 'touch_enabled'),
3475
3477
  wslAvailable: wslDistros.length > 0,
3476
3478
  wslDistros,
3477
3479
  };
@@ -4784,6 +4786,15 @@ else {
4784
4786
  return { servers: mcpManager.list(), discovered };
4785
4787
  });
4786
4788
  electron_1.ipcMain.handle('dsh:discover', async () => (0, dshCompatibility_1.discoverDshCompatibility)(root));
4789
+ electron_1.ipcMain.handle('dsh:installBundle', async (_event, manifestPath) => {
4790
+ return (0, dshCompatibility_1.installDshBundle)(root, String(manifestPath || ''));
4791
+ });
4792
+ electron_1.ipcMain.handle('dsh:uninstallBundle', async (_event, name) => {
4793
+ return (0, dshCompatibility_1.uninstallDshBundle)(root, String(name || ''));
4794
+ });
4795
+ electron_1.ipcMain.handle('dsh:setBundleEnabled', async (_event, name, enabled) => {
4796
+ return (0, dshCompatibility_1.setDshBundleEnabled)(root, String(name || ''), enabled === true);
4797
+ });
4787
4798
  electron_1.ipcMain.handle('mcp:upsert', async (_event, input) => {
4788
4799
  if (!mcpManager)
4789
4800
  return { ok: false, error: 'MCP manager is unavailable.' };
@@ -4855,6 +4866,21 @@ else {
4855
4866
  electron_1.ipcMain.handle('update:version', async () => {
4856
4867
  return { ok: true, version: (0, installUpdate_1.currentAppVersion)(), root };
4857
4868
  });
4869
+ electron_1.ipcMain.handle('mobile:pairingQr', async () => {
4870
+ const qr = await (0, mobilePairing_1.pairingQrDataUrl)(root);
4871
+ return {
4872
+ ok: true,
4873
+ url: qr.session.url,
4874
+ dataUrl: qr.dataUrl,
4875
+ pairingId: qr.session.pairingId,
4876
+ expiresAt: qr.session.expiresAt,
4877
+ tokenFile: (0, mobilePairing_1.pairingTokenPath)(root),
4878
+ tailscaleIpv4: (0, mobilePairing_1.tailscaleIpv4)(),
4879
+ };
4880
+ });
4881
+ electron_1.ipcMain.handle('mobile:pairingStatus', async () => {
4882
+ return { ok: true, status: (0, mobilePairing_1.pairingStatus)(root) };
4883
+ });
4858
4884
  electron_1.ipcMain.handle('update:checkGithub', async (_event, input = {}) => {
4859
4885
  return (0, installUpdate_1.checkGitHubUpdate)(String(input.repo || ''), String(input.tag || ''), String(input.asset || ''));
4860
4886
  });
@@ -4884,6 +4910,29 @@ else {
4884
4910
  setTimeout(() => electron_1.app.quit(), 150);
4885
4911
  return result;
4886
4912
  });
4913
+ electron_1.ipcMain.handle('update:planMsi', async (_event, input = {}) => {
4914
+ return (0, installUpdate_1.planManagedMsiInstall)(String(input.msiPath || ''), {
4915
+ stopConfirmed: input.stopConfirmed === true,
4916
+ removeLegacyConfirmed: input.removeLegacyConfirmed === true,
4917
+ uninstallPrevious: input.uninstallPrevious !== false,
4918
+ allowElevate: input.allowElevate !== false,
4919
+ excludeRoots: Array.isArray(input.excludeRoots) ? input.excludeRoots.map(String) : undefined,
4920
+ logDir: typeof input.logDir === 'string' ? input.logDir : undefined,
4921
+ });
4922
+ });
4923
+ electron_1.ipcMain.handle('update:executeMsi', async (_event, input = {}) => {
4924
+ const result = (0, installUpdate_1.executeManagedMsiInstall)(String(input.msiPath || ''), {
4925
+ stopConfirmed: input.stopConfirmed === true,
4926
+ removeLegacyConfirmed: input.removeLegacyConfirmed === true,
4927
+ uninstallPrevious: input.uninstallPrevious !== false,
4928
+ allowElevate: input.allowElevate !== false,
4929
+ excludeRoots: Array.isArray(input.excludeRoots) ? input.excludeRoots.map(String) : undefined,
4930
+ logDir: typeof input.logDir === 'string' ? input.logDir : undefined,
4931
+ });
4932
+ if (result.ok)
4933
+ setTimeout(() => electron_1.app.quit(), 150);
4934
+ return result;
4935
+ });
4887
4936
  electron_1.ipcMain.handle('github:gh', async (_event, argv = []) => {
4888
4937
  const safeArgs = Array.isArray(argv) ? argv.map(String).filter(a => a.length < 400) : [];
4889
4938
  try {
package/dist/preload.js CHANGED
@@ -124,6 +124,9 @@ contextBridge.exposeInMainWorld('api', {
124
124
  refreshSkills: () => ipcRenderer.invoke('skills:refresh'),
125
125
  listMcpServers: () => ipcRenderer.invoke('mcp:list'),
126
126
  discoverDshCompatibility: () => ipcRenderer.invoke('dsh:discover'),
127
+ installDshBundle: (manifestPath) => ipcRenderer.invoke('dsh:installBundle', manifestPath),
128
+ uninstallDshBundle: (name) => ipcRenderer.invoke('dsh:uninstallBundle', name),
129
+ setDshBundleEnabled: (name, enabled) => ipcRenderer.invoke('dsh:setBundleEnabled', name, enabled),
127
130
  upsertMcpServer: (input) => ipcRenderer.invoke('mcp:upsert', input),
128
131
  setMcpServerEnabled: (id, enabled) => ipcRenderer.invoke('mcp:setEnabled', id, enabled),
129
132
  removeMcpServer: (id) => ipcRenderer.invoke('mcp:remove', id),
@@ -134,6 +137,8 @@ contextBridge.exposeInMainWorld('api', {
134
137
  memoryLabUpdate: (input) => ipcRenderer.invoke('memoryLab:update', input),
135
138
  memoryLabReindex: () => ipcRenderer.invoke('memoryLab:reindex'),
136
139
  updateVersion: () => ipcRenderer.invoke('update:version'),
140
+ mobilePairingQr: () => ipcRenderer.invoke('mobile:pairingQr'),
141
+ mobilePairingStatus: () => ipcRenderer.invoke('mobile:pairingStatus'),
137
142
  updateCheckGithub: (input) => ipcRenderer.invoke('update:checkGithub', input),
138
143
  updateApplyGithub: (input) => ipcRenderer.invoke('update:applyGithub', input),
139
144
  updateInstallLocal: (input) => ipcRenderer.invoke('update:installLocal', input),
package/dist/server.js CHANGED
@@ -37,6 +37,7 @@ exports.runServer = runServer;
37
37
  const http = __importStar(require("http"));
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
+ const os = __importStar(require("os"));
40
41
  const uiPreferences_1 = require("./core/uiPreferences");
41
42
  const child_process_1 = require("child_process");
42
43
  const agent_1 = require("./core/agent");
@@ -45,10 +46,76 @@ const config_1 = require("./core/config");
45
46
  const flow_1 = require("./core/flow");
46
47
  const workspaceFileRouter_1 = require("./core/workspaceFileRouter");
47
48
  const nativeBash_1 = require("./core/nativeBash");
49
+ const installUpdate_1 = require("./core/installUpdate");
50
+ const mobilePairing_1 = require("./core/mobilePairing");
48
51
  const PORT = 47890;
49
52
  let agent = null;
50
53
  let automation = null;
51
54
  let workspaceFileRouter = null;
55
+ let mobileToken = '';
56
+ let appRoot = '';
57
+ function mobileAuthorized(req) {
58
+ if (!mobileToken)
59
+ return false;
60
+ const bearer = String(req.headers.authorization || '');
61
+ if (bearer.startsWith('Bearer '))
62
+ return bearer.slice('Bearer '.length).trim() === mobileToken;
63
+ try {
64
+ const url = new URL(req.url || '/', `http://localhost:${PORT}`);
65
+ const queryToken = url.searchParams.get('token') || '';
66
+ return queryToken === mobileToken;
67
+ }
68
+ catch {
69
+ return false;
70
+ }
71
+ }
72
+ function mobileJson(res, data, code = 200) {
73
+ const body = JSON.stringify(data);
74
+ res.writeHead(code, {
75
+ 'Content-Type': 'application/json; charset=utf-8',
76
+ 'Access-Control-Allow-Origin': '*',
77
+ });
78
+ res.end(body);
79
+ }
80
+ function handleMobileEvents(req, res) {
81
+ if (agent && !agent.config.getBool('remote', 'touch_enabled')) {
82
+ mobileJson(res, { error: 'Remote touch disabled' }, 403);
83
+ return;
84
+ }
85
+ if (!mobileAuthorized(req)) {
86
+ mobileJson(res, { error: 'Unauthorized' }, 401);
87
+ return;
88
+ }
89
+ if (!agent) {
90
+ mobileJson(res, { error: 'Agent not initialized' }, 500);
91
+ return;
92
+ }
93
+ res.writeHead(200, {
94
+ 'Content-Type': 'text/event-stream; charset=utf-8',
95
+ 'Cache-Control': 'no-cache',
96
+ 'Connection': 'keep-alive',
97
+ 'Access-Control-Allow-Origin': '*',
98
+ });
99
+ res.write('retry: 3000\n\n');
100
+ const unsubscribe = agent.subscribeWorkEvents(event => {
101
+ try {
102
+ res.write(`event: work\ndata: ${JSON.stringify(event)}\n\n`);
103
+ }
104
+ catch {
105
+ // socket is gone; the close handler will clean up
106
+ }
107
+ });
108
+ const heartbeat = setInterval(() => {
109
+ try {
110
+ res.write(': ping\n\n');
111
+ }
112
+ catch { }
113
+ }, 15000);
114
+ req.on('close', () => {
115
+ clearInterval(heartbeat);
116
+ unsubscribe();
117
+ });
118
+ }
52
119
  function resolveAppPath(root, targetPath) {
53
120
  if (!targetPath)
54
121
  return root;
@@ -214,6 +281,16 @@ function jsonResponse(res, data, code = 200) {
214
281
  async function handleApi(req, res, body) {
215
282
  const url = new URL(req.url || '/', `http://localhost:${PORT}`);
216
283
  const pathname = url.pathname;
284
+ if (pathname.startsWith('/api/mobile/')) {
285
+ if (agent && !agent.config.getBool('remote', 'touch_enabled')) {
286
+ mobileJson(res, { error: 'Remote touch disabled' }, 403);
287
+ return;
288
+ }
289
+ if (!mobileAuthorized(req)) {
290
+ mobileJson(res, { error: 'Unauthorized' }, 401);
291
+ return;
292
+ }
293
+ }
217
294
  if (!agent) {
218
295
  jsonResponse(res, { error: 'Agent not initialized' }, 500);
219
296
  return;
@@ -603,15 +680,114 @@ async function handleApi(req, res, body) {
603
680
  jsonResponse(res, { content: agent.readArchive(aName2) });
604
681
  return;
605
682
  }
683
+ case '/api/mobile/pair-confirm': {
684
+ let pairingId = url.searchParams.get('pairingId') || '';
685
+ if (!pairingId) {
686
+ try {
687
+ pairingId = String(JSON.parse(body || '{}').pairingId || '');
688
+ }
689
+ catch { }
690
+ }
691
+ const result = (0, mobilePairing_1.confirmPairing)(appRoot, pairingId, mobileToken);
692
+ mobileJson(res, result.ok
693
+ ? { ok: true, status: result.status }
694
+ : { ok: false, error: result.error, status: result.status }, result.ok ? 200 : 401);
695
+ return;
696
+ }
697
+ case '/api/mobile/pair-status': {
698
+ mobileJson(res, { ok: true, status: (0, mobilePairing_1.pairingStatus)(appRoot) });
699
+ return;
700
+ }
701
+ case '/api/mobile/hello': {
702
+ mobileJson(res, {
703
+ ok: true,
704
+ version: (0, installUpdate_1.currentAppVersion)(),
705
+ hostname: os.hostname(),
706
+ platform: process.platform,
707
+ tailscaleIpv4: (0, mobilePairing_1.tailscaleIpv4)(),
708
+ workspace: agent.workspace.current ? {
709
+ id: agent.workspace.current.id,
710
+ name: agent.workspace.current.name,
711
+ path: agent.workspace.current.path,
712
+ } : null,
713
+ conversationCount: agent.listConversationStates().length,
714
+ activeConversationId: agent.activeConversationId,
715
+ });
716
+ return;
717
+ }
718
+ case '/api/mobile/state': {
719
+ const active = agent.getConversationSnapshot(agent.activeConversationId, { window: 200 });
720
+ mobileJson(res, {
721
+ mode: agent.mode,
722
+ model: agent.modelSelectionValue(),
723
+ status: agent.status,
724
+ activeConversationId: agent.activeConversationId,
725
+ conversations: agent.listConversationStates(),
726
+ workspaces: { internal: agent.workspace.internal, external: agent.workspace.external, current: agent.workspace.current },
727
+ pendingOptions: agent.pendingOptions,
728
+ contextWindow: agent.contextWindow(),
729
+ chatMessages: active.chatMessages,
730
+ totalMessages: active.totalMessages,
731
+ conversationLocked: agent.isConversationLocked(),
732
+ });
733
+ return;
734
+ }
735
+ case '/api/mobile/conversations': {
736
+ mobileJson(res, agent.listConversationStates());
737
+ return;
738
+ }
739
+ case '/api/mobile/conversation': {
740
+ const conversationId = url.searchParams.get('conversationId') || agent.activeConversationId;
741
+ const windowParam = url.searchParams.get('window');
742
+ const beforeParam = url.searchParams.get('before');
743
+ const windowSize = windowParam ? Math.max(1, Math.min(500, Number(windowParam) || 200)) : 200;
744
+ const before = beforeParam ? Math.max(0, Number(beforeParam) || 0) : undefined;
745
+ const snapshot = agent.getConversationSnapshot(String(conversationId), { window: windowSize, before });
746
+ mobileJson(res, snapshot);
747
+ return;
748
+ }
749
+ case '/api/mobile/workspaces': {
750
+ mobileJson(res, { internal: agent.workspace.internal, external: agent.workspace.external, current: agent.workspace.current });
751
+ return;
752
+ }
753
+ case '/api/mobile/send': {
754
+ const params = JSON.parse(body || '{}');
755
+ const message = String(params.message || '');
756
+ if (!message) {
757
+ mobileJson(res, { error: 'No message' }, 400);
758
+ return;
759
+ }
760
+ if (params.conversationId)
761
+ agent.setConversation(String(params.conversationId));
762
+ const tokens = await agent.process(message);
763
+ const snapshot = agent.getConversationSnapshot(agent.activeConversationId, { window: 200 });
764
+ mobileJson(res, {
765
+ ok: true,
766
+ conversationId: agent.activeConversationId,
767
+ response: tokens.map(token => token.text).join(''),
768
+ tokens: tokens.map(token => ({ type: token.type, text: token.text })),
769
+ options: agent.pendingOptions,
770
+ status: agent.status,
771
+ conversations: agent.listConversationStates(),
772
+ chatMessages: snapshot.chatMessages,
773
+ totalMessages: snapshot.totalMessages,
774
+ });
775
+ return;
776
+ }
606
777
  default:
607
778
  jsonResponse(res, { error: 'Unknown API' }, 404);
608
779
  }
609
780
  }
610
781
  catch (e) {
611
- jsonResponse(res, { error: e.message }, 500);
782
+ if (pathname.startsWith('/api/mobile/'))
783
+ mobileJson(res, { error: e.message }, 500);
784
+ else
785
+ jsonResponse(res, { error: e.message }, 500);
612
786
  }
613
787
  }
614
788
  function startServer(root) {
789
+ mobileToken = (0, mobilePairing_1.ensureMobileToken)(root);
790
+ appRoot = root;
615
791
  agent = new agent_1.Agent(root);
616
792
  workspaceFileRouter = new workspaceFileRouter_1.WorkspaceFileRouter(() => path.resolve(agent?.workspace.current?.path || root));
617
793
  automation = new automation_1.AutomationManager(agent.config, async (prompt, model, item) => {
@@ -645,11 +821,19 @@ function startServer(root) {
645
821
  const uiDir = path.join(__dirname, 'ui');
646
822
  const server = http.createServer(async (req, res) => {
647
823
  if (req.method === 'OPTIONS') {
648
- res.writeHead(204);
824
+ res.writeHead(204, {
825
+ 'Access-Control-Allow-Origin': '*',
826
+ 'Access-Control-Allow-Headers': 'Authorization, Content-Type',
827
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
828
+ });
649
829
  res.end();
650
830
  return;
651
831
  }
652
832
  const url = new URL(req.url || '/', `http://localhost:${PORT}`);
833
+ if (url.pathname === '/api/mobile/events') {
834
+ handleMobileEvents(req, res);
835
+ return;
836
+ }
653
837
  if (req.method === 'POST' && url.pathname.startsWith('/api/')) {
654
838
  let body = '';
655
839
  req.on('data', chunk => body += chunk);
@@ -667,9 +851,17 @@ function startServer(root) {
667
851
  const fullPath = path.join(uiDir, filePath);
668
852
  serveFile(res, fullPath);
669
853
  });
670
- server.listen(PORT, '127.0.0.1', () => {
854
+ const bindHost = process.env.NEWMARK_BIND_HOST || '0.0.0.0';
855
+ const tailscale = (0, mobilePairing_1.tailscaleIpv4)();
856
+ const tokenPath = path.join(root, '.newmark-mobile-token');
857
+ server.listen(PORT, bindHost, () => {
671
858
  console.log(`\n Newmark Agent v1.0 - Server Mode`);
859
+ console.log(` Bind: ${bindHost}:${PORT}`);
672
860
  console.log(` GUI: http://localhost:${PORT}`);
861
+ console.log(` Tailscale IPv4: ${tailscale || 'not detected (install/start Tailscale)'}`);
862
+ console.log(` Mobile endpoint: http://${tailscale || '<tailscale-ip>'}:${PORT}/api/mobile/hello?token=<token>`);
863
+ console.log(` Mobile token file: ${tokenPath}`);
864
+ console.log(` Mobile events (SSE): http://${tailscale || '<tailscale-ip>'}:${PORT}/api/mobile/events?token=<token>`);
673
865
  console.log(` Press Ctrl+C to stop\n`);
674
866
  });
675
867
  }
@@ -91,13 +91,27 @@ export declare class ToolExecutor {
91
91
  private parseGitHubRepo;
92
92
  private githubFileAudit;
93
93
  private repoSecurityAudit;
94
+ private collectRepositoryFiles;
94
95
  private scanRepositorySecrets;
96
+ /**
97
+ * 扫描隐私地址类高危信息:带凭据的 URL(user:pass@)、私网 IP、本地用户目录
98
+ * 绝对路径(C:\Users\<user>、/home/<user>、/Users/<user>)。这些内容泄露个人
99
+ * 账号、内网拓扑或本地机器结构,进入公开 remote 即构成隐私暴露,需与密钥同级
100
+ * 硬性阻挡并在 Agent 二轮审查确认后放行。
101
+ */
102
+ private scanRepositoryPrivacyLeaks;
95
103
  private releaseExcludedPathFindings;
96
104
  private ghJson;
97
105
  private gbranch;
98
106
  private ghFork;
99
107
  private ghPrCreate;
100
108
  private withRemoteSecurityPreamble;
109
+ /**
110
+ * 硬性阻挡远程写:当密钥或隐私地址类高危信息存在且 Agent 尚未二轮审查确认时,
111
+ * 返回明确的阻挡结果(脱敏 findings),要求处理/确认后再以
112
+ * security_review_confirmed=true 重试放行。
113
+ */
114
+ private formatRemoteSecurityBlock;
101
115
  private gstat;
102
116
  private gpull;
103
117
  private gpush;