heyio 0.42.0 → 1.0.0
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 +40 -52
- package/dist/api/auth.js +35 -38
- package/dist/api/server.js +157 -1139
- package/dist/config.js +49 -32
- package/dist/copilot/agents.js +72 -1055
- package/dist/copilot/client.js +6 -17
- package/dist/copilot/io-scheduler.js +55 -139
- package/dist/copilot/model-router.js +100 -72
- package/dist/copilot/orchestrator.js +91 -515
- package/dist/copilot/scheduler.js +67 -189
- package/dist/copilot/skills.js +41 -366
- package/dist/copilot/system-message.js +40 -200
- package/dist/copilot/tools.js +191 -2042
- package/dist/daemon.js +54 -201
- package/dist/index.js +15 -133
- package/dist/mcp/config.js +23 -31
- package/dist/mcp/index.js +2 -3
- package/dist/mcp/registry.js +33 -88
- package/dist/notify.js +18 -100
- package/dist/paths.js +13 -24
- package/dist/setup.js +35 -0
- package/dist/store/db.js +111 -297
- package/dist/store/feed.js +29 -97
- package/dist/store/instances.js +56 -121
- package/dist/store/schedules.js +21 -73
- package/dist/store/squads.js +35 -186
- package/dist/store/tasks.js +25 -168
- package/dist/telegram/bot.js +20 -312
- package/dist/telegram/handlers.js +39 -3
- package/dist/watchdog.js +31 -45
- package/dist/wiki/fs.js +38 -155
- package/dist/wiki/search.js +31 -44
- package/package.json +5 -8
- package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
- package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
- package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
- package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
- package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
- package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
- package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
- package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
- package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
- package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
- package/web-dist/assets/api-WGvTsXaE.js +1 -0
- package/web-dist/assets/index-D7M5O-_l.css +1 -0
- package/web-dist/assets/index-DZOS9syn.js +95 -0
- package/web-dist/assets/plus-BOvyX1BC.js +6 -0
- package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
- package/web-dist/favicon.svg +4 -1
- package/web-dist/index.html +7 -10
- package/dist/api/logout.test.js +0 -129
- package/dist/api/mcp.test.js +0 -285
- package/dist/api/wiki.test.js +0 -283
- package/dist/auth/session-logic.js +0 -79
- package/dist/auth/session-logic.test.js +0 -201
- package/dist/copilot/auto-complete-instance.test.js +0 -104
- package/dist/copilot/cron.js +0 -136
- package/dist/copilot/event-summary.js +0 -286
- package/dist/copilot/instance-deactivate.test.js +0 -119
- package/dist/copilot/model-router.test.js +0 -71
- package/dist/copilot/review-backfill.js +0 -57
- package/dist/copilot/session-timeout.js +0 -112
- package/dist/copilot/session-timeout.test.js +0 -372
- package/dist/copilot/skills.test.js +0 -55
- package/dist/copilot/universes.js +0 -469
- package/dist/instance-watchdog.js +0 -104
- package/dist/instance-watchdog.test.js +0 -183
- package/dist/mcp/client.js +0 -109
- package/dist/mcp/client.test.js +0 -99
- package/dist/mcp/config.test.js +0 -49
- package/dist/mcp/registry.test.js +0 -79
- package/dist/notify.test.js +0 -232
- package/dist/store/feed.test.js +0 -279
- package/dist/store/instances.test.js +0 -310
- package/dist/store/io-schedules.js +0 -63
- package/dist/store/notifications.js +0 -79
- package/dist/store/notifications.test.js +0 -197
- package/dist/store/schedule-runs.js +0 -46
- package/dist/store/squads.test.js +0 -405
- package/dist/store/tasks.test.js +0 -150
- package/dist/store/worktrees.js +0 -83
- package/dist/tui/index.js +0 -286
- package/dist/update.js +0 -81
- package/dist/watchdog.test.js +0 -83
- package/dist/wiki/wiki-squad.test.js +0 -54
- package/web-dist/assets/AgentActivityView-CedxxE6K.js +0 -1
- package/web-dist/assets/ChatView-DMkYQo_V.js +0 -4
- package/web-dist/assets/FeedView-BH4q-31V.js +0 -1
- package/web-dist/assets/InboxView-BVwVP4EW.js +0 -1
- package/web-dist/assets/LoginView-DRPDhnwu.js +0 -1
- package/web-dist/assets/McpView-D8yWz-lq.js +0 -1
- package/web-dist/assets/SchedulesView-BzzyncGF.js +0 -1
- package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-oW3ySu7Y.js +0 -1
- package/web-dist/assets/SkillsView-oxpYuhx7.js +0 -1
- package/web-dist/assets/SquadsView-CaKUIKlq.js +0 -1
- package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-8U15Qp_Q.js +0 -1
- package/web-dist/assets/WikiView-C5jXUlfW.js +0 -1
- package/web-dist/assets/index-BrWzNw-N.css +0 -10
- package/web-dist/assets/index-f67odrrt.js +0 -81
- package/web-dist/icons.svg +0 -24
package/dist/notify.js
CHANGED
|
@@ -1,106 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/^idle\.?$/i,
|
|
10
|
-
/^heartbeat\.?$/i,
|
|
11
|
-
/^ok\.?$/i,
|
|
12
|
-
];
|
|
13
|
-
export function isMeaningfulOutput(text) {
|
|
14
|
-
const trimmed = (text ?? "").trim();
|
|
15
|
-
if (trimmed.length < 20)
|
|
16
|
-
return false;
|
|
17
|
-
const firstLine = trimmed.split("\n").map((l) => l.trim()).find((l) => l.length > 0) ?? "";
|
|
18
|
-
if (HEARTBEAT_PATTERNS.some((re) => re.test(firstLine)))
|
|
19
|
-
return false;
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
let telegramSender;
|
|
23
|
-
let tuiSender;
|
|
24
|
-
let sseBroadcaster;
|
|
25
|
-
export function setTelegramSender(fn) {
|
|
26
|
-
telegramSender = fn;
|
|
27
|
-
}
|
|
28
|
-
export function setTuiSender(fn) {
|
|
29
|
-
tuiSender = fn;
|
|
30
|
-
}
|
|
31
|
-
export function setSseBroadcaster(fn) {
|
|
32
|
-
sseBroadcaster = fn;
|
|
33
|
-
}
|
|
34
|
-
export function _resetNotifySendersForTests() {
|
|
35
|
-
telegramSender = undefined;
|
|
36
|
-
tuiSender = undefined;
|
|
37
|
-
sseBroadcaster = undefined;
|
|
38
|
-
}
|
|
39
|
-
export async function notifyBackground(input) {
|
|
40
|
-
const dispatched = { telegram: false, tui: false, sse: false };
|
|
41
|
-
const text = (input.text ?? "").trim();
|
|
42
|
-
if (text.length === 0)
|
|
43
|
-
return { dispatched, skipped: "empty" };
|
|
44
|
-
const mode = config.backgroundNotifyMode ?? "meaningful";
|
|
45
|
-
if (mode === "off")
|
|
46
|
-
return { dispatched, skipped: "off" };
|
|
47
|
-
if (mode === "meaningful" && !isMeaningfulOutput(text)) {
|
|
48
|
-
return { dispatched, skipped: "not-meaningful" };
|
|
49
|
-
}
|
|
50
|
-
const { source, title } = input;
|
|
51
|
-
const sourceRefJson = JSON.stringify(stripType(source));
|
|
52
|
-
let row;
|
|
53
|
-
try {
|
|
54
|
-
row = createFeedEntry({
|
|
55
|
-
type: "notification",
|
|
56
|
-
title,
|
|
57
|
-
body: text,
|
|
58
|
-
source_type: source.type,
|
|
59
|
-
source_ref: sourceRefJson === "{}" ? null : sourceRefJson,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
console.error("[notify] failed to persist notification:", err);
|
|
64
|
-
return { dispatched };
|
|
65
|
-
}
|
|
66
|
-
if (sseBroadcaster) {
|
|
1
|
+
import { loadConfig } from "./config.js";
|
|
2
|
+
import { postFeedItem } from "./store/feed.js";
|
|
3
|
+
export async function notify(title, content, source) {
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
// Always post to feed
|
|
6
|
+
postFeedItem(source, title, content);
|
|
7
|
+
// Notify via Telegram if enabled
|
|
8
|
+
if (config.backgroundNotifyTelegram && config.telegramEnabled) {
|
|
67
9
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
title,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
10
|
+
const { getBot } = await import("./telegram/bot.js");
|
|
11
|
+
const bot = getBot();
|
|
12
|
+
if (bot && config.authorizedUserId) {
|
|
13
|
+
const msg = `📬 *${title}*\n\n${content.slice(0, 1000)}`;
|
|
14
|
+
await bot.api.sendMessage(config.authorizedUserId, msg, {
|
|
15
|
+
parse_mode: "Markdown",
|
|
16
|
+
});
|
|
17
|
+
}
|
|
76
18
|
}
|
|
77
|
-
catch
|
|
78
|
-
|
|
19
|
+
catch {
|
|
20
|
+
// Telegram notification failed silently
|
|
79
21
|
}
|
|
80
22
|
}
|
|
81
|
-
if (tuiSender && (config.backgroundNotifyTui ?? true)) {
|
|
82
|
-
try {
|
|
83
|
-
tuiSender({ title, text });
|
|
84
|
-
dispatched.tui = true;
|
|
85
|
-
}
|
|
86
|
-
catch (err) {
|
|
87
|
-
console.error("[notify] tui send failed:", err);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (telegramSender && (config.backgroundNotifyTelegram ?? true)) {
|
|
91
|
-
try {
|
|
92
|
-
await telegramSender({ title, text });
|
|
93
|
-
dispatched.telegram = true;
|
|
94
|
-
}
|
|
95
|
-
catch (err) {
|
|
96
|
-
console.error("[notify] telegram send failed:", err);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return { id: row.id, dispatched };
|
|
100
|
-
}
|
|
101
|
-
function stripType(s) {
|
|
102
|
-
const { type: _t, ...rest } = s;
|
|
103
|
-
void _t;
|
|
104
|
-
return rest;
|
|
105
23
|
}
|
|
106
24
|
//# sourceMappingURL=notify.js.map
|
package/dist/paths.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
|
-
import { homedir } from "os";
|
|
2
|
-
import { join
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
for (const rel of ["..", join("..", "..")]) {
|
|
15
|
-
try {
|
|
16
|
-
const pkg = JSON.parse(readFileSync(join(__dirname, rel, "package.json"), "utf-8"));
|
|
17
|
-
if (pkg.version)
|
|
18
|
-
return pkg.version;
|
|
19
|
-
}
|
|
20
|
-
catch { /* try next */ }
|
|
21
|
-
}
|
|
22
|
-
return "0.0.0";
|
|
23
|
-
}
|
|
24
|
-
export const IO_VERSION = resolveVersion();
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const IO_HOME = join(homedir(), ".io");
|
|
4
|
+
export const PATHS = {
|
|
5
|
+
home: IO_HOME,
|
|
6
|
+
config: join(IO_HOME, "config.json"),
|
|
7
|
+
db: join(IO_HOME, "io.db"),
|
|
8
|
+
wiki: join(IO_HOME, "wiki"),
|
|
9
|
+
wikiPages: join(IO_HOME, "wiki", "pages"),
|
|
10
|
+
skills: join(IO_HOME, "skills"),
|
|
11
|
+
mcpConfig: join(IO_HOME, "mcp.json"),
|
|
12
|
+
sessions: join(IO_HOME, "sessions"),
|
|
13
|
+
};
|
|
25
14
|
//# sourceMappingURL=paths.js.map
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createInterface } from "node:readline";
|
|
2
|
+
import { loadConfig, saveConfig } from "./config.js";
|
|
3
|
+
import { PATHS } from "./paths.js";
|
|
4
|
+
import { mkdirSync, existsSync } from "node:fs";
|
|
5
|
+
export async function runSetup() {
|
|
6
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7
|
+
const ask = (q) => new Promise((resolve) => rl.question(q, resolve));
|
|
8
|
+
console.log("\n🤖 IO Setup\n");
|
|
9
|
+
if (!existsSync(PATHS.home))
|
|
10
|
+
mkdirSync(PATHS.home, { recursive: true });
|
|
11
|
+
const config = loadConfig();
|
|
12
|
+
// Supabase (required for API auth)
|
|
13
|
+
console.log("— Supabase Auth (required for web/API access) —");
|
|
14
|
+
const supabaseUrl = await ask(`Supabase URL [${config.supabaseUrl ?? ""}]: `);
|
|
15
|
+
const supabaseAnonKey = await ask(`Supabase Anon Key [${config.supabaseAnonKey ? "****" : ""}]: `);
|
|
16
|
+
const authorizedEmail = await ask(`Authorized Email [${config.authorizedEmail ?? ""}]: `);
|
|
17
|
+
// Telegram (optional)
|
|
18
|
+
console.log("\n— Telegram Bot (optional) —");
|
|
19
|
+
const telegramBotToken = await ask(`Bot Token [${config.telegramBotToken ? "****" : ""}]: `);
|
|
20
|
+
const authorizedUserId = await ask(`Your Telegram User ID [${config.authorizedUserId ?? ""}]: `);
|
|
21
|
+
const telegramEnabled = await ask(`Enable Telegram? (y/n) [${config.telegramEnabled ? "y" : "n"}]: `);
|
|
22
|
+
saveConfig({
|
|
23
|
+
supabaseUrl: supabaseUrl || config.supabaseUrl,
|
|
24
|
+
supabaseAnonKey: supabaseAnonKey || config.supabaseAnonKey,
|
|
25
|
+
authorizedEmail: authorizedEmail || config.authorizedEmail,
|
|
26
|
+
telegramBotToken: telegramBotToken || config.telegramBotToken,
|
|
27
|
+
authorizedUserId: authorizedUserId
|
|
28
|
+
? parseInt(authorizedUserId, 10)
|
|
29
|
+
: config.authorizedUserId,
|
|
30
|
+
telegramEnabled: telegramEnabled === "y" ? true : telegramEnabled === "n" ? false : config.telegramEnabled,
|
|
31
|
+
});
|
|
32
|
+
console.log(`\n✅ Configuration saved to ${PATHS.config}\n`);
|
|
33
|
+
rl.close();
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=setup.js.map
|
package/dist/store/db.js
CHANGED
|
@@ -1,318 +1,132 @@
|
|
|
1
1
|
import Database from "better-sqlite3";
|
|
2
|
-
import { mkdirSync } from "fs";
|
|
3
|
-
import { dirname } from "path";
|
|
4
|
-
import {
|
|
5
|
-
let db
|
|
6
|
-
let insertCount = 0;
|
|
7
|
-
let dbPathOverride = null;
|
|
8
|
-
/**
|
|
9
|
-
* Override the DB path for tests. Closes the existing connection (if any)
|
|
10
|
-
* so the next getDb() call opens a fresh DB at the given path.
|
|
11
|
-
* Never call this in production code.
|
|
12
|
-
*/
|
|
13
|
-
export function setDbPathForTests(path) {
|
|
14
|
-
if (db) {
|
|
15
|
-
db.close();
|
|
16
|
-
db = null;
|
|
17
|
-
}
|
|
18
|
-
dbPathOverride = path;
|
|
19
|
-
}
|
|
2
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { PATHS } from "../paths.js";
|
|
5
|
+
let db;
|
|
20
6
|
export function getDb() {
|
|
21
7
|
if (db)
|
|
22
8
|
return db;
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
db = new Database(
|
|
9
|
+
const dir = dirname(PATHS.db);
|
|
10
|
+
if (!existsSync(dir))
|
|
11
|
+
mkdirSync(dir, { recursive: true });
|
|
12
|
+
db = new Database(PATHS.db);
|
|
27
13
|
db.pragma("journal_mode = WAL");
|
|
14
|
+
db.pragma("foreign_keys = ON");
|
|
15
|
+
runMigrations(db);
|
|
16
|
+
return db;
|
|
17
|
+
}
|
|
18
|
+
function runMigrations(db) {
|
|
28
19
|
db.exec(`
|
|
29
|
-
CREATE TABLE IF NOT EXISTS
|
|
20
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
30
21
|
key TEXT PRIMARY KEY,
|
|
31
22
|
value TEXT NOT NULL
|
|
32
23
|
);
|
|
24
|
+
`);
|
|
25
|
+
const version = getSchemaVersion(db);
|
|
26
|
+
if (version < 1) {
|
|
27
|
+
db.exec(`
|
|
28
|
+
CREATE TABLE IF NOT EXISTS squads (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
name TEXT NOT NULL,
|
|
31
|
+
universe TEXT NOT NULL,
|
|
32
|
+
repo_url TEXT,
|
|
33
|
+
rules TEXT DEFAULT '',
|
|
34
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
35
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
36
|
+
);
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
CREATE TABLE IF NOT EXISTS agents (
|
|
39
|
+
id TEXT PRIMARY KEY,
|
|
40
|
+
squad_id TEXT NOT NULL REFERENCES squads(id) ON DELETE CASCADE,
|
|
41
|
+
character_name TEXT NOT NULL,
|
|
42
|
+
role_title TEXT NOT NULL,
|
|
43
|
+
persona TEXT DEFAULT '',
|
|
44
|
+
is_lead INTEGER NOT NULL DEFAULT 0,
|
|
45
|
+
is_qa INTEGER NOT NULL DEFAULT 0,
|
|
46
|
+
is_test INTEGER NOT NULL DEFAULT 0,
|
|
47
|
+
status TEXT NOT NULL DEFAULT 'idle',
|
|
48
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
49
|
+
);
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
52
|
+
id TEXT PRIMARY KEY,
|
|
53
|
+
squad_id TEXT NOT NULL REFERENCES squads(id) ON DELETE CASCADE,
|
|
54
|
+
instance_id TEXT,
|
|
55
|
+
agent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
|
|
56
|
+
description TEXT NOT NULL,
|
|
57
|
+
status TEXT NOT NULL DEFAULT 'pending',
|
|
58
|
+
result TEXT,
|
|
59
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
60
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
61
|
+
);
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
CREATE TABLE IF NOT EXISTS instances (
|
|
64
|
+
id TEXT PRIMARY KEY,
|
|
65
|
+
squad_id TEXT NOT NULL REFERENCES squads(id) ON DELETE CASCADE,
|
|
66
|
+
branch TEXT NOT NULL,
|
|
67
|
+
worktree_path TEXT NOT NULL,
|
|
68
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
69
|
+
last_activity TEXT NOT NULL DEFAULT (datetime('now')),
|
|
70
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
71
|
+
);
|
|
61
72
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
agenda TEXT NOT NULL,
|
|
107
|
-
notes TEXT,
|
|
108
|
-
enabled INTEGER NOT NULL DEFAULT 1,
|
|
109
|
-
last_run_at DATETIME,
|
|
110
|
-
next_run_at DATETIME,
|
|
111
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
112
|
-
)`,
|
|
113
|
-
`CREATE INDEX IF NOT EXISTS idx_squad_schedules_due
|
|
114
|
-
ON squad_schedules (enabled, next_run_at)`,
|
|
115
|
-
`CREATE TABLE IF NOT EXISTS io_schedules (
|
|
116
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
117
|
-
name TEXT NOT NULL,
|
|
118
|
-
cron_expr TEXT NOT NULL,
|
|
119
|
-
prompt TEXT NOT NULL,
|
|
120
|
-
notes TEXT,
|
|
121
|
-
enabled INTEGER NOT NULL DEFAULT 1,
|
|
122
|
-
last_run_at DATETIME,
|
|
123
|
-
next_run_at DATETIME,
|
|
124
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
125
|
-
)`,
|
|
126
|
-
`CREATE INDEX IF NOT EXISTS idx_io_schedules_due
|
|
127
|
-
ON io_schedules (enabled, next_run_at)`,
|
|
128
|
-
`CREATE VIEW IF NOT EXISTS agent_stats AS
|
|
129
|
-
SELECT agent_slug,
|
|
130
|
-
COUNT(*) AS task_count,
|
|
131
|
-
MAX(started_at) AS last_delegated_at
|
|
132
|
-
FROM agent_tasks
|
|
133
|
-
GROUP BY agent_slug`,
|
|
134
|
-
`CREATE TABLE IF NOT EXISTS background_notifications (
|
|
135
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
136
|
-
source_type TEXT NOT NULL,
|
|
137
|
-
source_ref TEXT,
|
|
138
|
-
title TEXT NOT NULL,
|
|
139
|
-
text TEXT NOT NULL,
|
|
140
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
141
|
-
read_at DATETIME
|
|
142
|
-
)`,
|
|
143
|
-
`CREATE INDEX IF NOT EXISTS idx_bg_notifications_unread ON background_notifications(read_at, created_at)`,
|
|
144
|
-
`CREATE TABLE IF NOT EXISTS schedule_runs (
|
|
145
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
146
|
-
schedule_type TEXT NOT NULL,
|
|
147
|
-
schedule_id INTEGER NOT NULL,
|
|
148
|
-
schedule_name TEXT NOT NULL,
|
|
149
|
-
squad_slug TEXT,
|
|
150
|
-
status TEXT NOT NULL DEFAULT 'running',
|
|
151
|
-
error_text TEXT,
|
|
152
|
-
notification_id INTEGER,
|
|
153
|
-
started_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
154
|
-
completed_at DATETIME
|
|
155
|
-
)`,
|
|
156
|
-
`CREATE INDEX IF NOT EXISTS idx_schedule_runs_lookup ON schedule_runs(schedule_type, schedule_id, started_at)`,
|
|
157
|
-
`CREATE TABLE IF NOT EXISTS inbox_entries (
|
|
158
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
159
|
-
title TEXT NOT NULL,
|
|
160
|
-
body TEXT NOT NULL,
|
|
161
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
162
|
-
)`,
|
|
163
|
-
`CREATE TABLE IF NOT EXISTS unified_feed (
|
|
164
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
165
|
-
type TEXT NOT NULL CHECK(type IN ('inbox', 'notification')),
|
|
166
|
-
title TEXT NOT NULL,
|
|
167
|
-
body TEXT NOT NULL,
|
|
168
|
-
source_type TEXT,
|
|
169
|
-
source_ref TEXT,
|
|
170
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
171
|
-
read_at DATETIME
|
|
172
|
-
)`,
|
|
173
|
-
`CREATE INDEX IF NOT EXISTS idx_unified_feed_type ON unified_feed(type, created_at)`,
|
|
174
|
-
`CREATE INDEX IF NOT EXISTS idx_unified_feed_unread ON unified_feed(read_at, created_at)`,
|
|
175
|
-
`ALTER TABLE squads ADD COLUMN color TEXT`,
|
|
176
|
-
`CREATE TABLE IF NOT EXISTS squad_instances (
|
|
177
|
-
id TEXT PRIMARY KEY,
|
|
178
|
-
master_squad_slug TEXT NOT NULL,
|
|
179
|
-
issue_ref TEXT,
|
|
180
|
-
worktree_path TEXT NOT NULL,
|
|
181
|
-
branch_name TEXT NOT NULL,
|
|
182
|
-
status TEXT NOT NULL DEFAULT 'pending',
|
|
183
|
-
context_snapshot TEXT,
|
|
184
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
185
|
-
completed_at DATETIME
|
|
186
|
-
)`,
|
|
187
|
-
`CREATE TABLE IF NOT EXISTS instance_decisions (
|
|
188
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
189
|
-
instance_id TEXT NOT NULL,
|
|
190
|
-
decision TEXT NOT NULL,
|
|
191
|
-
context TEXT,
|
|
192
|
-
merged_to_master INTEGER DEFAULT 0,
|
|
193
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
194
|
-
)`,
|
|
195
|
-
`ALTER TABLE agent_tasks ADD COLUMN instance_id TEXT`,
|
|
196
|
-
`CREATE INDEX IF NOT EXISTS idx_instance_decisions_instance ON instance_decisions(instance_id, merged_to_master)`,
|
|
197
|
-
`ALTER TABLE unified_feed RENAME TO unified_feed_old`,
|
|
198
|
-
`CREATE TABLE unified_feed (
|
|
199
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
200
|
-
type TEXT NOT NULL CHECK(type IN ('inbox', 'notification')),
|
|
201
|
-
title TEXT NOT NULL,
|
|
202
|
-
body TEXT NOT NULL,
|
|
203
|
-
source_type TEXT,
|
|
204
|
-
source_ref TEXT,
|
|
205
|
-
squad_slug TEXT,
|
|
206
|
-
instance_id TEXT,
|
|
207
|
-
task_id TEXT,
|
|
208
|
-
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
209
|
-
read_at DATETIME
|
|
210
|
-
)`,
|
|
211
|
-
`INSERT INTO unified_feed (id, type, title, body, source_type, source_ref, created_at, read_at)
|
|
212
|
-
SELECT id, CASE WHEN type='deliverable' THEN 'inbox' ELSE type END, title, body, source_type, source_ref, created_at, read_at
|
|
213
|
-
FROM unified_feed_old`,
|
|
214
|
-
`DROP TABLE unified_feed_old`,
|
|
215
|
-
`CREATE INDEX IF NOT EXISTS idx_unified_feed_type ON unified_feed(type, created_at)`,
|
|
216
|
-
`CREATE INDEX IF NOT EXISTS idx_unified_feed_unread ON unified_feed(read_at, created_at)`,
|
|
217
|
-
`ALTER TABLE squads ADD COLUMN color TEXT`,
|
|
218
|
-
];
|
|
219
|
-
for (const migration of migrations) {
|
|
220
|
-
try {
|
|
221
|
-
db.exec(migration);
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
// Already applied — ignore
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// One-time data migration: copy inbox_entries + background_notifications → unified_feed
|
|
228
|
-
try {
|
|
229
|
-
const migrated = db.prepare("SELECT value FROM io_state WHERE key = 'unified_feed_migrated'").get();
|
|
230
|
-
if (!migrated) {
|
|
231
|
-
db.exec(`
|
|
232
|
-
INSERT OR IGNORE INTO unified_feed (type, title, body, source_type, source_ref, created_at, read_at)
|
|
233
|
-
SELECT 'deliverable', title, body, NULL, NULL, created_at, NULL
|
|
234
|
-
FROM inbox_entries
|
|
235
|
-
`);
|
|
236
|
-
db.exec(`
|
|
237
|
-
INSERT OR IGNORE INTO unified_feed (type, title, body, source_type, source_ref, created_at, read_at)
|
|
238
|
-
SELECT 'notification', title, text, source_type, source_ref, created_at, read_at
|
|
239
|
-
FROM background_notifications
|
|
240
|
-
`);
|
|
241
|
-
db.prepare("INSERT OR REPLACE INTO io_state (key, value) VALUES ('unified_feed_migrated', '1')").run();
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
// Migration failed (e.g. old tables don't exist yet on a fresh install) — safe to ignore
|
|
246
|
-
}
|
|
247
|
-
// One-time migration: assign colors to existing squads that have none
|
|
248
|
-
try {
|
|
249
|
-
const colorMigrated = db.prepare("SELECT value FROM io_state WHERE key = 'squad_colors_migrated'").get();
|
|
250
|
-
if (!colorMigrated) {
|
|
251
|
-
const palette = [
|
|
252
|
-
"#ff6b35", "#ffd000", "#5fff87", "#c4a7ff", "#00d9ff",
|
|
253
|
-
"#ff9800", "#9c27b0", "#2196f3", "#e91e63", "#00bcd4",
|
|
254
|
-
"#8bc34a", "#ff5722",
|
|
255
|
-
];
|
|
256
|
-
const uncolored = db.prepare("SELECT slug FROM squads WHERE color IS NULL ORDER BY id ASC").all();
|
|
257
|
-
const usedColors = new Set(db.prepare("SELECT color FROM squads WHERE color IS NOT NULL").all().map(r => r.color));
|
|
258
|
-
const update = db.prepare("UPDATE squads SET color = ? WHERE slug = ?");
|
|
259
|
-
let idx = 0;
|
|
260
|
-
for (const { slug } of uncolored) {
|
|
261
|
-
const available = palette.filter(c => !usedColors.has(c));
|
|
262
|
-
const color = available.length > 0 ? available[0] : palette[idx % palette.length];
|
|
263
|
-
update.run(color, slug);
|
|
264
|
-
usedColors.add(color);
|
|
265
|
-
idx++;
|
|
266
|
-
}
|
|
267
|
-
db.prepare("INSERT OR REPLACE INTO io_state (key, value) VALUES ('squad_colors_migrated', '1')").run();
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
// Safe to ignore on fresh install
|
|
73
|
+
CREATE TABLE IF NOT EXISTS feed_items (
|
|
74
|
+
id TEXT PRIMARY KEY,
|
|
75
|
+
source TEXT NOT NULL,
|
|
76
|
+
title TEXT NOT NULL,
|
|
77
|
+
content TEXT NOT NULL DEFAULT '',
|
|
78
|
+
read INTEGER NOT NULL DEFAULT 0,
|
|
79
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE TABLE IF NOT EXISTS schedules (
|
|
83
|
+
id TEXT PRIMARY KEY,
|
|
84
|
+
type TEXT NOT NULL CHECK(type IN ('squad', 'io')),
|
|
85
|
+
squad_id TEXT REFERENCES squads(id) ON DELETE CASCADE,
|
|
86
|
+
cron TEXT NOT NULL,
|
|
87
|
+
agenda TEXT NOT NULL DEFAULT '',
|
|
88
|
+
prompt TEXT NOT NULL DEFAULT '',
|
|
89
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
90
|
+
last_run TEXT,
|
|
91
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS session_state (
|
|
95
|
+
key TEXT PRIMARY KEY,
|
|
96
|
+
value TEXT NOT NULL
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS mcp_servers (
|
|
100
|
+
id TEXT PRIMARY KEY,
|
|
101
|
+
name TEXT NOT NULL,
|
|
102
|
+
type TEXT NOT NULL CHECK(type IN ('stdio', 'http')),
|
|
103
|
+
config TEXT NOT NULL DEFAULT '{}',
|
|
104
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
105
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
CREATE TABLE IF NOT EXISTS mcp_tools (
|
|
109
|
+
id TEXT PRIMARY KEY,
|
|
110
|
+
server_id TEXT NOT NULL REFERENCES mcp_servers(id) ON DELETE CASCADE,
|
|
111
|
+
name TEXT NOT NULL,
|
|
112
|
+
description TEXT DEFAULT '',
|
|
113
|
+
enabled INTEGER NOT NULL DEFAULT 1
|
|
114
|
+
);
|
|
115
|
+
`);
|
|
116
|
+
setSchemaVersion(db, 1);
|
|
272
117
|
}
|
|
273
|
-
|
|
118
|
+
}
|
|
119
|
+
function getSchemaVersion(db) {
|
|
120
|
+
const row = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
|
121
|
+
return row ? parseInt(row.value, 10) : 0;
|
|
122
|
+
}
|
|
123
|
+
function setSchemaVersion(db, version) {
|
|
124
|
+
db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)").run(String(version));
|
|
274
125
|
}
|
|
275
126
|
export function closeDb() {
|
|
276
127
|
if (db) {
|
|
277
128
|
db.close();
|
|
278
|
-
db =
|
|
129
|
+
db = undefined;
|
|
279
130
|
}
|
|
280
131
|
}
|
|
281
|
-
export function getState(key) {
|
|
282
|
-
const row = getDb()
|
|
283
|
-
.prepare("SELECT value FROM io_state WHERE key = ?")
|
|
284
|
-
.get(key);
|
|
285
|
-
return row?.value;
|
|
286
|
-
}
|
|
287
|
-
export function setState(key, value) {
|
|
288
|
-
getDb()
|
|
289
|
-
.prepare("INSERT INTO io_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value")
|
|
290
|
-
.run(key, value);
|
|
291
|
-
}
|
|
292
|
-
export function deleteState(key) {
|
|
293
|
-
getDb().prepare("DELETE FROM io_state WHERE key = ?").run(key);
|
|
294
|
-
}
|
|
295
|
-
export function logConversation(role, content, source) {
|
|
296
|
-
const database = getDb();
|
|
297
|
-
database
|
|
298
|
-
.prepare("INSERT INTO conversation_log (role, content, source) VALUES (?, ?, ?)")
|
|
299
|
-
.run(role, content, source);
|
|
300
|
-
insertCount++;
|
|
301
|
-
if (insertCount % 50 === 0) {
|
|
302
|
-
database
|
|
303
|
-
.prepare("DELETE FROM conversation_log WHERE id NOT IN (SELECT id FROM conversation_log ORDER BY id DESC LIMIT 1000)")
|
|
304
|
-
.run();
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
export function getRecentConversation(limit = 50) {
|
|
308
|
-
const rows = getDb()
|
|
309
|
-
.prepare("SELECT role, content, source, ts FROM conversation_log ORDER BY id DESC LIMIT ?")
|
|
310
|
-
.all(limit);
|
|
311
|
-
return rows.reverse().map((row) => ({
|
|
312
|
-
...row,
|
|
313
|
-
content: row.content.length > 1500
|
|
314
|
-
? row.content.slice(0, 1500) + "…"
|
|
315
|
-
: row.content,
|
|
316
|
-
}));
|
|
317
|
-
}
|
|
318
132
|
//# sourceMappingURL=db.js.map
|