@@ -203,13 +203,7 @@ function getEmulatorConfig() {
203203 if ( useDevServer ) {
204204 // Dev mode: run the Slack plugin's server directly
205205 // The server/index.ts has `if (import.meta.main)` to run when executed directly
206- const slackPackageDir = path . join (
207- appRoot ,
208- '..' ,
209- '..' ,
210- 'packages' ,
211- 'slack'
212- )
206+ const slackPackageDir = path . join ( appRoot , '..' , '..' , 'packages' , 'slack' )
213207 return {
214208 type : 'bun' ,
215209 bunPath : 'bun' ,
@@ -256,15 +250,98 @@ function readBotsManifest() {
256250 }
257251}
258252
253+ /**
254+ * Read bots.json and resolve dev-mode configs (run from source via bun).
255+ * Mirrors the resolution logic in compile-bots.ts.
256+ */
257+ function readBotsJsonDev ( ) {
258+ const botsJsonPath = path . join ( appRoot , 'bots.json' )
259+ if ( ! fs . existsSync ( botsJsonPath ) ) {
260+ electronLogger . info ( 'No bots.json found for dev mode' )
261+ return [ ]
262+ }
263+
264+ let config
265+ try {
266+ config = JSON . parse ( fs . readFileSync ( botsJsonPath , 'utf-8' ) )
267+ } catch ( err ) {
268+ electronLogger . error ( { err } , 'Failed to parse bots.json' )
269+ return [ ]
270+ }
271+
272+ const entries = config . bots ?? [ ]
273+ const results = [ ]
274+
275+ for ( const entry of entries ) {
276+ const source = typeof entry === 'string' ? entry : entry . source
277+ if ( ! source ) {
278+ electronLogger . error ( { entry } , 'Bot entry missing source path' )
279+ continue
280+ }
281+ const sourcePath = path . resolve ( appRoot , source )
282+ const entryFile =
283+ ( typeof entry === 'object' ? entry . entry : undefined ) ?? 'src/app.ts'
284+
285+ // Determine bot name: explicit override or from config.yaml simulator.id
286+ let name
287+ if ( typeof entry === 'object' && entry . name ) {
288+ name = entry . name
289+ } else {
290+ const configYamlPath = path . join ( sourcePath , 'config.yaml' )
291+ if ( fs . existsSync ( configYamlPath ) ) {
292+ const yamlContent = fs . readFileSync ( configYamlPath , 'utf-8' )
293+ const match = yamlContent . match ( / ^ s i m u l a t o r : \s * \n \s + i d : \s * ( .+ ) / m)
294+ if ( match ) {
295+ name = match [ 1 ] . trim ( )
296+ }
297+ }
298+ }
299+
300+ if ( ! name ) {
301+ electronLogger . error (
302+ { source } ,
303+ 'Cannot determine bot name (no name override and no simulator.id in config.yaml)'
304+ )
305+ continue
306+ }
307+
308+ const script = path . join ( sourcePath , entryFile )
309+ if ( ! fs . existsSync ( script ) ) {
310+ electronLogger . error ( { script } , 'Bot entry point not found' )
311+ continue
312+ }
313+
314+ results . push ( {
315+ type : 'bun' ,
316+ bunPath : 'bun' ,
317+ script,
318+ cwd : sourcePath ,
319+ name,
320+ } )
321+ }
322+
323+ return results
324+ }
325+
259326/**
260327 * Get bot configurations for starting
261- * Reads from manifest .json generated by compile-bots.ts
262- * Returns configs for compiled binaries
328+ * In dev mode: reads bots .json and runs from source via bun
329+ * In production: reads manifest.json for compiled binaries
263330 */
264331function getBotConfigs ( ) {
265- const botsManifest = readBotsManifest ( )
266332 const isPackaged = app . isPackaged
267333
334+ if ( ! isPackaged ) {
335+ const devConfigs = readBotsJsonDev ( )
336+ electronLogger . info (
337+ { count : devConfigs . length , isPackaged } ,
338+ 'getBotConfigs called (dev mode)'
339+ )
340+ return devConfigs
341+ }
342+
343+ const botsManifest = readBotsManifest ( )
344+
268345 electronLogger . info ( { botsManifest, isPackaged } , 'getBotConfigs called' )
269346
270347 if ( botsManifest . length === 0 ) {
@@ -273,9 +350,7 @@ function getBotConfigs() {
273350 }
274351
275352 // Use compiled binaries from manifest
276- const botsDir = isPackaged
277- ? path . join ( process . resourcesPath , 'bots' )
278- : path . join ( appRoot , 'dist' , 'bots' )
353+ const botsDir = path . join ( process . resourcesPath , 'bots' )
279354
280355 return botsManifest
281356 . map ( ( bot ) => {
@@ -355,10 +430,19 @@ function settingsToEnv(settings) {
355430
356431 // Track all vars injected by the emulator (from UI settings, not user's .env file)
357432 // This allows bots to distinguish between UI-derived env vars and actual .env overrides
358- const injectedVars = [ ]
433+ const injectedVars = [
434+ 'SLACK_BOT_TOKEN' ,
435+ 'SLACK_APP_TOKEN' ,
436+ 'SLACK_SIGNING_SECRET' ,
437+ ]
359438
360439 const env = {
361440 SLACK_API_URL : `http://localhost:${ EMULATOR_PORT } /api` ,
441+ // Provide placeholder Slack tokens so bots pass env validation.
442+ // Real tokens aren't needed — bots always talk to the emulator.
443+ SLACK_BOT_TOKEN : 'xoxb-emulator' ,
444+ SLACK_APP_TOKEN : 'xapp-emulator' ,
445+ SLACK_SIGNING_SECRET : 'emulator' ,
362446 DATA_DIR : dataDir ,
363447 }
364448
@@ -543,6 +627,7 @@ function spawnProcess(config, env, label, forwardLogs = false) {
543627 proc = spawn ( config . path , [ ] , {
544628 env : { ...childEnv , ...env } ,
545629 stdio : [ 'ignore' , 'pipe' , 'pipe' ] ,
630+ cwd : app . getPath ( 'userData' ) ,
546631 } )
547632 }
548633
0 commit comments