heyio 0.42.1 → 1.0.1

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.
Files changed (100) hide show
  1. package/README.md +40 -52
  2. package/dist/api/auth.js +35 -38
  3. package/dist/api/server.js +157 -1134
  4. package/dist/config.js +49 -32
  5. package/dist/copilot/agents.js +72 -1055
  6. package/dist/copilot/client.js +6 -17
  7. package/dist/copilot/io-scheduler.js +55 -139
  8. package/dist/copilot/model-router.js +100 -72
  9. package/dist/copilot/orchestrator.js +91 -515
  10. package/dist/copilot/scheduler.js +67 -189
  11. package/dist/copilot/skills.js +41 -366
  12. package/dist/copilot/system-message.js +40 -200
  13. package/dist/copilot/tools.js +191 -2042
  14. package/dist/daemon.js +54 -201
  15. package/dist/index.js +15 -133
  16. package/dist/mcp/config.js +23 -31
  17. package/dist/mcp/index.js +2 -3
  18. package/dist/mcp/registry.js +33 -88
  19. package/dist/notify.js +18 -100
  20. package/dist/paths.js +13 -24
  21. package/dist/setup.js +35 -0
  22. package/dist/store/db.js +111 -297
  23. package/dist/store/feed.js +29 -97
  24. package/dist/store/instances.js +56 -121
  25. package/dist/store/schedules.js +21 -73
  26. package/dist/store/squads.js +35 -186
  27. package/dist/store/tasks.js +25 -168
  28. package/dist/telegram/bot.js +20 -312
  29. package/dist/telegram/handlers.js +39 -3
  30. package/dist/watchdog.js +31 -45
  31. package/dist/wiki/fs.js +38 -155
  32. package/dist/wiki/search.js +31 -44
  33. package/package.json +5 -8
  34. package/web-dist/assets/ChatView-EFFiln1H.js +11 -0
  35. package/web-dist/assets/FeedView-bN4NMOL7.js +6 -0
  36. package/web-dist/assets/LoginView-CNtasq3n.js +1 -0
  37. package/web-dist/assets/McpView-C2CHiwsi.js +1 -0
  38. package/web-dist/assets/SchedulesView-CyilLban.js +1 -0
  39. package/web-dist/assets/SettingsView-1wLXKEF4.js +1 -0
  40. package/web-dist/assets/SkillsView-BLsD-0u0.js +1 -0
  41. package/web-dist/assets/SquadDetailView-CsCw2ZLp.js +21 -0
  42. package/web-dist/assets/SquadsView-DQ3vFlyO.js +6 -0
  43. package/web-dist/assets/WikiView-19M3oqnq.js +21 -0
  44. package/web-dist/assets/api-WGvTsXaE.js +1 -0
  45. package/web-dist/assets/index-D7M5O-_l.css +1 -0
  46. package/web-dist/assets/index-DZOS9syn.js +95 -0
  47. package/web-dist/assets/plus-BOvyX1BC.js +6 -0
  48. package/web-dist/assets/trash-2-DHoetkC4.js +6 -0
  49. package/web-dist/favicon.svg +4 -1
  50. package/web-dist/index.html +7 -10
  51. package/dist/api/logout.test.js +0 -128
  52. package/dist/api/mcp.test.js +0 -285
  53. package/dist/api/wiki.test.js +0 -283
  54. package/dist/auth/session-logic.js +0 -79
  55. package/dist/auth/session-logic.test.js +0 -201
  56. package/dist/copilot/auto-complete-instance.test.js +0 -104
  57. package/dist/copilot/cron.js +0 -136
  58. package/dist/copilot/event-summary.js +0 -286
  59. package/dist/copilot/instance-deactivate.test.js +0 -119
  60. package/dist/copilot/model-router.test.js +0 -71
  61. package/dist/copilot/review-backfill.js +0 -57
  62. package/dist/copilot/session-timeout.js +0 -112
  63. package/dist/copilot/session-timeout.test.js +0 -372
  64. package/dist/copilot/skills.test.js +0 -55
  65. package/dist/copilot/universes.js +0 -469
  66. package/dist/instance-watchdog.js +0 -104
  67. package/dist/instance-watchdog.test.js +0 -183
  68. package/dist/mcp/client.js +0 -109
  69. package/dist/mcp/client.test.js +0 -99
  70. package/dist/mcp/config.test.js +0 -49
  71. package/dist/mcp/registry.test.js +0 -79
  72. package/dist/notify.test.js +0 -232
  73. package/dist/store/feed.test.js +0 -279
  74. package/dist/store/instances.test.js +0 -310
  75. package/dist/store/io-schedules.js +0 -63
  76. package/dist/store/notifications.js +0 -79
  77. package/dist/store/notifications.test.js +0 -197
  78. package/dist/store/schedule-runs.js +0 -46
  79. package/dist/store/squads.test.js +0 -405
  80. package/dist/store/tasks.test.js +0 -150
  81. package/dist/store/worktrees.js +0 -83
  82. package/dist/tui/index.js +0 -286
  83. package/dist/update.js +0 -81
  84. package/dist/watchdog.test.js +0 -83
  85. package/dist/wiki/wiki-squad.test.js +0 -54
  86. package/web-dist/assets/AgentActivityView-B1PaNYy8.js +0 -1
  87. package/web-dist/assets/ChatView-BbpWnrtC.js +0 -4
  88. package/web-dist/assets/FeedView-B5LaMV0I.js +0 -1
  89. package/web-dist/assets/InboxView-Cwqt8rH7.js +0 -1
  90. package/web-dist/assets/LoginView-refmPLKT.js +0 -1
  91. package/web-dist/assets/McpView-B1w0dRFY.js +0 -1
  92. package/web-dist/assets/SchedulesView-D9l2DI7X.js +0 -1
  93. package/web-dist/assets/SettingsTabs.vue_vue_type_script_setup_true_lang-DncOVVEB.js +0 -1
  94. package/web-dist/assets/SkillsView-uFX0q1mV.js +0 -1
  95. package/web-dist/assets/SquadsView-B1nZW4ml.js +0 -1
  96. package/web-dist/assets/StatusIndicator.vue_vue_type_script_setup_true_lang-pTrJJwX1.js +0 -1
  97. package/web-dist/assets/WikiView-B54cCKIK.js +0 -1
  98. package/web-dist/assets/index-C0VEUWQ1.js +0 -81
  99. package/web-dist/assets/index-eluTyieM.css +0 -10
  100. package/web-dist/icons.svg +0 -24
