mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(apps): run an app's own agents when its data/config.json changes
Apps store user settings in data/config.json (e.g. pr-dashboard's tracked repo) and rely on their bundled agents to turn config into data. Without this, changing a setting meant staring at an empty app until the next cron tick. The apps-server watcher now debounces config.json writes and fires a one-shot manual run of the app's owned tasks (app--<slug>--*). Generic for any app; dynamic import keeps the server decoupled from the bg-task runner at load time.
This commit is contained in:
parent
7d38dc84a2
commit
5df26e6fca
1 changed files with 37 additions and 0 deletions
|
|
@ -683,6 +683,38 @@ function slugFromAbsolutePath(absolutePath: string): { slug: string; rel: string
|
|||
return { slug, rel: segments.slice(1).join('/') };
|
||||
}
|
||||
|
||||
// Config-change → one-shot agent run. An app writes data/config.json when the
|
||||
// user changes its settings (e.g. picks the repo to track); its bundled agents
|
||||
// are what turn that config into fresh data. Without this kick the user would
|
||||
// stare at an empty app until the next cron tick. Generic: any app, any agent
|
||||
// the app owns (app--<slug>--*). Dynamic import keeps apps/server decoupled
|
||||
// from the bg-task runner at module-load time.
|
||||
const agentKickTimers = new Map<string, NodeJS.Timeout>();
|
||||
function scheduleAgentKick(slug: string): void {
|
||||
const existing = agentKickTimers.get(slug);
|
||||
if (existing) clearTimeout(existing);
|
||||
agentKickTimers.set(slug, setTimeout(() => {
|
||||
agentKickTimers.delete(slug);
|
||||
void (async () => {
|
||||
try {
|
||||
const tasksDir = path.join(path.dirname(APPS_DIR), 'bg-tasks');
|
||||
const entries = await fsp.readdir(tasksDir).catch(() => [] as string[]);
|
||||
const owned = entries.filter((e) => e.startsWith(`app--${slug}--`));
|
||||
if (!owned.length) return;
|
||||
const { runBackgroundTask } = await import('../background-tasks/runner.js');
|
||||
for (const taskSlug of owned) {
|
||||
console.log(`[Apps] ${slug}/data/config.json changed — running ${taskSlug}`);
|
||||
void runBackgroundTask(taskSlug, 'manual').catch((e: unknown) => {
|
||||
console.warn(`[Apps] config-change agent run failed for ${taskSlug}:`, e);
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[Apps] config-change agent kick failed for ${slug}:`, e);
|
||||
}
|
||||
})();
|
||||
}, 400));
|
||||
}
|
||||
|
||||
async function startWatcher(): Promise<void> {
|
||||
if (watcher) return;
|
||||
const w = chokidar.watch(APPS_DIR, {
|
||||
|
|
@ -695,6 +727,9 @@ async function startWatcher(): Promise<void> {
|
|||
if (!hit || hit.rel.endsWith('.tmp') || /\.tmp-[0-9a-f]+$/.test(hit.rel)) return;
|
||||
const area: 'dist' | 'data' = hit.rel === 'data' || hit.rel.startsWith('data/') ? 'data' : 'dist';
|
||||
scheduleChangeBroadcast(hit.slug, area, hit.rel);
|
||||
if (hit.rel === 'data/config.json' && (eventName === 'add' || eventName === 'change')) {
|
||||
scheduleAgentKick(hit.slug);
|
||||
}
|
||||
});
|
||||
w.on('error', (error: unknown) => {
|
||||
console.error('[Apps] watcher error:', error);
|
||||
|
|
@ -795,6 +830,8 @@ export async function shutdown(): Promise<void> {
|
|||
|
||||
for (const timer of reloadTimers.values()) clearTimeout(timer);
|
||||
reloadTimers.clear();
|
||||
for (const timer of agentKickTimers.values()) clearTimeout(timer);
|
||||
agentKickTimers.clear();
|
||||
|
||||
for (const clients of eventClients.values()) {
|
||||
for (const res of clients) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue