create-walle 0.9.0 → 0.9.3
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/README.md +35 -31
- package/package.json +3 -3
- package/template/CLAUDE.md +23 -1
- package/template/claude-task-manager/bin/restart-ctm.sh +3 -2
- package/template/claude-task-manager/db.js +38 -0
- package/template/claude-task-manager/public/css/walle.css +123 -0
- package/template/claude-task-manager/public/index.html +962 -69
- package/template/claude-task-manager/public/js/walle.js +374 -121
- package/template/claude-task-manager/public/prompts.html +84 -26
- package/template/claude-task-manager/public/walle-icon.svg +45 -0
- package/template/claude-task-manager/server.js +69 -4
- package/template/docs/openclaw-vs-walle-comparison.md +103 -0
- package/template/package.json +1 -1
- package/template/wall-e/agent.js +63 -3
- package/template/wall-e/api-walle.js +42 -0
- package/template/wall-e/brain.js +182 -5
- package/template/wall-e/channels/imessage-channel.js +4 -1
- package/template/wall-e/channels/slack-channel.js +3 -1
- package/template/wall-e/chat.js +106 -224
- package/template/wall-e/context/compactor.js +163 -0
- package/template/wall-e/context/context-builder.js +355 -0
- package/template/wall-e/context/state-snapshot.js +209 -0
- package/template/wall-e/context/token-counter.js +55 -0
- package/template/wall-e/context/topic-matcher.js +79 -0
- package/template/wall-e/core-tasks.js +24 -0
- package/template/wall-e/events/event-bus.js +23 -0
- package/template/wall-e/loops/ingest.js +4 -0
- package/template/wall-e/loops/initiative.js +316 -0
- package/template/wall-e/loops/tasks.js +55 -5
- package/template/wall-e/skills/_bundled/email-sync/run.js +3 -1
- package/template/wall-e/skills/_bundled/morning-briefing/run.js +41 -0
- package/template/wall-e/skills/_bundled/proactive-alerts/SKILL.md +20 -0
- package/template/wall-e/skills/_bundled/proactive-alerts/run.js +144 -0
- package/template/wall-e/skills/_bundled/slack-mentions/.watched-threads.json +18 -0
- package/template/wall-e/skills/_bundled/slack-mentions/.watermark.json +4 -0
- package/template/wall-e/skills/_bundled/slack-mentions/SKILL.md +52 -0
- package/template/wall-e/skills/_bundled/slack-mentions/run.js +470 -0
- package/template/wall-e/skills/_bundled/weekly-reflection/SKILL.md +69 -0
- package/template/wall-e/tests/brain.test.js +4 -4
- package/template/wall-e/tests/compactor.test.js +323 -0
- package/template/wall-e/tests/context-builder.test.js +215 -0
- package/template/wall-e/tests/event-bus.test.js +74 -0
- package/template/wall-e/tests/initiative.test.js +354 -0
- package/template/wall-e/tests/proactive-alerts.test.js +140 -0
- package/template/wall-e/tests/session-persistence.test.js +335 -0
|
@@ -622,6 +622,12 @@ function handleWalleApi(req, res, url) {
|
|
|
622
622
|
try {
|
|
623
623
|
const status = params.get('status') || undefined;
|
|
624
624
|
const limit = parseLimit(params, 50);
|
|
625
|
+
// Load active watched threads from brain DB for Slack task enrichment
|
|
626
|
+
let watchedThreads = [];
|
|
627
|
+
try {
|
|
628
|
+
if (brain.listActiveSlackThreads) watchedThreads = brain.listActiveSlackThreads(2 * 60 * 60 * 1000);
|
|
629
|
+
} catch {}
|
|
630
|
+
|
|
625
631
|
const data = brain.listTasks({ status, limit }).map(function(t) {
|
|
626
632
|
if (t.started_at && t.completed_at) {
|
|
627
633
|
// Normalize timestamps: append 'Z' if missing to ensure UTC parsing
|
|
@@ -630,6 +636,20 @@ function handleWalleApi(req, res, url) {
|
|
|
630
636
|
const dur = new Date(c).getTime() - new Date(s).getTime();
|
|
631
637
|
if (dur > 0 && dur < 86400000) t.last_duration_ms = dur; // sanity: max 24h
|
|
632
638
|
}
|
|
639
|
+
// Enrich Slack tasks with watched thread info
|
|
640
|
+
if (t.source === 'slack' && t.id) {
|
|
641
|
+
const watched = watchedThreads.find(w => w.task_id === t.id);
|
|
642
|
+
if (watched) {
|
|
643
|
+
const expiresAt = new Date(new Date(watched.last_activity).getTime() + 2 * 60 * 60 * 1000);
|
|
644
|
+
t.slack_thread = {
|
|
645
|
+
channel_id: watched.channel_id,
|
|
646
|
+
thread_ts: watched.thread_ts,
|
|
647
|
+
last_activity: watched.last_activity,
|
|
648
|
+
expires_at: expiresAt.toISOString(),
|
|
649
|
+
active: expiresAt > new Date(),
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
}
|
|
633
653
|
return t;
|
|
634
654
|
});
|
|
635
655
|
jsonResponse(res, { data });
|
|
@@ -668,6 +688,8 @@ function handleWalleApi(req, res, url) {
|
|
|
668
688
|
due_at: body.due_at,
|
|
669
689
|
skill: body.skill,
|
|
670
690
|
skill_config: body.skill_config ? (typeof body.skill_config === 'string' ? body.skill_config : JSON.stringify(body.skill_config)) : undefined,
|
|
691
|
+
source: body.source,
|
|
692
|
+
source_ref: body.source_ref,
|
|
671
693
|
});
|
|
672
694
|
jsonResponse(res, { data: { id: result.id } }, 201);
|
|
673
695
|
} catch (e) {
|
|
@@ -1224,6 +1246,26 @@ function handleWalleApi(req, res, url) {
|
|
|
1224
1246
|
return true;
|
|
1225
1247
|
}
|
|
1226
1248
|
|
|
1249
|
+
// POST /api/wall-e/webhook — receive external events
|
|
1250
|
+
if (p === '/api/wall-e/webhook' && m === 'POST') {
|
|
1251
|
+
// Verify webhook secret if configured
|
|
1252
|
+
const expectedToken = process.env.WALLE_WEBHOOK_SECRET;
|
|
1253
|
+
if (expectedToken && req.headers['authorization'] !== `Bearer ${expectedToken}`) {
|
|
1254
|
+
jsonResponse(res, { error: 'Unauthorized' }, 401);
|
|
1255
|
+
return true;
|
|
1256
|
+
}
|
|
1257
|
+
readBody(req).then(body => {
|
|
1258
|
+
if (!body.source || !body.event) {
|
|
1259
|
+
return jsonResponse(res, { error: 'source and event are required' }, 400);
|
|
1260
|
+
}
|
|
1261
|
+
const eventBus = require('./events/event-bus');
|
|
1262
|
+
const { event, source, ...payload } = body;
|
|
1263
|
+
eventBus.emitWebhook(source, { event, ...payload });
|
|
1264
|
+
jsonResponse(res, { ok: true, message: `Webhook received from ${source}` });
|
|
1265
|
+
}).catch(e => jsonResponse(res, { error: e.message }, 400));
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1227
1269
|
// No matching route under /api/wall-e
|
|
1228
1270
|
jsonResponse(res, { error: 'Not found' }, 404);
|
|
1229
1271
|
return true;
|
package/template/wall-e/brain.js
CHANGED
|
@@ -77,6 +77,15 @@ function initDb(dbPath) {
|
|
|
77
77
|
try { newDb.prepare("SELECT skill_config FROM tasks LIMIT 0").run(); } catch (_) {
|
|
78
78
|
newDb.prepare("ALTER TABLE tasks ADD COLUMN skill_config TEXT").run();
|
|
79
79
|
}
|
|
80
|
+
// Migration: add source/source_ref columns to tasks table
|
|
81
|
+
try { newDb.prepare("SELECT source FROM tasks LIMIT 0").run(); } catch (_) {
|
|
82
|
+
newDb.prepare("ALTER TABLE tasks ADD COLUMN source TEXT DEFAULT 'system'").run();
|
|
83
|
+
}
|
|
84
|
+
try { newDb.prepare("SELECT source_ref FROM tasks LIMIT 0").run(); } catch (_) {
|
|
85
|
+
newDb.prepare("ALTER TABLE tasks ADD COLUMN source_ref TEXT").run();
|
|
86
|
+
}
|
|
87
|
+
try { newDb.prepare("SELECT name FROM sqlite_master WHERE type='index' AND name='idx_tasks_source'").get() ||
|
|
88
|
+
newDb.prepare("CREATE INDEX IF NOT EXISTS idx_tasks_source ON tasks(source)").run(); } catch (_) {}
|
|
80
89
|
} catch (err) {
|
|
81
90
|
newDb.close();
|
|
82
91
|
db = null;
|
|
@@ -379,12 +388,57 @@ function createTables() {
|
|
|
379
388
|
result TEXT,
|
|
380
389
|
error TEXT,
|
|
381
390
|
checkpoint TEXT,
|
|
391
|
+
source TEXT DEFAULT 'system',
|
|
392
|
+
source_ref TEXT,
|
|
382
393
|
created_at TEXT DEFAULT (datetime('now')),
|
|
383
394
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
384
395
|
);
|
|
385
396
|
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
386
397
|
CREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks(due_at);
|
|
387
398
|
CREATE INDEX IF NOT EXISTS idx_tasks_next ON tasks(next_run_at);
|
|
399
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_source ON tasks(source);
|
|
400
|
+
|
|
401
|
+
CREATE TABLE IF NOT EXISTS initiative_log (
|
|
402
|
+
id TEXT PRIMARY KEY,
|
|
403
|
+
trigger TEXT NOT NULL,
|
|
404
|
+
state_snapshot TEXT,
|
|
405
|
+
reasoning TEXT,
|
|
406
|
+
decision TEXT NOT NULL,
|
|
407
|
+
decision_data TEXT,
|
|
408
|
+
autonomy_tier INTEGER,
|
|
409
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
410
|
+
);
|
|
411
|
+
CREATE INDEX IF NOT EXISTS idx_initiative_time ON initiative_log(created_at);
|
|
412
|
+
|
|
413
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
414
|
+
id TEXT PRIMARY KEY,
|
|
415
|
+
channel TEXT NOT NULL,
|
|
416
|
+
summary TEXT,
|
|
417
|
+
compacted_messages TEXT,
|
|
418
|
+
turn_count INTEGER DEFAULT 0,
|
|
419
|
+
token_estimate INTEGER DEFAULT 0,
|
|
420
|
+
metadata TEXT,
|
|
421
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
422
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
423
|
+
);
|
|
424
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_channel ON sessions(channel, updated_at);
|
|
425
|
+
|
|
426
|
+
CREATE TABLE IF NOT EXISTS brain_metadata (
|
|
427
|
+
key TEXT PRIMARY KEY,
|
|
428
|
+
value TEXT NOT NULL
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
CREATE TABLE IF NOT EXISTS slack_threads (
|
|
432
|
+
id TEXT PRIMARY KEY,
|
|
433
|
+
channel_id TEXT NOT NULL,
|
|
434
|
+
thread_ts TEXT NOT NULL,
|
|
435
|
+
task_id TEXT,
|
|
436
|
+
session_id TEXT,
|
|
437
|
+
last_activity TEXT NOT NULL,
|
|
438
|
+
last_seen_ts TEXT,
|
|
439
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
440
|
+
);
|
|
441
|
+
CREATE INDEX IF NOT EXISTS idx_slack_threads_active ON slack_threads(last_activity);
|
|
388
442
|
`);
|
|
389
443
|
}
|
|
390
444
|
|
|
@@ -1085,14 +1139,21 @@ function updateSkillStats(skillId, success) {
|
|
|
1085
1139
|
|
|
1086
1140
|
// ── Tasks ──
|
|
1087
1141
|
|
|
1088
|
-
function insertTask({ title, description, priority, type, execution, script, schedule, due_at, next_run_at, skill, skill_config }) {
|
|
1142
|
+
function insertTask({ title, description, priority, type, execution, script, schedule, due_at, next_run_at, skill, skill_config, source, source_ref }) {
|
|
1143
|
+
// Dedup: skip if a task with the same title+source+source_ref already exists and isn't archived
|
|
1144
|
+
if (title && source) {
|
|
1145
|
+
const existing = source_ref
|
|
1146
|
+
? getDb().prepare(`SELECT id, status FROM tasks WHERE title = ? AND source = ? AND source_ref = ? AND status != 'archived' LIMIT 1`).get(title, source, source_ref)
|
|
1147
|
+
: getDb().prepare(`SELECT id, status FROM tasks WHERE title = ? AND source = ? AND status NOT IN ('archived','completed') LIMIT 1`).get(title, source);
|
|
1148
|
+
if (existing) return { id: existing.id, deduplicated: true };
|
|
1149
|
+
}
|
|
1089
1150
|
const id = uuidv4();
|
|
1090
1151
|
getDb().prepare(`
|
|
1091
|
-
INSERT INTO tasks (id, title, description, priority, type, execution, script, schedule, due_at, next_run_at, skill, skill_config)
|
|
1092
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1152
|
+
INSERT INTO tasks (id, title, description, priority, type, execution, script, schedule, due_at, next_run_at, skill, skill_config, source, source_ref)
|
|
1153
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1093
1154
|
`).run(id, title, description || null, priority || 'normal', type || 'once',
|
|
1094
1155
|
execution || 'chat', script || null, schedule || null, due_at || null, next_run_at || due_at || null,
|
|
1095
|
-
skill || null, skill_config || null);
|
|
1156
|
+
skill || null, skill_config || null, source || 'system', source_ref || null);
|
|
1096
1157
|
return { id };
|
|
1097
1158
|
}
|
|
1098
1159
|
|
|
@@ -1110,7 +1171,7 @@ function listTasks({ status, limit } = {}) {
|
|
|
1110
1171
|
}
|
|
1111
1172
|
|
|
1112
1173
|
function updateTask(id, updates) {
|
|
1113
|
-
const allowed = ['title', 'description', 'status', 'priority', 'type', 'execution', 'script', 'schedule', 'due_at', 'next_run_at', 'started_at', 'completed_at', 'last_run_at', 'run_count', 'result', 'error', 'checkpoint', 'skill', 'skill_config'];
|
|
1174
|
+
const allowed = ['title', 'description', 'status', 'priority', 'type', 'execution', 'script', 'schedule', 'due_at', 'next_run_at', 'started_at', 'completed_at', 'last_run_at', 'run_count', 'result', 'error', 'checkpoint', 'skill', 'skill_config', 'source', 'source_ref'];
|
|
1114
1175
|
const sets = [];
|
|
1115
1176
|
const params = [];
|
|
1116
1177
|
for (const [k, v] of Object.entries(updates)) {
|
|
@@ -1190,6 +1251,108 @@ function listOpenBriefingItems(skill) {
|
|
|
1190
1251
|
).all(skill);
|
|
1191
1252
|
}
|
|
1192
1253
|
|
|
1254
|
+
// -- Sessions (context compaction) --
|
|
1255
|
+
|
|
1256
|
+
function getSession(id) {
|
|
1257
|
+
return getDb().prepare('SELECT * FROM sessions WHERE id = ?').get(id) || null;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function upsertSession({ id, channel, summary, compacted_messages, turn_count, token_estimate, metadata }) {
|
|
1261
|
+
const now = new Date().toISOString();
|
|
1262
|
+
getDb().prepare(`
|
|
1263
|
+
INSERT INTO sessions (id, channel, summary, compacted_messages, turn_count, token_estimate, metadata, created_at, updated_at)
|
|
1264
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1265
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1266
|
+
summary = excluded.summary,
|
|
1267
|
+
compacted_messages = excluded.compacted_messages,
|
|
1268
|
+
turn_count = excluded.turn_count,
|
|
1269
|
+
token_estimate = excluded.token_estimate,
|
|
1270
|
+
metadata = excluded.metadata,
|
|
1271
|
+
updated_at = excluded.updated_at
|
|
1272
|
+
`).run(id, channel || 'ctm', summary || null, compacted_messages || null,
|
|
1273
|
+
turn_count || 0, token_estimate || 0, metadata || null, now, now);
|
|
1274
|
+
return id;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function listSessions({ channel, limit } = {}) {
|
|
1278
|
+
const params = [];
|
|
1279
|
+
let sql = 'SELECT * FROM sessions';
|
|
1280
|
+
if (channel) { sql += ' WHERE channel = ?'; params.push(channel); }
|
|
1281
|
+
sql += ' ORDER BY updated_at DESC';
|
|
1282
|
+
if (limit) { sql += ' LIMIT ?'; params.push(limit); }
|
|
1283
|
+
return getDb().prepare(sql).all(...params);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function expireSessions(maxAgeHours = 24) {
|
|
1287
|
+
const db = getDb();
|
|
1288
|
+
const cutoff = new Date(Date.now() - maxAgeHours * 3600000).toISOString();
|
|
1289
|
+
const result = db.prepare("DELETE FROM sessions WHERE updated_at < ?").run(cutoff);
|
|
1290
|
+
return result.changes;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// -- Initiative Log CRUD --
|
|
1294
|
+
|
|
1295
|
+
function insertInitiativeLog({ trigger, state_snapshot, reasoning, decision, decision_data, autonomy_tier }) {
|
|
1296
|
+
if (!trigger) throw new Error('Initiative log requires trigger');
|
|
1297
|
+
if (!decision) throw new Error('Initiative log requires decision');
|
|
1298
|
+
const id = uuidv4();
|
|
1299
|
+
getDb().prepare(
|
|
1300
|
+
'INSERT INTO initiative_log (id, trigger, state_snapshot, reasoning, decision, decision_data, autonomy_tier) VALUES (?, ?, ?, ?, ?, ?, ?)'
|
|
1301
|
+
).run(id, trigger, state_snapshot || null, reasoning || null, decision, decision_data || null, autonomy_tier || 1);
|
|
1302
|
+
return id;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function listInitiativeLogs({ limit, since } = {}) {
|
|
1306
|
+
const params = [];
|
|
1307
|
+
let sql = 'SELECT * FROM initiative_log';
|
|
1308
|
+
if (since) { sql += ' WHERE created_at >= ?'; params.push(since); }
|
|
1309
|
+
sql += ' ORDER BY created_at DESC';
|
|
1310
|
+
if (limit) { sql += ' LIMIT ?'; params.push(limit); }
|
|
1311
|
+
return getDb().prepare(sql).all(...params);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// -- Slack Threads CRUD --
|
|
1315
|
+
|
|
1316
|
+
function upsertSlackThread({ channelId, threadTs, taskId, sessionId }) {
|
|
1317
|
+
const id = `${channelId}:${threadTs}`;
|
|
1318
|
+
const now = new Date().toISOString();
|
|
1319
|
+
getDb().prepare(`
|
|
1320
|
+
INSERT INTO slack_threads (id, channel_id, thread_ts, task_id, session_id, last_activity)
|
|
1321
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
1322
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
1323
|
+
task_id = excluded.task_id,
|
|
1324
|
+
session_id = excluded.session_id,
|
|
1325
|
+
last_activity = excluded.last_activity
|
|
1326
|
+
`).run(id, channelId, threadTs, taskId || null, sessionId || null, now);
|
|
1327
|
+
return id;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
function getSlackThread(channelId, threadTs) {
|
|
1331
|
+
return getDb().prepare('SELECT * FROM slack_threads WHERE id = ?').get(`${channelId}:${threadTs}`);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
function listActiveSlackThreads(watchDurationMs) {
|
|
1335
|
+
const cutoff = new Date(Date.now() - (watchDurationMs || 7200000)).toISOString();
|
|
1336
|
+
return getDb().prepare('SELECT * FROM slack_threads WHERE last_activity >= ? ORDER BY last_activity DESC').all(cutoff);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function updateSlackThread(id, updates) {
|
|
1340
|
+
const allowed = ['last_activity', 'last_seen_ts', 'task_id', 'session_id'];
|
|
1341
|
+
const sets = [];
|
|
1342
|
+
const vals = [];
|
|
1343
|
+
for (const [k, v] of Object.entries(updates)) {
|
|
1344
|
+
if (allowed.includes(k)) { sets.push(`${k} = ?`); vals.push(v); }
|
|
1345
|
+
}
|
|
1346
|
+
if (sets.length === 0) return;
|
|
1347
|
+
vals.push(id);
|
|
1348
|
+
getDb().prepare(`UPDATE slack_threads SET ${sets.join(', ')} WHERE id = ?`).run(...vals);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function deleteExpiredSlackThreads(watchDurationMs) {
|
|
1352
|
+
const cutoff = new Date(Date.now() - (watchDurationMs || 7200000)).toISOString();
|
|
1353
|
+
return getDb().prepare('DELETE FROM slack_threads WHERE last_activity < ?').run(cutoff).changes;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1193
1356
|
module.exports = {
|
|
1194
1357
|
initDb,
|
|
1195
1358
|
getDb,
|
|
@@ -1277,4 +1440,18 @@ module.exports = {
|
|
|
1277
1440
|
updateBriefingItem,
|
|
1278
1441
|
findBriefingItemByTitle,
|
|
1279
1442
|
listOpenBriefingItems,
|
|
1443
|
+
// Sessions (context compaction)
|
|
1444
|
+
getSession,
|
|
1445
|
+
upsertSession,
|
|
1446
|
+
listSessions,
|
|
1447
|
+
expireSessions,
|
|
1448
|
+
// Initiative Log
|
|
1449
|
+
insertInitiativeLog,
|
|
1450
|
+
listInitiativeLogs,
|
|
1451
|
+
// Slack Threads
|
|
1452
|
+
upsertSlackThread,
|
|
1453
|
+
getSlackThread,
|
|
1454
|
+
listActiveSlackThreads,
|
|
1455
|
+
updateSlackThread,
|
|
1456
|
+
deleteExpiredSlackThreads,
|
|
1280
1457
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
const { execFile } = require('child_process');
|
|
3
3
|
const { promisify } = require('util');
|
|
4
4
|
const ChannelBase = require('./channel-base');
|
|
5
|
+
const eventBus = require('../events/event-bus');
|
|
5
6
|
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
|
|
@@ -43,9 +44,10 @@ class IMessageChannel extends ChannelBase {
|
|
|
43
44
|
|
|
44
45
|
async _checkMessages() {
|
|
45
46
|
// Use AppleScript to get the latest message from the buddy
|
|
47
|
+
const sanitizedBuddy = String(this.buddyId).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
46
48
|
const script = `
|
|
47
49
|
tell application "Messages"
|
|
48
|
-
set targetBuddy to "${
|
|
50
|
+
set targetBuddy to "${sanitizedBuddy}"
|
|
49
51
|
set latestMsg to ""
|
|
50
52
|
set latestId to ""
|
|
51
53
|
repeat with svc in services
|
|
@@ -76,6 +78,7 @@ class IMessageChannel extends ChannelBase {
|
|
|
76
78
|
this.lastMessageId = msgId;
|
|
77
79
|
|
|
78
80
|
console.log(`[imessage] New message from ${this.buddyId}: ${msgText.slice(0, 50)}...`);
|
|
81
|
+
eventBus.emitMessage('imessage', msgText, this.buddyId);
|
|
79
82
|
|
|
80
83
|
if (this._onMessage) {
|
|
81
84
|
const response = await this._onMessage(msgText, this.buddyId);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const ChannelBase = require('./channel-base');
|
|
3
|
+
const eventBus = require('../events/event-bus');
|
|
3
4
|
|
|
4
5
|
class SlackChannel extends ChannelBase {
|
|
5
6
|
constructor(opts = {}) {
|
|
6
7
|
super('slack_dm');
|
|
7
8
|
this.botToken = opts.botToken || process.env.SLACK_BOT_TOKEN || null;
|
|
8
|
-
this.pollInterval = opts.pollInterval ||
|
|
9
|
+
this.pollInterval = opts.pollInterval || 5000; // 5s
|
|
9
10
|
this.lastTimestamp = null;
|
|
10
11
|
this._pollTimer = null;
|
|
11
12
|
this._onMessage = opts.onMessage || null;
|
|
@@ -70,6 +71,7 @@ class SlackChannel extends ChannelBase {
|
|
|
70
71
|
|
|
71
72
|
this.lastTimestamp = msg.ts;
|
|
72
73
|
console.log(`[slack_dm] New DM from ${msg.user}: ${(msg.text || '').slice(0, 50)}...`);
|
|
74
|
+
eventBus.emitMessage('slack_dm', msg.text, msg.user);
|
|
73
75
|
|
|
74
76
|
if (this._onMessage && msg.text) {
|
|
75
77
|
const response = await this._onMessage(msg.text, msg.user);
|