!7 concept Config { new L bots; /* contains program IDs */ } static L> bots = synchroList(); // pair(program ID, main class) static JList lCommands, lBots; p-subst { db(); loadBots(); if (isMainProgram()) { swing { addToWindowSplitRight_aggressive(consoleFrame(), jvsplit( withMargin(withCenteredTitle("Bots", lBots = jlist())), withMargin(withCenteredTitle("Commands", lCommands = jlist())))); } awtCalcEvery(lBots, 1000, f updateBotsList); updateCommandList(); focusConsole(); botSleep(); } } svoid loadBots { cleanUpAndClear(bots); for (S bot : config().bots) bots.add(pair(bot, runSubBot(bot))); } answer { lock dbLock(); if "add bot *" { setAddOrMoveToFront(config().bots, formatSnippetID($1)); config().change(); loadBots(); ret "OK"; } if "remove bot *" { config().bots.remove(formatSnippetID($1)); config().change(); loadBots(); ret "OK"; } if "list bots" ret sfu(config().bots); try answer scanMultipleBots(map_precise(f second, bots), s); } svoid updateCommandList { fillListWithStrings(lCommands, knownCommands()); } svoid updateBotsList { fillListWithStrings(lBots, map_precise(f first, bots)); } static L knownCommands() { ret findSupportedQuestionsInFunctions('answer); // TODO } static Config config() { ret uniq_sync(Config); }