@@ -2,17 +2,7 @@ import { CopilotClient } from "@github/copilot-sdk";
2
2
  let client;
3
3
  export async function getClient() {
4
4
  if (!client) {
5
- const opts = {
6
- autoStart: true,
7
- };
8
- // Pass explicit token if available (env vars are also auto-detected by the SDK)
9
- const token = process.env.COPILOT_GITHUB_TOKEN
10
- || process.env.GH_TOKEN
11
- || process.env.GITHUB_TOKEN;
12
- if (token) {
13
- opts.githubToken = token;
14
- }
15
- client = new CopilotClient(opts);
5
+ client = new CopilotClient();
16
6
  await client.start();
17
7
  }
18
8
  return client;
@@ -22,15 +12,14 @@ export async function resetClient() {
22
12
  try {
23
13
  await client.stop();
24
14
  }
25
- catch { /* best-effort */ }
15
+ catch {
16
+ // ignore stop errors during reset
17
+ }
26
18
  client = undefined;
27
19
  }
28
20
  return getClient();
29
21
  }
30
- export async function stopClient() {
31
- if (client) {
32
- await client.stop();
33
- client = undefined;
34
- }
22
+ export function getClientInstance() {
23
+ return client;
35
24
  }
36
25
  //# sourceMappingURL=client.js.map
@@ -1,155 +1,71 @@
1
- // IO-level scheduler fires recurring tasks for IO itself, independent of
2
- // any squad. Mirrors the squad scheduler in shape (TICK_MS loop, in-flight
3
- // guard, reconcile on startup) but dispatches into the orchestrator via
4
- // sendToOrchestrator with a `background` source so IO can handle the prompt
5
- // the same way it handles any other user message.
6
- import { listIoSchedules, listDueIoSchedules, recordIoScheduleRun, setIoScheduleTimestamps, updateIoScheduleNextRun, } from "../store/io-schedules.js";
1
+ import { listSchedules, updateScheduleLastRun } from "../store/schedules.js";
7
2
  import { sendToOrchestrator } from "./orchestrator.js";
8
- import { nextRun } from "./cron.js";
9
- import { notifyBackground } from "../notify.js";
10
- import { startScheduleRun, completeScheduleRun, failScheduleRun } from "../store/schedule-runs.js";
11
- const TICK_MS = 30_000;
12
- let timer;
13
- const inFlight = new Set();
14
- function buildPrompt(schedule) {
15
- const header = `# Scheduled task: ${schedule.name}\n\n_This prompt was fired automatically by the IO scheduler. Cron expression: \`${schedule.cron_expr}\`._`;
16
- const notes = schedule.notes
17
- ? `\n\n**Operator notes:** ${schedule.notes}`
18
- : "";
19
- return `${header}\n\n${schedule.prompt}${notes}`;
3
+ let ioSchedulerInterval;
4
+ export function startIoScheduler() {
5
+ ioSchedulerInterval = setInterval(() => {
6
+ checkIoSchedules();
7
+ }, 60_000);
8
+ ioSchedulerInterval.unref();
20
9
  }
21
- async function fireSchedule(schedule) {
22
- if (inFlight.has(schedule.id))
23
- return;
24
- inFlight.add(schedule.id);
25
- const ranAt = new Date();
26
- let nextIso = null;
27
- try {
28
- nextIso = nextRun(schedule.cron_expr, ranAt).toISOString();
29
- }
30
- catch (err) {
31
- console.error(`[io] io-scheduler: cron parse error for schedule ${schedule.id}:`, err instanceof Error ? err.message : err);
32
- }
33
- recordIoScheduleRun(schedule.id, ranAt, nextIso);
34
- console.log(`[io] io-scheduler: firing schedule "${schedule.name}" (next run: ${nextIso ?? "never"})`);
35
- const run = startScheduleRun({
36
- schedule_type: "io",
37
- schedule_id: schedule.id,
38
- schedule_name: schedule.name,
39
- });
40
- try {
41
- let buffer = "";
42
- await sendToOrchestrator(buildPrompt(schedule), { type: "background" }, (text, done) => {
43
- buffer += text;
10
+ function checkIoSchedules() {
11
+ const schedules = listSchedules("io");
12
+ const now = new Date();
13
+ for (const schedule of schedules) {
14
+ if (!schedule.enabled)
15
+ continue;
16
+ if (!isDue(schedule.cron, schedule.last_run, now))
17
+ continue;
18
+ updateScheduleLastRun(schedule.id);
19
+ sendToOrchestrator(schedule.prompt, "io-scheduler", (_text, done) => {
44
20
  if (done) {
45
- void notifyBackground({
46
- source: {
47
- type: "io-schedule",
48
- scheduleId: schedule.id,
49
- scheduleName: schedule.name,
50
- },
51
- title: `IO schedule: ${schedule.name}`,
52
- text: buffer.trim(),
53
- }).then((notifyResult) => {
54
- completeScheduleRun(run.id, notifyResult.id);
55
- }).catch((err) => {
56
- failScheduleRun(run.id, err instanceof Error ? err.message : String(err));
57
- });
21
+ console.log(`[io-scheduler] Schedule ${schedule.id} completed.`);
58
22
  }
59
23
  });
60
24
  }
61
- catch (err) {
62
- failScheduleRun(run.id, err instanceof Error ? err.message : String(err));
63
- console.error(`[io] io-scheduler: failed to dispatch schedule ${schedule.id}:`, err instanceof Error ? err.message : err);
64
- }
65
- finally {
66
- inFlight.delete(schedule.id);
67
- }
68
25
  }
69
- async function tick() {
70
- let due;
71
- try {
72
- due = listDueIoSchedules(new Date());
26
+ function isDue(cron, lastRun, now) {
27
+ const parts = cron.split(" ");
28
+ if (parts.length !== 5)
29
+ return false;
30
+ const [minSpec, hourSpec, daySpec, monthSpec, weekdaySpec] = parts;
31
+ if (!matchesCronField(minSpec, now.getMinutes()))
32
+ return false;
33
+ if (!matchesCronField(hourSpec, now.getHours()))
34
+ return false;
35
+ if (!matchesCronField(daySpec, now.getDate()))
36
+ return false;
37
+ if (!matchesCronField(monthSpec, now.getMonth() + 1))
38
+ return false;
39
+ if (!matchesCronField(weekdaySpec, now.getDay()))
40
+ return false;
41
+ if (lastRun) {
42
+ const lastDate = new Date(lastRun);
43
+ const diffMs = now.getTime() - lastDate.getTime();
44
+ if (diffMs < 60_000)
45
+ return false;
73
46
  }
74
- catch (err) {
75
- console.error("[io] io-scheduler tick failed:", err instanceof Error ? err.message : err);
76
- return;
47
+ return true;
48
+ }
49
+ function matchesCronField(spec, value) {
50
+ if (spec === "*")
51
+ return true;
52
+ if (spec.includes("-")) {
53
+ const [start, end] = spec.split("-").map(Number);
54
+ return value >= start && value <= end;
77
55
  }
78
- for (const s of due) {
79
- await fireSchedule(s);
56
+ if (spec.includes(",")) {
57
+ return spec.split(",").map(Number).includes(value);
80
58
  }
81
- }
82
- /**
83
- * Backfill next_run_at for any IO schedules that are NULL or stale. We
84
- * advance to the next future occurrence rather than replaying missed runs
85
- * — same semantics as the squad scheduler.
86
- */
87
- export function reconcileIoSchedules(now = new Date()) {
88
- for (const s of listIoSchedules()) {
89
- if (!s.enabled)
90
- continue;
91
- let needsUpdate = false;
92
- if (!s.next_run_at) {
93
- needsUpdate = true;
94
- }
95
- else {
96
- const next = new Date(s.next_run_at);
97
- if (Number.isNaN(next.getTime()) || next <= now)
98
- needsUpdate = true;
99
- }
100
- if (!needsUpdate)
101
- continue;
102
- try {
103
- const next = nextRun(s.cron_expr, now);
104
- updateIoScheduleNextRun(s.id, next.toISOString());
105
- }
106
- catch (err) {
107
- console.error(`[io] io-scheduler: invalid cron "${s.cron_expr}" on schedule ${s.id}; clearing next_run_at:`, err instanceof Error ? err.message : err);
108
- updateIoScheduleNextRun(s.id, null);
109
- }
59
+ if (spec.startsWith("*/")) {
60
+ const step = parseInt(spec.slice(2), 10);
61
+ return value % step === 0;
110
62
  }
111
- }
112
- export function startIoScheduler() {
113
- if (timer)
114
- return;
115
- reconcileIoSchedules();
116
- timer = setInterval(() => {
117
- void tick();
118
- }, TICK_MS);
119
- // Don't keep the event loop alive on shutdown
120
- timer.unref?.();
63
+ return parseInt(spec, 10) === value;
121
64
  }
122
65
  export function stopIoScheduler() {
123
- if (timer) {
124
- clearInterval(timer);
125
- timer = undefined;
126
- }
127
- }
128
- /**
129
- * Force a schedule to run immediately. Used by the `schedule_run_now` tool.
130
- *
131
- * The regular tick path (`fireSchedule`) advances `last_run_at` and
132
- * `next_run_at` as a side effect, which is correct for an automatic firing
133
- * but is the wrong behaviour for a manual one — a user testing a schedule at
134
- * 04:30 should not have the 05:00 occurrence skipped or the schedule shifted.
135
- * We therefore snapshot both timestamps before firing and restore them after,
136
- * leaving the persisted schedule untouched.
137
- */
138
- export async function runIoScheduleNow(id) {
139
- const all = listIoSchedules();
140
- const s = all.find((x) => x.id === id);
141
- if (!s)
142
- return false;
143
- const previousLast = s.last_run_at;
144
- const previousNext = s.next_run_at;
145
- try {
146
- await fireSchedule(s);
147
- }
148
- finally {
149
- // Restore the original timestamps even if fireSchedule threw, so a
150
- // failed manual run cannot silently shift the schedule either.
151
- setIoScheduleTimestamps(id, previousLast, previousNext);
66
+ if (ioSchedulerInterval) {
67
+ clearInterval(ioSchedulerInterval);
68
+ ioSchedulerInterval = undefined;
152
69
  }
153
- return true;
154
70
  }
155
71
  //# sourceMappingURL=io-scheduler.js.map
@@ -1,84 +1,112 @@
1
- import { config } from "../config.js";
2
- const DEFAULT_TIERS = {
3
- high: ["claude-opus-4.7", "claude-opus-4.6"],
4
- medium: ["claude-sonnet-4.6", "gpt-5.5", "claude-opus-4.5"],
5
- low: ["claude-haiku-4.5", "gpt-5.4-mini"],
1
+ import { loadConfig } from "../config.js";
2
+ import { getClient } from "./client.js";
3
+ // Cache discovered models so we don't call listModels() on every task
4
+ let discoveredModels;
5
+ /**
6
+ * Built-in model capability hints. Used as fallback when billing info
7
+ * isn't available from the SDK. Higher = more capable.
8
+ */
9
+ const MODEL_CAPABILITY_HINTS = {
10
+ "claude-opus-4.7": 90,
11
+ "claude-opus-4.6": 88,
12
+ "claude-opus-4.5": 85,
13
+ "gpt-5.5": 87,
14
+ "gpt-5.4": 84,
15
+ "gpt-5.3-codex": 83,
16
+ "gpt-5.2-codex": 82,
17
+ "gpt-5.2": 80,
18
+ "claude-sonnet-4.6": 70,
19
+ "claude-sonnet-4.5": 68,
20
+ "gpt-4.1": 65,
21
+ "claude-haiku-4.5": 40,
22
+ "gpt-5.4-mini": 42,
23
+ "gpt-5-mini": 38,
6
24
  };
7
- // Resolved model for each tier (populated at startup)
8
- let resolvedTiers = null;
9
- const HIGH_KEYWORDS = [
10
- "architect", "refactor", "redesign", "debug", "design",
11
- "complex", "migration", "security", "performance", "optimize",
12
- "rewrite", "overhaul", "investigate", "diagnose", "plan",
13
- ];
14
- const LOW_KEYWORDS = [
15
- "read", "list", "format", "lookup", "check", "status",
16
- "simple", "rename", "typo", "log", "print", "echo",
17
- "delete file", "remove file", "copy file", "move file",
18
- ];
19
25
  /**
20
- * Resolve each tier to the first available model from its preference list.
21
- * Call once at startup with the result of `client.listModels()`.
26
+ * Discover available models from the Copilot SDK and score them.
27
+ * Uses billing multiplier as primary capability signal, falls back to
28
+ * built-in hints for unknown models.
22
29
  */
23
- export function resolveModelTiers(availableModelIds) {
24
- const available = new Set(availableModelIds);
25
- const tiers = config.modelTiers ?? {};
26
- const result = {};
27
- for (const tier of ["high", "medium", "low"]) {
28
- const candidates = tiers[tier] ?? DEFAULT_TIERS[tier];
29
- const match = candidates.find((m) => available.has(m));
30
- if (match) {
31
- result[tier] = match;
32
- }
33
- else {
34
- // Fallback: use defaultModel, then first candidate regardless of availability
35
- result[tier] = config.defaultModel || candidates[0];
36
- }
37
- console.error(`[io] Model tier "${tier}" resolved to: ${result[tier]}`);
30
+ export async function discoverModels() {
31
+ if (discoveredModels)
32
+ return discoveredModels;
33
+ try {
34
+ const client = await getClient();
35
+ const models = await client.listModels();
36
+ discoveredModels = models
37
+ .filter((m) => !m.policy || m.policy.state === "enabled")
38
+ .map((m) => ({
39
+ id: m.id,
40
+ // Use billing multiplier as capability proxy (higher cost = more capable)
41
+ // Fall back to built-in hints, then default of 50
42
+ score: m.billing
43
+ ? Math.min(m.billing.multiplier * 10, 100)
44
+ : (MODEL_CAPABILITY_HINTS[m.id] ?? 50),
45
+ }))
46
+ .sort((a, b) => b.score - a.score);
38
47
  }
39
- resolvedTiers = result;
40
- return result;
48
+ catch {
49
+ // SDK discovery failed — fall back to defaultModel only
50
+ const config = loadConfig();
51
+ discoveredModels = [{ id: config.defaultModel, score: 65 }];
52
+ }
53
+ return discoveredModels;
41
54
  }
42
- /**
43
- * Classify a task description into a complexity tier.
44
- */
45
- export function classifyComplexity(taskDescription) {
46
- const lower = taskDescription.toLowerCase();
47
- const highScore = HIGH_KEYWORDS.filter((kw) => lower.includes(kw)).length;
48
- const lowScore = LOW_KEYWORDS.filter((kw) => lower.includes(kw)).length;
49
- // Strong signals
50
- if (highScore >= 2)
51
- return "high";
52
- if (lowScore >= 2)
53
- return "low";
54
- // Weak signals
55
- if (highScore > lowScore)
56
- return "high";
57
- if (lowScore > highScore)
58
- return "low";
59
- // Default to medium for ambiguous tasks
60
- return "medium";
55
+ /** Reset cached models (e.g. after client reconnect) */
56
+ export function resetModelCache() {
57
+ discoveredModels = undefined;
61
58
  }
62
59
  /**
63
- * Get the resolved model for a tier.
60
+ * Select the best available model for a given task complexity.
61
+ * Discovers models from the Copilot SDK and picks based on capability —
62
+ * zero configuration required.
64
63
  */
65
- export function getModelForTier(tier) {
66
- if (!resolvedTiers) {
67
- console.error("[io] Warning: model tiers not yet resolved, using defaults");
68
- return config.defaultModel || DEFAULT_TIERS[tier][0];
64
+ export async function selectModel(complexity) {
65
+ const config = loadConfig();
66
+ const scored = await discoverModels();
67
+ if (scored.length === 0)
68
+ return config.defaultModel;
69
+ if (complexity === "high") {
70
+ // Most capable model
71
+ return scored[0].id;
69
72
  }
70
- return resolvedTiers[tier];
73
+ if (complexity === "low") {
74
+ // Cheapest model
75
+ return scored[scored.length - 1].id;
76
+ }
77
+ // Medium — pick a model around the middle of the ranked list
78
+ const midIdx = Math.floor(scored.length / 2);
79
+ return scored[midIdx].id;
71
80
  }
72
- /**
73
- * Get the best model for a task based on its description.
74
- * If squadModel is provided, it always takes priority.
75
- */
76
- export function getModelForTask(taskDescription, squadModel) {
77
- if (squadModel)
78
- return squadModel;
79
- const tier = classifyComplexity(taskDescription);
80
- const model = getModelForTier(tier);
81
- console.error(`[io] Task classified as "${tier}" → model: ${model}`);
82
- return model;
81
+ export function classifyComplexity(task) {
82
+ const lower = task.toLowerCase();
83
+ const highPatterns = [
84
+ "architect",
85
+ "design system",
86
+ "refactor",
87
+ "security audit",
88
+ "performance optimization",
89
+ "migration",
90
+ "complex",
91
+ "deep analysis",
92
+ "debug",
93
+ "race condition",
94
+ ];
95
+ if (highPatterns.some((p) => lower.includes(p)))
96
+ return "high";
97
+ const lowPatterns = [
98
+ "format",
99
+ "rename",
100
+ "typo",
101
+ "simple",
102
+ "lookup",
103
+ "list",
104
+ "read",
105
+ "status",
106
+ "check",
107
+ ];
108
+ if (lowPatterns.some((p) => lower.includes(p)))
109
+ return "low";
110
+ return "medium";
83
111
  }
84
112
  //# sourceMappingURL=model-router.js.map