heyio 3.0.2 → 3.0.4

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 (65) hide show
  1. package/dist/api/server.js +1 -1
  2. package/dist/api/server.js.map +1 -1
  3. package/dist/index.js +8 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/logging/logger.d.ts.map +1 -1
  6. package/dist/logging/logger.js +13 -1
  7. package/dist/logging/logger.js.map +1 -1
  8. package/node_modules/@io/shared/package.json +1 -1
  9. package/package.json +7 -2
  10. package/public/assets/index-2RY89H3W.js +336 -0
  11. package/public/assets/index-2RY89H3W.js.map +1 -0
  12. package/public/assets/index-D3cGfBsj.css +1 -0
  13. package/public/index.html +14 -0
  14. package/src/api/middleware/auth.ts +0 -76
  15. package/src/api/notifications.ts +0 -122
  16. package/src/api/routes/activity.ts +0 -29
  17. package/src/api/routes/attachments.ts +0 -93
  18. package/src/api/routes/config.ts +0 -115
  19. package/src/api/routes/conversations.ts +0 -87
  20. package/src/api/routes/health.ts +0 -18
  21. package/src/api/routes/inbox.ts +0 -98
  22. package/src/api/routes/schedules.ts +0 -121
  23. package/src/api/routes/skills.ts +0 -105
  24. package/src/api/routes/squads.ts +0 -145
  25. package/src/api/routes/usage.ts +0 -57
  26. package/src/api/routes/wiki.ts +0 -49
  27. package/src/api/server.ts +0 -186
  28. package/src/config.ts +0 -3
  29. package/src/copilot/client.ts +0 -42
  30. package/src/copilot/health-monitor.ts +0 -85
  31. package/src/copilot/orchestrator.ts +0 -222
  32. package/src/copilot/tools.ts +0 -707
  33. package/src/index.ts +0 -113
  34. package/src/logging/logger.ts +0 -26
  35. package/src/models/index.ts +0 -11
  36. package/src/models/pricing.ts +0 -121
  37. package/src/models/registry.ts +0 -131
  38. package/src/models/token-tracker.ts +0 -151
  39. package/src/scheduler/engine.ts +0 -146
  40. package/src/skills/index.ts +0 -13
  41. package/src/skills/store.ts +0 -188
  42. package/src/squad/agent.ts +0 -326
  43. package/src/squad/autonomy.ts +0 -78
  44. package/src/squad/event-bus.ts +0 -71
  45. package/src/squad/execution/index.ts +0 -17
  46. package/src/squad/execution/instance.ts +0 -186
  47. package/src/squad/execution/meeting.ts +0 -191
  48. package/src/squad/execution/pr.ts +0 -127
  49. package/src/squad/execution/runner.ts +0 -97
  50. package/src/squad/execution/tasks.ts +0 -111
  51. package/src/squad/execution/worktree.ts +0 -138
  52. package/src/squad/hiring.ts +0 -222
  53. package/src/squad/index.ts +0 -17
  54. package/src/squad/manager.ts +0 -337
  55. package/src/squad/name-generator.ts +0 -135
  56. package/src/squad/roles/templates.ts +0 -104
  57. package/src/squad/skill-parser.ts +0 -120
  58. package/src/squad/source-resolver.ts +0 -57
  59. package/src/store/activity.ts +0 -176
  60. package/src/store/db.ts +0 -237
  61. package/src/store/inbox.ts +0 -199
  62. package/src/store/schedules.ts +0 -199
  63. package/src/wiki/index.ts +0 -12
  64. package/src/wiki/store.ts +0 -139
  65. package/tsconfig.json +0 -9
