@agenticmail/enterprise 0.5.82 → 0.5.84
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/chunk-4DXQQPEC.js +15441 -0
- package/dist/chunk-56ZDS6X4.js +15035 -0
- package/dist/chunk-GWRYZJNJ.js +2191 -0
- package/dist/chunk-KRXDNJUR.js +898 -0
- package/dist/chunk-SEGTMIPI.js +2191 -0
- package/dist/chunk-ZTOVB5OQ.js +898 -0
- package/dist/cli.js +1 -1
- package/dist/dashboard/pages/agent-detail.js +185 -51
- package/dist/index.js +3 -3
- package/dist/routes-4NHH2DJW.js +6849 -0
- package/dist/routes-WJXU7KGH.js +6841 -0
- package/dist/runtime-DAVEFGHR.js +47 -0
- package/dist/runtime-GBRQXSB6.js +47 -0
- package/dist/server-F6GBGLJY.js +12 -0
- package/dist/server-XGQSE75U.js +12 -0
- package/dist/setup-CZJDHYTE.js +20 -0
- package/dist/setup-NKJBKCEG.js +20 -0
- package/package.json +1 -1
- package/src/agent-tools/index.ts +1 -1
- package/src/agent-tools/tools/google/index.ts +4 -1
- package/src/agent-tools/tools/google/meetings.ts +468 -0
- package/src/dashboard/pages/agent-detail.js +185 -51
- package/src/engine/agent-routes.ts +105 -0
|
@@ -3988,6 +3988,190 @@ function ToolsSection(props) {
|
|
|
3988
3988
|
// BROWSER CONFIG CARD — Configurable browser settings per agent
|
|
3989
3989
|
// ════════════════════════════════════════════════════════════
|
|
3990
3990
|
|
|
3991
|
+
// ════════════════════════════════════════════════════════════
|
|
3992
|
+
// MEETING CAPABILITIES — Simple toggle, everything auto-managed
|
|
3993
|
+
// ════════════════════════════════════════════════════════════
|
|
3994
|
+
|
|
3995
|
+
function MeetingCapabilitiesSection(props) {
|
|
3996
|
+
var agentId = props.agentId;
|
|
3997
|
+
var cfg = props.cfg;
|
|
3998
|
+
var update = props.update;
|
|
3999
|
+
var sectionStyle = props.sectionStyle;
|
|
4000
|
+
var sectionTitle = props.sectionTitle;
|
|
4001
|
+
var labelStyle = props.labelStyle;
|
|
4002
|
+
var helpStyle = props.helpStyle;
|
|
4003
|
+
var _d = useApp(); var toast = _d.toast;
|
|
4004
|
+
var _launching = useState(false); var launching = _launching[0]; var setLaunching = _launching[1];
|
|
4005
|
+
var _browserStatus = useState(null); var browserStatus = _browserStatus[0]; var setBrowserStatus = _browserStatus[1];
|
|
4006
|
+
|
|
4007
|
+
function checkMeetingBrowser() {
|
|
4008
|
+
engineCall('/bridge/agents/' + agentId + '/browser-config/test', { method: 'POST' })
|
|
4009
|
+
.then(function(d) { setBrowserStatus(d); })
|
|
4010
|
+
.catch(function() { setBrowserStatus(null); });
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
useEffect(function() {
|
|
4014
|
+
if (cfg.meetingsEnabled) checkMeetingBrowser();
|
|
4015
|
+
}, [cfg.meetingsEnabled]);
|
|
4016
|
+
|
|
4017
|
+
function launchMeetingBrowser() {
|
|
4018
|
+
setLaunching(true);
|
|
4019
|
+
engineCall('/bridge/agents/' + agentId + '/browser-config/launch-meeting-browser', { method: 'POST' })
|
|
4020
|
+
.then(function(d) {
|
|
4021
|
+
if (d.error) { toast(d.error, 'error'); }
|
|
4022
|
+
else { toast('Meeting browser ready', 'success'); setBrowserStatus(d); }
|
|
4023
|
+
setLaunching(false);
|
|
4024
|
+
})
|
|
4025
|
+
.catch(function(e) { toast(e.message, 'error'); setLaunching(false); });
|
|
4026
|
+
}
|
|
4027
|
+
|
|
4028
|
+
var meetingsOn = cfg.meetingsEnabled === true;
|
|
4029
|
+
|
|
4030
|
+
return h('div', { style: sectionStyle },
|
|
4031
|
+
sectionTitle('\uD83C\uDFA5', 'Meetings & Video Calls'),
|
|
4032
|
+
|
|
4033
|
+
// Main toggle
|
|
4034
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 16, marginBottom: meetingsOn ? 16 : 0 } },
|
|
4035
|
+
h('div', {
|
|
4036
|
+
onClick: function() { update('meetingsEnabled', !meetingsOn); },
|
|
4037
|
+
style: {
|
|
4038
|
+
width: 52, height: 28, borderRadius: 14, position: 'relative', cursor: 'pointer',
|
|
4039
|
+
background: meetingsOn ? 'var(--accent)' : 'var(--border)', transition: 'background 0.2s', flexShrink: 0,
|
|
4040
|
+
},
|
|
4041
|
+
},
|
|
4042
|
+
h('div', { style: {
|
|
4043
|
+
width: 24, height: 24, borderRadius: 12, background: '#fff', position: 'absolute', top: 2,
|
|
4044
|
+
left: meetingsOn ? 26 : 2, transition: 'left 0.2s',
|
|
4045
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
|
|
4046
|
+
} })
|
|
4047
|
+
),
|
|
4048
|
+
h('div', null,
|
|
4049
|
+
h('div', { style: { fontWeight: 600, fontSize: 13 } }, meetingsOn ? 'Meeting participation enabled' : 'Meeting participation disabled'),
|
|
4050
|
+
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginTop: 2 } },
|
|
4051
|
+
meetingsOn
|
|
4052
|
+
? 'Agent can join Google Meet, Microsoft Teams, and Zoom calls automatically'
|
|
4053
|
+
: 'Enable to let this agent join video calls and meetings on behalf of your organization'
|
|
4054
|
+
)
|
|
4055
|
+
)
|
|
4056
|
+
),
|
|
4057
|
+
|
|
4058
|
+
// When enabled, show status + options
|
|
4059
|
+
meetingsOn && h('div', null,
|
|
4060
|
+
|
|
4061
|
+
// Status card
|
|
4062
|
+
h('div', { style: { display: 'flex', gap: 12, marginBottom: 16 } },
|
|
4063
|
+
h('div', { className: 'card', style: { flex: 1, padding: '12px 16px' } },
|
|
4064
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 } },
|
|
4065
|
+
h('div', { style: {
|
|
4066
|
+
width: 8, height: 8, borderRadius: 4,
|
|
4067
|
+
background: browserStatus?.ok ? 'var(--success)' : 'var(--warning)',
|
|
4068
|
+
} }),
|
|
4069
|
+
h('span', { style: { fontSize: 13, fontWeight: 600 } }, 'Meeting Browser'),
|
|
4070
|
+
browserStatus?.ok && h('span', { className: 'badge', style: { fontSize: 10, padding: '1px 6px', background: 'var(--success-soft)', color: 'var(--success)' } }, 'Running')
|
|
4071
|
+
),
|
|
4072
|
+
browserStatus?.ok
|
|
4073
|
+
? h('div', { style: { fontSize: 12, color: 'var(--text-muted)' } }, browserStatus.browserVersion || 'Chromium ready')
|
|
4074
|
+
: h('div', null,
|
|
4075
|
+
h('div', { style: { fontSize: 12, color: 'var(--text-muted)', marginBottom: 8 } }, 'A dedicated browser instance will be launched for video calls with virtual display and audio.'),
|
|
4076
|
+
h('button', { className: 'btn btn-sm', disabled: launching, onClick: launchMeetingBrowser },
|
|
4077
|
+
launching ? 'Launching...' : '\u25B6\uFE0F Launch Meeting Browser'
|
|
4078
|
+
)
|
|
4079
|
+
)
|
|
4080
|
+
)
|
|
4081
|
+
),
|
|
4082
|
+
|
|
4083
|
+
// Supported platforms
|
|
4084
|
+
h('div', { style: { display: 'grid', gap: 8, gridTemplateColumns: '1fr 1fr 1fr', marginBottom: 16 } },
|
|
4085
|
+
[
|
|
4086
|
+
{ name: 'Google Meet', icon: '\uD83D\uDFE2', enabled: cfg.meetingGoogleMeet !== false, key: 'meetingGoogleMeet', desc: 'Join via Google Calendar integration' },
|
|
4087
|
+
{ name: 'Microsoft Teams', icon: '\uD83D\uDFE3', enabled: cfg.meetingTeams !== false, key: 'meetingTeams', desc: 'Join via meeting links' },
|
|
4088
|
+
{ name: 'Zoom', icon: '\uD83D\uDD35', enabled: cfg.meetingZoom !== false, key: 'meetingZoom', desc: 'Join via meeting links' },
|
|
4089
|
+
].map(function(p) {
|
|
4090
|
+
return h('div', { key: p.key, className: 'card', style: { padding: '10px 12px', cursor: 'pointer', border: '1px solid ' + (p.enabled ? 'var(--accent)' : 'var(--border)') },
|
|
4091
|
+
onClick: function() { update(p.key, !p.enabled); }
|
|
4092
|
+
},
|
|
4093
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 } },
|
|
4094
|
+
h('span', null, p.icon),
|
|
4095
|
+
h('span', { style: { fontWeight: 600, fontSize: 12 } }, p.name),
|
|
4096
|
+
h('span', { style: { marginLeft: 'auto', fontSize: 11, color: p.enabled ? 'var(--success)' : 'var(--text-muted)' } }, p.enabled ? 'ON' : 'OFF')
|
|
4097
|
+
),
|
|
4098
|
+
h('div', { style: { fontSize: 11, color: 'var(--text-muted)' } }, p.desc)
|
|
4099
|
+
);
|
|
4100
|
+
})
|
|
4101
|
+
),
|
|
4102
|
+
|
|
4103
|
+
// Meeting behavior
|
|
4104
|
+
h('div', { style: { display: 'grid', gap: 12, gridTemplateColumns: '1fr 1fr' } },
|
|
4105
|
+
h('div', { className: 'form-group' },
|
|
4106
|
+
h('label', { style: labelStyle }, 'Auto-Join Calendar Meetings'),
|
|
4107
|
+
h('select', { className: 'input', value: cfg.meetingAutoJoin || 'ask',
|
|
4108
|
+
onChange: function(e) { update('meetingAutoJoin', e.target.value); }
|
|
4109
|
+
},
|
|
4110
|
+
h('option', { value: 'always' }, 'Always — Join all meetings automatically'),
|
|
4111
|
+
h('option', { value: 'invited' }, 'When Invited — Only join meetings the agent is invited to'),
|
|
4112
|
+
h('option', { value: 'ask' }, 'Ask First — Request approval before joining'),
|
|
4113
|
+
h('option', { value: 'never' }, 'Manual Only — Agent only joins when explicitly told')
|
|
4114
|
+
),
|
|
4115
|
+
h('div', { style: helpStyle }, 'How the agent decides when to join meetings.')
|
|
4116
|
+
),
|
|
4117
|
+
h('div', { className: 'form-group' },
|
|
4118
|
+
h('label', { style: labelStyle }, 'Meeting Role'),
|
|
4119
|
+
h('select', { className: 'input', value: cfg.meetingRole || 'observer',
|
|
4120
|
+
onChange: function(e) { update('meetingRole', e.target.value); }
|
|
4121
|
+
},
|
|
4122
|
+
h('option', { value: 'observer' }, 'Observer — Listen and take notes only'),
|
|
4123
|
+
h('option', { value: 'participant' }, 'Participant — Can speak and interact'),
|
|
4124
|
+
h('option', { value: 'presenter' }, 'Presenter — Can share screen and present')
|
|
4125
|
+
),
|
|
4126
|
+
h('div', { style: helpStyle }, 'What the agent is allowed to do in meetings.')
|
|
4127
|
+
),
|
|
4128
|
+
h('div', { className: 'form-group' },
|
|
4129
|
+
h('label', { style: labelStyle }, 'Join Timing'),
|
|
4130
|
+
h('select', { className: 'input', value: cfg.meetingJoinTiming || 'ontime',
|
|
4131
|
+
onChange: function(e) { update('meetingJoinTiming', e.target.value); }
|
|
4132
|
+
},
|
|
4133
|
+
h('option', { value: 'early' }, 'Early — Join 2 minutes before start'),
|
|
4134
|
+
h('option', { value: 'ontime' }, 'On Time — Join at scheduled start'),
|
|
4135
|
+
h('option', { value: 'late' }, 'Fashionably Late — Join 2 minutes after start')
|
|
4136
|
+
)
|
|
4137
|
+
),
|
|
4138
|
+
h('div', { className: 'form-group' },
|
|
4139
|
+
h('label', { style: labelStyle }, 'After Meeting'),
|
|
4140
|
+
h('select', { className: 'input', value: cfg.meetingAfterAction || 'notes',
|
|
4141
|
+
onChange: function(e) { update('meetingAfterAction', e.target.value); }
|
|
4142
|
+
},
|
|
4143
|
+
h('option', { value: 'notes' }, 'Send meeting notes to organizer'),
|
|
4144
|
+
h('option', { value: 'summary' }, 'Post summary to team channel'),
|
|
4145
|
+
h('option', { value: 'transcript' }, 'Save full transcript'),
|
|
4146
|
+
h('option', { value: 'nothing' }, 'Do nothing')
|
|
4147
|
+
),
|
|
4148
|
+
h('div', { style: helpStyle }, 'What happens after the meeting ends.')
|
|
4149
|
+
)
|
|
4150
|
+
),
|
|
4151
|
+
|
|
4152
|
+
// Display name in meetings
|
|
4153
|
+
h('div', { style: { display: 'grid', gap: 12, gridTemplateColumns: '1fr 1fr', marginTop: 12 } },
|
|
4154
|
+
h('div', { className: 'form-group' },
|
|
4155
|
+
h('label', { style: labelStyle }, 'Display Name in Meetings'),
|
|
4156
|
+
h('input', { className: 'input', placeholder: 'Agent name (e.g. "Fola - AI Assistant")',
|
|
4157
|
+
value: cfg.meetingDisplayName || '',
|
|
4158
|
+
onChange: function(e) { update('meetingDisplayName', e.target.value || undefined); }
|
|
4159
|
+
}),
|
|
4160
|
+
h('div', { style: helpStyle }, 'How the agent appears to other participants.')
|
|
4161
|
+
),
|
|
4162
|
+
h('div', { className: 'form-group' },
|
|
4163
|
+
h('label', { style: labelStyle }, 'Max Meeting Duration (minutes)'),
|
|
4164
|
+
h('input', { className: 'input', type: 'number', min: 5, max: 480,
|
|
4165
|
+
value: cfg.meetingMaxDuration || 120,
|
|
4166
|
+
onChange: function(e) { update('meetingMaxDuration', parseInt(e.target.value) || 120); }
|
|
4167
|
+
}),
|
|
4168
|
+
h('div', { style: helpStyle }, 'Agent will leave after this duration to prevent runaway sessions.')
|
|
4169
|
+
)
|
|
4170
|
+
)
|
|
4171
|
+
)
|
|
4172
|
+
);
|
|
4173
|
+
}
|
|
4174
|
+
|
|
3991
4175
|
function BrowserConfigCard(props) {
|
|
3992
4176
|
var agentId = props.agentId;
|
|
3993
4177
|
var _d = useApp(); var toast = _d.toast;
|
|
@@ -4420,57 +4604,7 @@ function BrowserConfigCard(props) {
|
|
|
4420
4604
|
),
|
|
4421
4605
|
|
|
4422
4606
|
// ─── Section 4: Meeting & Video Capabilities ─────
|
|
4423
|
-
h(
|
|
4424
|
-
sectionTitle('\uD83C\uDFA5', 'Meeting & Video Call Capabilities'),
|
|
4425
|
-
h('div', { style: { padding: '10px 14px', background: 'var(--bg-secondary)', borderRadius: 'var(--radius)', marginBottom: 12, fontSize: 12, lineHeight: 1.6 } },
|
|
4426
|
-
h('div', { style: { fontWeight: 600, marginBottom: 6 } }, 'For Google Meet, Teams, and Zoom:'),
|
|
4427
|
-
h('div', null, '\u2022 Use ', h('strong', null, 'Remote Browser (CDP)'), ' provider pointed at a VM with display + virtual camera'),
|
|
4428
|
-
h('div', null, '\u2022 The remote machine needs: X11/Wayland display, PulseAudio/PipeWire (audio), v4l2loopback (virtual camera)'),
|
|
4429
|
-
h('div', null, '\u2022 Agent will navigate to meeting URL, handle permissions, and interact with the meeting UI'),
|
|
4430
|
-
h('div', null, '\u2022 For voice participation: integrate with a Speech-to-Text / Text-to-Speech service'),
|
|
4431
|
-
h('div', { style: { marginTop: 8 } },
|
|
4432
|
-
h('strong', null, 'Quick VM Setup (Linux):'),
|
|
4433
|
-
h('pre', { style: { margin: '6px 0', padding: 8, background: 'var(--bg-tertiary)', borderRadius: 4, fontSize: 11, overflow: 'auto' } },
|
|
4434
|
-
'# Install deps\nsudo apt install -y xvfb pulseaudio chromium v4l2loopback-dkms\n\n# Start virtual display + audio\nXvfb :99 -screen 0 1920x1080x24 &\nexport DISPLAY=:99\npulseaudio --start\n\n# Launch Chrome with remote debugging\nchromium --remote-debugging-port=9222 \\\n --no-first-run --disable-gpu \\\n --use-fake-ui-for-media-stream \\\n --use-fake-device-for-media-stream \\\n --auto-accept-camera-and-microphone-capture'
|
|
4435
|
-
)
|
|
4436
|
-
)
|
|
4437
|
-
),
|
|
4438
|
-
h('div', { style: { display: 'grid', gap: 12, gridTemplateColumns: '1fr 1fr' } },
|
|
4439
|
-
h('div', { className: 'form-group' },
|
|
4440
|
-
h('label', { style: labelStyle }, 'Auto-Accept Permissions'),
|
|
4441
|
-
h('select', { className: 'input', value: cfg.autoAcceptPermissions !== false ? 'true' : 'false',
|
|
4442
|
-
onChange: function(e) { update('autoAcceptPermissions', e.target.value === 'true'); }
|
|
4443
|
-
},
|
|
4444
|
-
h('option', { value: 'true' }, 'Yes — Auto-grant camera/microphone access'),
|
|
4445
|
-
h('option', { value: 'false' }, 'No — Require manual permission grants')
|
|
4446
|
-
)
|
|
4447
|
-
),
|
|
4448
|
-
h('div', { className: 'form-group' },
|
|
4449
|
-
h('label', { style: labelStyle }, 'Virtual Camera Feed'),
|
|
4450
|
-
h('input', { className: 'input', placeholder: '/dev/video0 or URL to video feed',
|
|
4451
|
-
value: cfg.virtualCameraSource || '',
|
|
4452
|
-
onChange: function(e) { update('virtualCameraSource', e.target.value || undefined); }
|
|
4453
|
-
}),
|
|
4454
|
-
h('div', { style: helpStyle }, 'Video source for the agent\'s "camera" in meetings.')
|
|
4455
|
-
),
|
|
4456
|
-
h('div', { className: 'form-group' },
|
|
4457
|
-
h('label', { style: labelStyle }, 'Audio Input'),
|
|
4458
|
-
h('input', { className: 'input', placeholder: 'PulseAudio source (e.g. virtual_mic)',
|
|
4459
|
-
value: cfg.audioInput || '',
|
|
4460
|
-
onChange: function(e) { update('audioInput', e.target.value || undefined); }
|
|
4461
|
-
}),
|
|
4462
|
-
h('div', { style: helpStyle }, 'Audio source for the agent to speak in meetings.')
|
|
4463
|
-
),
|
|
4464
|
-
h('div', { className: 'form-group' },
|
|
4465
|
-
h('label', { style: labelStyle }, 'Audio Output'),
|
|
4466
|
-
h('input', { className: 'input', placeholder: 'PulseAudio sink (e.g. virtual_speaker)',
|
|
4467
|
-
value: cfg.audioOutput || '',
|
|
4468
|
-
onChange: function(e) { update('audioOutput', e.target.value || undefined); }
|
|
4469
|
-
}),
|
|
4470
|
-
h('div', { style: helpStyle }, 'Audio sink for the agent to hear meeting participants.')
|
|
4471
|
-
)
|
|
4472
|
-
)
|
|
4473
|
-
),
|
|
4607
|
+
h(MeetingCapabilitiesSection, { agentId: agentId, cfg: cfg, update: update, labelStyle: labelStyle, helpStyle: helpStyle, sectionStyle: sectionStyle, sectionTitle: sectionTitle }),
|
|
4474
4608
|
|
|
4475
4609
|
// ─── Section 5: Persistent Sessions ──────────────
|
|
4476
4610
|
h('div', { style: { paddingTop: 12 } },
|
|
@@ -832,6 +832,11 @@ export function createAgentRoutes(opts: {
|
|
|
832
832
|
tools: ['google_contacts_list', 'google_contacts_search', 'google_contacts_search_directory',
|
|
833
833
|
'google_contacts_create', 'google_contacts_update'],
|
|
834
834
|
},
|
|
835
|
+
{
|
|
836
|
+
id: 'meetings', name: 'Meetings', description: 'Join Google Meet, Zoom, Teams calls. Scan calendar and inbox for meeting invites. RSVP, take notes, send summaries.',
|
|
837
|
+
icon: '🎥', requiresOAuth: 'google',
|
|
838
|
+
tools: ['meetings_upcoming', 'meeting_join', 'meeting_action', 'meetings_scan_inbox', 'meeting_rsvp'],
|
|
839
|
+
},
|
|
835
840
|
{
|
|
836
841
|
id: 'enterprise_database', name: 'Database', description: 'SQL queries, schema inspection, data sampling',
|
|
837
842
|
icon: '🗄️',
|
|
@@ -927,6 +932,106 @@ export function createAgentRoutes(opts: {
|
|
|
927
932
|
return c.json({ config: managed.config?.browserConfig || {} });
|
|
928
933
|
});
|
|
929
934
|
|
|
935
|
+
/**
|
|
936
|
+
* POST /bridge/agents/:id/browser-config/launch-meeting-browser
|
|
937
|
+
* Launches a meeting-ready headed Chrome instance with virtual display + audio.
|
|
938
|
+
* Returns the CDP URL for the agent to connect to.
|
|
939
|
+
*/
|
|
940
|
+
router.post('/bridge/agents/:id/browser-config/launch-meeting-browser', async (c) => {
|
|
941
|
+
const agentId = c.req.param('id');
|
|
942
|
+
const managed = lifecycle.getAgent(agentId);
|
|
943
|
+
if (!managed) return c.json({ error: 'Agent not found' }, 404);
|
|
944
|
+
|
|
945
|
+
try {
|
|
946
|
+
const { execSync, spawn } = await import('node:child_process');
|
|
947
|
+
const chromePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH || '/usr/bin/chromium';
|
|
948
|
+
|
|
949
|
+
// Check if a meeting browser is already running for this agent
|
|
950
|
+
const existingPort = (managed.config as any)?.meetingBrowserPort;
|
|
951
|
+
if (existingPort) {
|
|
952
|
+
try {
|
|
953
|
+
const resp = await fetch(`http://127.0.0.1:${existingPort}/json/version`, { signal: AbortSignal.timeout(2000) });
|
|
954
|
+
if (resp.ok) {
|
|
955
|
+
const data = await resp.json() as any;
|
|
956
|
+
return c.json({ ok: true, alreadyRunning: true, cdpUrl: data.webSocketDebuggerUrl, port: existingPort, browserVersion: data.Browser });
|
|
957
|
+
}
|
|
958
|
+
} catch { /* not running, will launch new one */ }
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Find available port
|
|
962
|
+
const net = await import('node:net');
|
|
963
|
+
const port = await new Promise<number>((resolve, reject) => {
|
|
964
|
+
const srv = net.createServer();
|
|
965
|
+
srv.listen(0, '127.0.0.1', () => {
|
|
966
|
+
const p = (srv.address() as any).port;
|
|
967
|
+
srv.close(() => resolve(p));
|
|
968
|
+
});
|
|
969
|
+
srv.on('error', reject);
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
// Launch Chrome with meeting-optimized flags
|
|
973
|
+
const chromeArgs = [
|
|
974
|
+
`--remote-debugging-port=${port}`,
|
|
975
|
+
'--remote-debugging-address=127.0.0.1',
|
|
976
|
+
'--no-first-run',
|
|
977
|
+
'--no-default-browser-check',
|
|
978
|
+
'--disable-background-networking',
|
|
979
|
+
'--disable-sync',
|
|
980
|
+
'--disable-translate',
|
|
981
|
+
'--metrics-recording-only',
|
|
982
|
+
'--no-sandbox',
|
|
983
|
+
// Meeting-specific: auto-grant camera/mic permissions
|
|
984
|
+
'--use-fake-ui-for-media-stream',
|
|
985
|
+
'--auto-accept-camera-and-microphone-capture',
|
|
986
|
+
// Use virtual audio
|
|
987
|
+
'--use-fake-device-for-media-stream',
|
|
988
|
+
// Window size for meeting UI
|
|
989
|
+
'--window-size=1920,1080',
|
|
990
|
+
'--start-maximized',
|
|
991
|
+
// User data dir for persistent logins
|
|
992
|
+
`/tmp/meeting-browser-${agentId.slice(0, 8)}`,
|
|
993
|
+
];
|
|
994
|
+
|
|
995
|
+
const child = spawn(chromePath, chromeArgs, {
|
|
996
|
+
detached: true,
|
|
997
|
+
stdio: 'ignore',
|
|
998
|
+
env: { ...process.env, DISPLAY: ':99' },
|
|
999
|
+
});
|
|
1000
|
+
child.unref();
|
|
1001
|
+
|
|
1002
|
+
// Wait for Chrome to be ready
|
|
1003
|
+
let cdpUrl = '';
|
|
1004
|
+
let browserVersion = '';
|
|
1005
|
+
for (let i = 0; i < 30; i++) {
|
|
1006
|
+
await new Promise(r => setTimeout(r, 500));
|
|
1007
|
+
try {
|
|
1008
|
+
const resp = await fetch(`http://127.0.0.1:${port}/json/version`, { signal: AbortSignal.timeout(2000) });
|
|
1009
|
+
if (resp.ok) {
|
|
1010
|
+
const data = await resp.json() as any;
|
|
1011
|
+
cdpUrl = data.webSocketDebuggerUrl;
|
|
1012
|
+
browserVersion = data.Browser;
|
|
1013
|
+
break;
|
|
1014
|
+
}
|
|
1015
|
+
} catch { /* retry */ }
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (!cdpUrl) {
|
|
1019
|
+
return c.json({ error: 'Chrome launched but CDP not responding after 15s' });
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Save port to agent config for reuse
|
|
1023
|
+
if (!managed.config) managed.config = {} as any;
|
|
1024
|
+
(managed.config as any).meetingBrowserPort = port;
|
|
1025
|
+
(managed.config as any).meetingBrowserCdpUrl = cdpUrl;
|
|
1026
|
+
managed.updatedAt = new Date().toISOString();
|
|
1027
|
+
await lifecycle.saveAgent(agentId);
|
|
1028
|
+
|
|
1029
|
+
return c.json({ ok: true, cdpUrl, port, browserVersion, pid: child.pid });
|
|
1030
|
+
} catch (e: any) {
|
|
1031
|
+
return c.json({ error: 'Failed to launch meeting browser: ' + e.message });
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
|
|
930
1035
|
router.post('/bridge/agents/:id/browser-config/test', async (c) => {
|
|
931
1036
|
const agentId = c.req.param('id');
|
|
932
1037
|
const managed = lifecycle.getAgent(agentId);
|