heyio 0.3.0 → 0.5.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/dist/api/server.js +171 -1
- package/dist/copilot/agents.js +29 -12
- package/dist/copilot/io-scheduler.js +132 -0
- package/dist/copilot/review-backfill.js +57 -0
- package/dist/copilot/scheduler.js +19 -3
- package/dist/copilot/system-message.js +8 -0
- package/dist/copilot/tools.js +115 -2
- package/dist/daemon.js +27 -1
- package/dist/store/db.js +13 -0
- package/dist/store/io-schedules.js +63 -0
- package/dist/store/schedules.js +10 -0
- package/dist/store/squads.js +21 -0
- package/package.json +1 -1
- package/web-dist/assets/{index-BksyB2za.js → index-BYoiwmlj.js} +20 -20
- package/web-dist/assets/index-DMKRXYjX.css +1 -0
- package/web-dist/index.html +2 -2
- package/web-dist/assets/index-BWGQix5_.css +0 -1
package/dist/daemon.js
CHANGED
|
@@ -4,7 +4,10 @@ import { startApiServer, setMessageHandler as setApiHandler } from "./api/server
|
|
|
4
4
|
import { createBot, startBot, stopBot, sendProactiveMessage, setMessageHandler as setTelegramHandler } from "./telegram/bot.js";
|
|
5
5
|
import { getDb, closeDb } from "./store/db.js";
|
|
6
6
|
import { clearStaleTasks } from "./store/tasks.js";
|
|
7
|
+
import { reconcileAgentStatuses, reconcileSquadStatuses } from "./store/squads.js";
|
|
8
|
+
import { backfillReviewVerdicts } from "./copilot/review-backfill.js";
|
|
7
9
|
import { startScheduler, stopScheduler } from "./copilot/scheduler.js";
|
|
10
|
+
import { startIoScheduler, stopIoScheduler } from "./copilot/io-scheduler.js";
|
|
8
11
|
import { config } from "./config.js";
|
|
9
12
|
import { ensureWikiStructure } from "./wiki/fs.js";
|
|
10
13
|
import { autoUpdate } from "./update.js";
|
|
@@ -62,8 +65,28 @@ export async function startDaemon() {
|
|
|
62
65
|
if (wikiIsNew) {
|
|
63
66
|
console.log("[io] Created wiki at ~/.io/wiki/");
|
|
64
67
|
}
|
|
65
|
-
// Clear stale tasks from previous run
|
|
68
|
+
// Clear stale tasks from previous run, and reset any agent/squad rows left
|
|
69
|
+
// in 'working' or 'error' state — the in-memory Copilot sessions backing
|
|
70
|
+
// those rows did not survive the restart, so the persisted status is lying.
|
|
66
71
|
clearStaleTasks();
|
|
72
|
+
const resetAgents = reconcileAgentStatuses();
|
|
73
|
+
const resetSquads = reconcileSquadStatuses();
|
|
74
|
+
if (resetAgents > 0 || resetSquads > 0) {
|
|
75
|
+
console.log(`[io] Reconciled stale statuses on startup: ${resetAgents} agent(s), ${resetSquads} squad(s) → idle`);
|
|
76
|
+
}
|
|
77
|
+
// Backfill any historical peer-review rows whose recorded verdict (approved
|
|
78
|
+
// 0/1) does not match what the current parser would extract from the prose.
|
|
79
|
+
// Earlier daemon builds had a brittle first-line-only parser that flipped
|
|
80
|
+
// many APPROVED reviews into REJECTED (issue #50).
|
|
81
|
+
try {
|
|
82
|
+
const fixed = backfillReviewVerdicts();
|
|
83
|
+
if (fixed > 0) {
|
|
84
|
+
console.log(`[io] Backfilled ${fixed} peer-review verdict(s) using current parser`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
console.error("[io] Review-verdict backfill failed:", err instanceof Error ? err.message : err);
|
|
89
|
+
}
|
|
67
90
|
// Prune old sessions
|
|
68
91
|
pruneOldSessions();
|
|
69
92
|
// Start Copilot SDK client
|
|
@@ -93,6 +116,8 @@ export async function startDaemon() {
|
|
|
93
116
|
}
|
|
94
117
|
// Start the squad scheduler (background cron-style stand-ups).
|
|
95
118
|
startScheduler();
|
|
119
|
+
// Start the IO-level scheduler (squad-independent recurring tasks).
|
|
120
|
+
startIoScheduler();
|
|
96
121
|
console.log("[io] IO is fully operational.");
|
|
97
122
|
// Notify Telegram if restarting
|
|
98
123
|
if (config.telegramEnabled && process.env.IO_RESTARTED === "1") {
|
|
@@ -121,6 +146,7 @@ async function shutdown() {
|
|
|
121
146
|
catch { /* best effort */ }
|
|
122
147
|
}
|
|
123
148
|
stopScheduler();
|
|
149
|
+
stopIoScheduler();
|
|
124
150
|
await shutdownOrchestrator();
|
|
125
151
|
try {
|
|
126
152
|
await stopClient();
|
package/dist/store/db.js
CHANGED
|
@@ -96,6 +96,19 @@ export function getDb() {
|
|
|
96
96
|
)`,
|
|
97
97
|
`CREATE INDEX IF NOT EXISTS idx_squad_schedules_due
|
|
98
98
|
ON squad_schedules (enabled, next_run_at)`,
|
|
99
|
+
`CREATE TABLE IF NOT EXISTS io_schedules (
|
|
100
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
101
|
+
name TEXT NOT NULL,
|
|
102
|
+
cron_expr TEXT NOT NULL,
|
|
103
|
+
prompt TEXT NOT NULL,
|
|
104
|
+
notes TEXT,
|
|
105
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
106
|
+
last_run_at DATETIME,
|
|
107
|
+
next_run_at DATETIME,
|
|
108
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
109
|
+
)`,
|
|
110
|
+
`CREATE INDEX IF NOT EXISTS idx_io_schedules_due
|
|
111
|
+
ON io_schedules (enabled, next_run_at)`,
|
|
99
112
|
];
|
|
100
113
|
for (const migration of migrations) {
|
|
101
114
|
try {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { getDb } from "./db.js";
|
|
2
|
+
export function createIoSchedule(input) {
|
|
3
|
+
const db = getDb();
|
|
4
|
+
const info = db
|
|
5
|
+
.prepare(`INSERT INTO io_schedules
|
|
6
|
+
(name, cron_expr, prompt, notes, enabled, next_run_at)
|
|
7
|
+
VALUES (?, ?, ?, ?, 1, ?)`)
|
|
8
|
+
.run(input.name, input.cronExpr, input.prompt, input.notes ?? null, input.nextRunAt);
|
|
9
|
+
const id = Number(info.lastInsertRowid);
|
|
10
|
+
return getIoSchedule(id);
|
|
11
|
+
}
|
|
12
|
+
export function getIoSchedule(id) {
|
|
13
|
+
return getDb()
|
|
14
|
+
.prepare("SELECT * FROM io_schedules WHERE id = ?")
|
|
15
|
+
.get(id);
|
|
16
|
+
}
|
|
17
|
+
export function listIoSchedules() {
|
|
18
|
+
return getDb()
|
|
19
|
+
.prepare("SELECT * FROM io_schedules ORDER BY id ASC")
|
|
20
|
+
.all();
|
|
21
|
+
}
|
|
22
|
+
export function listDueIoSchedules(now) {
|
|
23
|
+
return getDb()
|
|
24
|
+
.prepare(`SELECT * FROM io_schedules
|
|
25
|
+
WHERE enabled = 1
|
|
26
|
+
AND next_run_at IS NOT NULL
|
|
27
|
+
AND next_run_at <= ?
|
|
28
|
+
ORDER BY next_run_at ASC`)
|
|
29
|
+
.all(now.toISOString());
|
|
30
|
+
}
|
|
31
|
+
export function deleteIoSchedule(id) {
|
|
32
|
+
const info = getDb()
|
|
33
|
+
.prepare("DELETE FROM io_schedules WHERE id = ?")
|
|
34
|
+
.run(id);
|
|
35
|
+
return info.changes > 0;
|
|
36
|
+
}
|
|
37
|
+
export function setIoScheduleEnabled(id, enabled) {
|
|
38
|
+
const info = getDb()
|
|
39
|
+
.prepare("UPDATE io_schedules SET enabled = ? WHERE id = ?")
|
|
40
|
+
.run(enabled ? 1 : 0, id);
|
|
41
|
+
return info.changes > 0;
|
|
42
|
+
}
|
|
43
|
+
export function recordIoScheduleRun(id, ranAt, nextRunAt) {
|
|
44
|
+
getDb()
|
|
45
|
+
.prepare("UPDATE io_schedules SET last_run_at = ?, next_run_at = ? WHERE id = ?")
|
|
46
|
+
.run(ranAt.toISOString(), nextRunAt, id);
|
|
47
|
+
}
|
|
48
|
+
export function updateIoScheduleNextRun(id, nextRunAt) {
|
|
49
|
+
getDb()
|
|
50
|
+
.prepare("UPDATE io_schedules SET next_run_at = ? WHERE id = ?")
|
|
51
|
+
.run(nextRunAt, id);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Overwrite both last_run_at and next_run_at directly. Unlike
|
|
55
|
+
* recordIoScheduleRun this accepts NULL for last_run_at, which is needed when
|
|
56
|
+
* restoring a schedule's "never run" state after a manual run_now.
|
|
57
|
+
*/
|
|
58
|
+
export function setIoScheduleTimestamps(id, lastRunAt, nextRunAt) {
|
|
59
|
+
getDb()
|
|
60
|
+
.prepare("UPDATE io_schedules SET last_run_at = ?, next_run_at = ? WHERE id = ?")
|
|
61
|
+
.run(lastRunAt, nextRunAt, id);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=io-schedules.js.map
|
package/dist/store/schedules.js
CHANGED
|
@@ -70,4 +70,14 @@ export function updateNextRun(id, nextRunAt) {
|
|
|
70
70
|
.prepare("UPDATE squad_schedules SET next_run_at = ? WHERE id = ?")
|
|
71
71
|
.run(nextRunAt, id);
|
|
72
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Overwrite both last_run_at and next_run_at directly. Unlike
|
|
75
|
+
* recordScheduleRun this accepts NULL for last_run_at, which is needed when
|
|
76
|
+
* restoring a schedule's "never run" state after a manual run_now.
|
|
77
|
+
*/
|
|
78
|
+
export function setScheduleTimestamps(id, lastRunAt, nextRunAt) {
|
|
79
|
+
getDb()
|
|
80
|
+
.prepare("UPDATE squad_schedules SET last_run_at = ?, next_run_at = ? WHERE id = ?")
|
|
81
|
+
.run(lastRunAt, nextRunAt, id);
|
|
82
|
+
}
|
|
73
83
|
//# sourceMappingURL=schedules.js.map
|
package/dist/store/squads.js
CHANGED
|
@@ -93,6 +93,27 @@ export function updateAgentStatus(squadSlug, characterName, status) {
|
|
|
93
93
|
.prepare("UPDATE squad_agents SET status = ? WHERE squad_slug = ? AND character_name = ?")
|
|
94
94
|
.run(status, squadSlug, characterName);
|
|
95
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* Reset any agent left in a non-idle status from a previous daemon run.
|
|
98
|
+
* The in-memory Copilot sessions don't survive a restart, so persisted
|
|
99
|
+
* "working" or "error" rows can never be accurate after startup. Returns
|
|
100
|
+
* the number of rows reset for logging.
|
|
101
|
+
*/
|
|
102
|
+
export function reconcileAgentStatuses() {
|
|
103
|
+
const info = getDb()
|
|
104
|
+
.prepare("UPDATE squad_agents SET status = 'idle' WHERE status IN ('working', 'error')")
|
|
105
|
+
.run();
|
|
106
|
+
return info.changes;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Mirror of reconcileAgentStatuses for squads themselves.
|
|
110
|
+
*/
|
|
111
|
+
export function reconcileSquadStatuses() {
|
|
112
|
+
const info = getDb()
|
|
113
|
+
.prepare("UPDATE squads SET status = 'idle' WHERE status IN ('working', 'error')")
|
|
114
|
+
.run();
|
|
115
|
+
return info.changes;
|
|
116
|
+
}
|
|
96
117
|
export function logDecision(squadSlug, decision, context) {
|
|
97
118
|
getDb()
|
|
98
119
|
.prepare("INSERT INTO squad_decisions (squad_slug, decision, context) VALUES (?, ?, ?)")
|