@@ -1,13 +0,0 @@
1
- export {
2
- initSkills,
3
- listInstalledSkills,
4
- getSkill,
5
- installSkill,
6
- installSkillFromUrl,
7
- removeSkill,
8
- activateSkill,
9
- deactivateSkill,
10
- getActiveSkills,
11
- getActiveSkillsContent,
12
- } from './store.js';
13
- export type { Skill, SkillActivation } from './store.js';
@@ -1,188 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { createChildLogger } from '../logging/logger.js';
4
- import { getDatabase } from '../store/db.js';
5
-
6
- const logger = () => createChildLogger('skills');
7
-
8
- export interface Skill {
9
- name: string;
10
- content: string;
11
- filePath: string;
12
- }
13
-
14
- export interface SkillActivation {
15
- skillName: string;
16
- targetType: 'orchestrator' | 'squad';
17
- targetId: string | null;
18
- activatedAt: string;
19
- }
20
-
21
- let skillsRoot = '';
22
-
23
- /**
24
- * Initialize the skills directory.
25
- */
26
- export function initSkills(dataDir: string): void {
27
- skillsRoot = join(dataDir, 'skills');
28
- mkdirSync(skillsRoot, { recursive: true });
29
- logger().info({ skillsRoot }, 'Skills directory initialized');
30
- }
31
-
32
- /**
33
- * List all installed skills.
34
- */
35
- export function listInstalledSkills(): Skill[] {
36
- if (!existsSync(skillsRoot)) return [];
37
-
38
- const dirs = readdirSync(skillsRoot, { withFileTypes: true }).filter((d) => d.isDirectory());
39
-
40
- const skills: Skill[] = [];
41
- for (const dir of dirs) {
42
- const skillPath = join(skillsRoot, dir.name, 'SKILL.md');
43
- if (existsSync(skillPath)) {
44
- skills.push({
45
- name: dir.name,
46
- content: readFileSync(skillPath, 'utf-8'),
47
- filePath: skillPath,
48
- });
49
- }
50
- }
51
- return skills;
52
- }
53
-
54
- /**
55
- * Read a specific skill by name.
56
- */
57
- export function getSkill(name: string): Skill | null {
58
- const skillPath = join(skillsRoot, name, 'SKILL.md');
59
- if (!existsSync(skillPath)) return null;
60
- return {
61
- name,
62
- content: readFileSync(skillPath, 'utf-8'),
63
- filePath: skillPath,
64
- };
65
- }
66
-
67
- /**
68
- * Install a skill from content (e.g., fetched from URL or provided directly).
69
- */
70
- export function installSkill(name: string, content: string): Skill {
71
- const skillDir = join(skillsRoot, name);
72
- mkdirSync(skillDir, { recursive: true });
73
- const skillPath = join(skillDir, 'SKILL.md');
74
- writeFileSync(skillPath, content, 'utf-8');
75
- logger().info({ name }, 'Skill installed');
76
- return { name, content, filePath: skillPath };
77
- }
78
-
79
- /**
80
- * Install a skill from a URL (fetches raw content).
81
- */
82
- export async function installSkillFromUrl(name: string, url: string): Promise<Skill> {
83
- const res = await fetch(url);
84
- if (!res.ok) {
85
- throw new Error(`Failed to fetch skill from ${url}: ${res.status} ${res.statusText}`);
86
- }
87
- const content = await res.text();
88
- return installSkill(name, content);
89
- }
90
-
91
- /**
92
- * Remove an installed skill.
93
- */
94
- export function removeSkill(name: string): void {
95
- const skillDir = join(skillsRoot, name);
96
- if (existsSync(skillDir)) {
97
- rmSync(skillDir, { recursive: true });
98
- logger().info({ name }, 'Skill removed');
99
- }
100
- // Also remove all activations
101
- deactivateSkillAll(name);
102
- }
103
-
104
- /**
105
- * Activate a skill for a target (orchestrator or a specific squad).
106
- */
107
- export async function activateSkill(
108
- skillName: string,
109
- targetType: 'orchestrator' | 'squad',
110
- targetId?: string,
111
- ): Promise<void> {
112
- const db = await getDatabase();
113
- await db.execute({
114
- sql: `INSERT OR IGNORE INTO skill_activations (skill_name, target_type, target_id, activated_at)
115
- VALUES (?, ?, ?, datetime('now'))`,
116
- args: [skillName, targetType, targetId ?? null],
117
- });
118
- logger().info({ skillName, targetType, targetId }, 'Skill activated');
119
- }
120
-
121
- /**
122
- * Deactivate a skill for a target.
123
- */
124
- export async function deactivateSkill(
125
- skillName: string,
126
- targetType: 'orchestrator' | 'squad',
127
- targetId?: string,
128
- ): Promise<void> {
129
- const db = await getDatabase();
130
- await db.execute({
131
- sql: 'DELETE FROM skill_activations WHERE skill_name = ? AND target_type = ? AND (target_id = ? OR (target_id IS NULL AND ? IS NULL))',
132
- args: [skillName, targetType, targetId ?? null, targetId ?? null],
133
- });
134
- logger().info({ skillName, targetType, targetId }, 'Skill deactivated');
135
- }
136
-
137
- /**
138
- * Remove all activations for a skill.
139
- */
140
- async function deactivateSkillAll(skillName: string): Promise<void> {
141
- const db = await getDatabase();
142
- await db.execute({
143
- sql: 'DELETE FROM skill_activations WHERE skill_name = ?',
144
- args: [skillName],
145
- });
146
- }
147
-
148
- /**
149
- * Get all activations for a target.
150
- */
151
- export async function getActiveSkills(
152
- targetType: 'orchestrator' | 'squad',
153
- targetId?: string,
154
- ): Promise<SkillActivation[]> {
155
- const db = await getDatabase();
156
- const result = await db.execute({
157
- sql: `SELECT skill_name, target_type, target_id, activated_at FROM skill_activations
158
- WHERE target_type = ? AND (target_id = ? OR (target_id IS NULL AND ? IS NULL))`,
159
- args: [targetType, targetId ?? null, targetId ?? null],
160
- });
161
- return result.rows.map((r) => ({
162
- skillName: r.skill_name as string,
163
- targetType: r.target_type as 'orchestrator' | 'squad',
164
- targetId: r.target_id as string | null,
165
- activatedAt: r.activated_at as string,
166
- }));
167
- }
168
-
169
- /**
170
- * Get the combined content of all active skills for a target (for system prompt injection).
171
- */
172
- export async function getActiveSkillsContent(
173
- targetType: 'orchestrator' | 'squad',
174
- targetId?: string,
175
- ): Promise<string> {
176
- const activations = await getActiveSkills(targetType, targetId);
177
- if (activations.length === 0) return '';
178
-
179
- const sections: string[] = [];
180
- for (const activation of activations) {
181
- const skill = getSkill(activation.skillName);
182
- if (skill) {
183
- sections.push(`### Skill: ${skill.name}\n${skill.content}`);
184
- }
185
- }
186
-
187
- return sections.length > 0 ? `\n## Active Skills\n${sections.join('\n\n')}` : '';
188
- }
@@ -1,326 +0,0 @@
1
- import { type CopilotSession, approveAll, defineTool } from '@github/copilot-sdk';
2
- import type { AgentEvent, AgentStatus } from '@io/shared';
3
- import { z } from 'zod';
4
- import { getClient } from '../copilot/client.js';
5
- import { createChildLogger } from '../logging/logger.js';
6
- import {
7
- getSquadScopes,
8
- listWikiPages,
9
- readWikiPage,
10
- searchWiki,
11
- writeWikiPage,
12
- } from '../wiki/index.js';
13
- import { getEventBus } from './event-bus.js';
14
- import { type SkillDefinition, compileSystemPrompt } from './skill-parser.js';
15
-
16
- export interface AgentConfig {
17
- skill: SkillDefinition;
18
- squadId: string;
19
- squadName: string;
20
- instanceId?: string;
21
- model?: string;
22
- identity?: { displayName: string; persona?: string; universe?: string };
23
- }
24
-
25
- export interface AgentMessage {
26
- role: 'user' | 'system';
27
- content: string;
28
- }
29
-
30
- type Session = Awaited<ReturnType<Awaited<ReturnType<typeof getClient>>['createSession']>>;
31
-
32
- /**
33
- * Base agent class. Each agent has its own Copilot session, enforces
34
- * tool allowlists from its SKILL.md, and emits events on the bus.
35
- */
36
- export class Agent {
37
- readonly role: string;
38
- readonly squadId: string;
39
- readonly squadName: string;
40
- readonly instanceId?: string;
41
- private session: Session | null = null;
42
- private skill: SkillDefinition;
43
- private model: string;
44
- private identity?: { displayName: string; persona?: string; universe?: string };
45
- private logger;
46
- private _status: AgentStatus = 'idle';
47
-
48
- constructor(config: AgentConfig) {
49
- this.skill = config.skill;
50
- this.role = config.skill.role;
51
- this.squadId = config.squadId;
52
- this.squadName = config.squadName;
53
- this.instanceId = config.instanceId;
54
- this.model = config.model ?? 'claude-opus-4.6';
55
- this.identity = config.identity;
56
- this.logger = createChildLogger(
57
- `agent:${config.squadName}:${config.identity?.displayName ?? this.role}`,
58
- );
59
- }
60
-
61
- get status(): AgentStatus {
62
- return this._status;
63
- }
64
-
65
- /** Initialize the agent's Copilot session */
66
- async init(squadContext?: string): Promise<void> {
67
- const client = await getClient();
68
- const systemPrompt = await compileSystemPrompt(
69
- this.skill,
70
- squadContext,
71
- this.squadName,
72
- this.squadId,
73
- this.identity,
74
- );
75
-
76
- this.session = await client.createSession({
77
- model: this.model,
78
- systemMessage: { mode: 'replace', content: systemPrompt },
79
- onPermissionRequest: approveAll,
80
- tools: this.buildTools(),
81
- });
82
-
83
- this.logger.info('Agent session initialized');
84
- }
85
-
86
- /** Send a message and get a response */
87
- async send(content: string): Promise<string> {
88
- if (!this.session) {
89
- throw new Error(`Agent ${this.role} not initialized`);
90
- }
91
-
92
- this._status = 'working';
93
- this.emitEvent('agent:task_started', { content: content.slice(0, 100) });
94
-
95
- try {
96
- const result = await this.session.sendAndWait({ prompt: content }, 300_000);
97
- const response = result?.data?.content ?? '';
98
- this._status = 'idle';
99
- this.emitEvent('agent:task_completed', { responseLength: response.length });
100
- return response;
101
- } catch (err) {
102
- this._status = 'error';
103
- this.logger.error({ err }, 'Agent send failed');
104
- this.emitEvent('agent:error', {
105
- error: err instanceof Error ? err.message : String(err),
106
- });
107
- throw err;
108
- }
109
- }
110
-
111
- /** Destroy the agent's session */
112
- async destroy(): Promise<void> {
113
- if (this.session) {
114
- await this.session.disconnect();
115
- this.session = null;
116
- }
117
- this.logger.info('Agent destroyed');
118
- }
119
-
120
- /** Build allowed tools for this agent based on SKILL.md */
121
- private buildTools() {
122
- const tools = [];
123
-
124
- // Always allow a "report" tool so agents can communicate results
125
- tools.push(
126
- defineTool('report_to_team_lead', {
127
- description: 'Report findings or completed work back to the team lead.',
128
- parameters: z.object({
129
- summary: z.string().describe('Summary of work completed or findings'),
130
- status: z
131
- .enum(['done', 'blocked', 'needs_review'])
132
- .describe('Current status of the work'),
133
- }),
134
- handler: async (args: { summary: string; status: string }) => {
135
- this.logger.info({ status: args.status }, 'Agent reported to team lead');
136
- return {
137
- textResultForLlm: JSON.stringify({
138
- acknowledged: true,
139
- message: 'Report received by team lead.',
140
- }),
141
- resultType: 'success' as const,
142
- };
143
- },
144
- }),
145
- );
146
-
147
- // Add role-specific tools based on allowlist
148
- if (this.skill.tools.includes('read_file')) {
149
- tools.push(
150
- defineTool('read_file', {
151
- description: 'Read the contents of a file in the project.',
152
- parameters: z.object({
153
- path: z.string().describe('Relative path to the file'),
154
- }),
155
- handler: async (args: { path: string }) => {
156
- // TODO: Implement actual file reading with path sandboxing
157
- return {
158
- textResultForLlm: `[read_file] Would read: ${args.path}`,
159
- resultType: 'success' as const,
160
- };
161
- },
162
- }),
163
- );
164
- }
165
-
166
- if (this.skill.tools.includes('edit_file')) {
167
- tools.push(
168
- defineTool('edit_file', {
169
- description: 'Edit a file in the project. Provide the full new content.',
170
- parameters: z.object({
171
- path: z.string().describe('Relative path to the file'),
172
- content: z.string().describe('New file content'),
173
- }),
174
- handler: async (args: { path: string; content: string }) => {
175
- // TODO: Implement actual file editing with path sandboxing
176
- return {
177
- textResultForLlm: `[edit_file] Would write ${args.content.length} chars to: ${args.path}`,
178
- resultType: 'success' as const,
179
- };
180
- },
181
- }),
182
- );
183
- }
184
-
185
- if (this.skill.tools.includes('run_command')) {
186
- tools.push(
187
- defineTool('run_command', {
188
- description: 'Run a shell command in the project directory.',
189
- parameters: z.object({
190
- command: z.string().describe('The command to execute'),
191
- }),
192
- handler: async (args: { command: string }) => {
193
- // TODO: Implement actual command execution with sandboxing
194
- return {
195
- textResultForLlm: `[run_command] Would run: ${args.command}`,
196
- resultType: 'success' as const,
197
- };
198
- },
199
- }),
200
- );
201
- }
202
-
203
- if (this.skill.tools.includes('search_code')) {
204
- tools.push(
205
- defineTool('search_code', {
206
- description: 'Search for patterns in the project codebase.',
207
- parameters: z.object({
208
- pattern: z.string().describe('Search pattern (regex or text)'),
209
- glob: z.string().optional().describe('File glob to limit search'),
210
- }),
211
- handler: async (args: { pattern: string; glob?: string }) => {
212
- // TODO: Implement actual code search
213
- return {
214
- textResultForLlm: `[search_code] Would search for: ${args.pattern}`,
215
- resultType: 'success' as const,
216
- };
217
- },
218
- }),
219
- );
220
- }
221
-
222
- // Wiki tools — always available to all agents
223
- tools.push(
224
- defineTool('read_wiki', {
225
- description:
226
- 'Read from the wiki knowledge base. Call with no pageName to list available pages, or with a pageName to read a specific page. You can access Shared wiki and your squad wiki.',
227
- parameters: z.object({
228
- scope: z
229
- .enum(['shared', 'squad'])
230
- .describe('"shared" for cross-squad knowledge, "squad" for this squad\'s knowledge'),
231
- pageName: z.string().optional().describe('Page name to read (omit to list all pages)'),
232
- }),
233
- handler: async (args: { scope: 'shared' | 'squad'; pageName?: string }) => {
234
- const resolvedScope = args.scope === 'squad' ? this.squadName : 'shared';
235
- if (!args.pageName) {
236
- const pages = listWikiPages(resolvedScope);
237
- return {
238
- textResultForLlm: JSON.stringify({
239
- scope: args.scope,
240
- pages: pages.length > 0 ? pages : '(empty)',
241
- }),
242
- resultType: 'success' as const,
243
- };
244
- }
245
- const page = readWikiPage(resolvedScope, args.pageName);
246
- if (!page) {
247
- return {
248
- textResultForLlm: JSON.stringify({
249
- error: `Page '${args.pageName}' not found in ${args.scope} wiki`,
250
- }),
251
- resultType: 'success' as const,
252
- };
253
- }
254
- return {
255
- textResultForLlm: JSON.stringify({
256
- scope: args.scope,
257
- name: page.name,
258
- content: page.content,
259
- }),
260
- resultType: 'success' as const,
261
- };
262
- },
263
- }),
264
- defineTool('write_wiki', {
265
- description:
266
- "Write a page to your squad's wiki knowledge base. Read the existing page first and merge knowledge if updating. You can only write to your squad wiki (not shared).",
267
- parameters: z.object({
268
- pageName: z
269
- .string()
270
- .describe('Page name (no .md extension, e.g., "architecture" or "conventions")'),
271
- content: z.string().describe('Full markdown content for the page'),
272
- }),
273
- handler: async (args: { pageName: string; content: string }) => {
274
- writeWikiPage(this.squadName, args.pageName, args.content);
275
- return {
276
- textResultForLlm: JSON.stringify({
277
- written: true,
278
- scope: 'squad',
279
- pageName: args.pageName,
280
- }),
281
- resultType: 'success' as const,
282
- };
283
- },
284
- }),
285
- defineTool('search_wiki', {
286
- description:
287
- 'Search across all accessible wiki pages by keyword. Searches both Shared and your squad wiki.',
288
- parameters: z.object({
289
- keyword: z.string().describe('Keyword or phrase to search for'),
290
- }),
291
- handler: async (args: { keyword: string }) => {
292
- const results = searchWiki(args.keyword, getSquadScopes(this.squadName));
293
- if (results.length === 0) {
294
- return {
295
- textResultForLlm: JSON.stringify({ results: [], message: 'No matches found.' }),
296
- resultType: 'success' as const,
297
- };
298
- }
299
- return {
300
- textResultForLlm: JSON.stringify({ results }),
301
- resultType: 'success' as const,
302
- };
303
- },
304
- }),
305
- );
306
-
307
- return tools;
308
- }
309
-
310
- private emitEvent(type: AgentEvent['type'], data?: Record<string, unknown>) {
311
- getEventBus()
312
- .emit({
313
- id: crypto.randomUUID(),
314
- timestamp: new Date(),
315
- type,
316
- squadId: this.squadId,
317
- instanceId: this.instanceId,
318
- agentRole: this.role,
319
- model: this.model,
320
- data,
321
- })
322
- .catch((err) => {
323
- this.logger.error({ err }, 'Failed to emit agent event');
324
- });
325
- }
326
- }
@@ -1,78 +0,0 @@
1
- import type { AutonomyConfig } from '@io/shared';
2
- import { createChildLogger } from '../logging/logger.js';
3
-
4
- const logger = () => createChildLogger('autonomy');
5
-
6
- export type PermissionAction = 'merge' | 'release' | 'close' | 'review';
7
-
8
- export interface PermissionCheck {
9
- allowed: boolean;
10
- requiresApproval: boolean;
11
- reason?: string;
12
- }
13
-
14
- /**
15
- * Check if a squad has permission to perform an action based on its autonomy tier.
16
- */
17
- export function checkPermission(config: AutonomyConfig, action: PermissionAction): PermissionCheck {
18
- switch (action) {
19
- case 'merge':
20
- if (config.canMergePrs) return { allowed: true, requiresApproval: false };
21
- if (config.requiresUserApprovalFor.includes('merge'))
22
- return {
23
- allowed: false,
24
- requiresApproval: true,
25
- reason: 'User approval required for merging PRs',
26
- };
27
- return { allowed: false, requiresApproval: false, reason: 'Merging PRs not allowed' };
28
-
29
- case 'release':
30
- if (config.canCreateReleases) return { allowed: true, requiresApproval: false };
31
- if (config.requiresUserApprovalFor.includes('release'))
32
- return {
33
- allowed: false,
34
- requiresApproval: true,
35
- reason: 'User approval required for releases',
36
- };
37
- return { allowed: false, requiresApproval: false, reason: 'Creating releases not allowed' };
38
-
39
- case 'close':
40
- if (config.canCloseIssues) return { allowed: true, requiresApproval: false };
41
- if (config.requiresUserApprovalFor.includes('close'))
42
- return {
43
- allowed: false,
44
- requiresApproval: true,
45
- reason: 'User approval required for closing issues',
46
- };
47
- return { allowed: false, requiresApproval: false, reason: 'Closing issues not allowed' };
48
-
49
- case 'review':
50
- if (config.canSelfReview) return { allowed: true, requiresApproval: false };
51
- if (config.requiresUserApprovalFor.includes('review'))
52
- return {
53
- allowed: false,
54
- requiresApproval: true,
55
- reason: 'User approval required for self-review',
56
- };
57
- return { allowed: false, requiresApproval: false, reason: 'Self-review not allowed' };
58
-
59
- default:
60
- return { allowed: false, requiresApproval: false, reason: `Unknown action: ${action}` };
61
- }
62
- }
63
-
64
- /**
65
- * Enforce a permission — logs and throws if not allowed.
66
- */
67
- export function enforcePermission(
68
- config: AutonomyConfig,
69
- action: PermissionAction,
70
- squadName: string,
71
- ): void {
72
- const check = checkPermission(config, action);
73
- if (!check.allowed && !check.requiresApproval) {
74
- const msg = `Squad '${squadName}' is not permitted to ${action}: ${check.reason}`;
75
- logger().warn({ squadName, action }, msg);
76
- throw new Error(msg);
77
- }
78
- }
@@ -1,71 +0,0 @@
1
- import type { IOEvent } from '@io/shared';
2
- import { createChildLogger } from '../logging/logger.js';
3
-
4
- type EventHandler<T extends IOEvent = IOEvent> = (event: T) => void | Promise<void>;
5
-
6
- /**
7
- * Typed pub/sub event bus with error-isolated handlers.
8
- * Handlers that throw are logged but don't crash the bus or other handlers.
9
- */
10
- export class EventBus {
11
- private handlers = new Map<string, Set<EventHandler>>();
12
- private globalHandlers = new Set<EventHandler>();
13
- private logger = createChildLogger('event-bus');
14
-
15
- /** Subscribe to a specific event type */
16
- on<T extends IOEvent>(type: T['type'], handler: EventHandler<T>): () => void {
17
- if (!this.handlers.has(type)) {
18
- this.handlers.set(type, new Set());
19
- }
20
- const set = this.handlers.get(type)!;
21
- set.add(handler as EventHandler);
22
- return () => set.delete(handler as EventHandler);
23
- }
24
-
25
- /** Subscribe to all events */
26
- onAny(handler: EventHandler): () => void {
27
- this.globalHandlers.add(handler);
28
- return () => this.globalHandlers.delete(handler);
29
- }
30
-
31
- /** Emit an event to all matching handlers */
32
- async emit(event: IOEvent): Promise<void> {
33
- const typeHandlers = this.handlers.get(event.type) ?? new Set();
34
- const allHandlers = [...typeHandlers, ...this.globalHandlers];
35
-
36
- const results = await Promise.allSettled(
37
- allHandlers.map((handler) => {
38
- try {
39
- return Promise.resolve(handler(event));
40
- } catch (err) {
41
- return Promise.reject(err);
42
- }
43
- }),
44
- );
45
-
46
- for (const result of results) {
47
- if (result.status === 'rejected') {
48
- this.logger.error(
49
- { err: result.reason, eventType: event.type },
50
- 'Event handler threw an error',
51
- );
52
- }
53
- }
54
- }
55
-
56
- /** Remove all handlers */
57
- clear(): void {
58
- this.handlers.clear();
59
- this.globalHandlers.clear();
60
- }
61
- }
62
-
63
- // Singleton instance
64
- let busInstance: EventBus | null = null;
65
-
66
- export function getEventBus(): EventBus {
67
- if (!busInstance) {
68
- busInstance = new EventBus();
69
- }
70
- return busInstance;
71
- }
@@ -1,17 +0,0 @@
1
- export { createWorktree, removeWorktree, listWorktrees } from './worktree.js';
2
- export type { WorktreeInfo } from './worktree.js';
3
- export {
4
- createInstance,
5
- transitionInstance,
6
- cleanupInstance,
7
- getInstance,
8
- getSquadInstances,
9
- } from './instance.js';
10
- export type { Instance, InstanceTask } from './instance.js';
11
- export { runMeeting } from './meeting.js';
12
- export type { MeetingResult } from './meeting.js';
13
- export { executeTasks } from './tasks.js';
14
- export { createPullRequest } from './pr.js';
15
- export type { PrResult } from './pr.js';
16
- export { runInstance } from './runner.js';
17
- export type { RunResult } from './runner.js';