heyio 3.0.2 → 3.0.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/dist/api/server.js +1 -1
- package/dist/api/server.js.map +1 -1
- package/dist/logging/logger.d.ts.map +1 -1
- package/dist/logging/logger.js +13 -1
- package/dist/logging/logger.js.map +1 -1
- package/node_modules/@io/shared/package.json +1 -1
- package/package.json +7 -2
- package/public/assets/index-2RY89H3W.js +336 -0
- package/public/assets/index-2RY89H3W.js.map +1 -0
- package/public/assets/index-D3cGfBsj.css +1 -0
- package/public/index.html +14 -0
- package/src/api/middleware/auth.ts +0 -76
- package/src/api/notifications.ts +0 -122
- package/src/api/routes/activity.ts +0 -29
- package/src/api/routes/attachments.ts +0 -93
- package/src/api/routes/config.ts +0 -115
- package/src/api/routes/conversations.ts +0 -87
- package/src/api/routes/health.ts +0 -18
- package/src/api/routes/inbox.ts +0 -98
- package/src/api/routes/schedules.ts +0 -121
- package/src/api/routes/skills.ts +0 -105
- package/src/api/routes/squads.ts +0 -145
- package/src/api/routes/usage.ts +0 -57
- package/src/api/routes/wiki.ts +0 -49
- package/src/api/server.ts +0 -186
- package/src/config.ts +0 -3
- package/src/copilot/client.ts +0 -42
- package/src/copilot/health-monitor.ts +0 -85
- package/src/copilot/orchestrator.ts +0 -222
- package/src/copilot/tools.ts +0 -707
- package/src/index.ts +0 -113
- package/src/logging/logger.ts +0 -26
- package/src/models/index.ts +0 -11
- package/src/models/pricing.ts +0 -121
- package/src/models/registry.ts +0 -131
- package/src/models/token-tracker.ts +0 -151
- package/src/scheduler/engine.ts +0 -146
- package/src/skills/index.ts +0 -13
- package/src/skills/store.ts +0 -188
- package/src/squad/agent.ts +0 -326
- package/src/squad/autonomy.ts +0 -78
- package/src/squad/event-bus.ts +0 -71
- package/src/squad/execution/index.ts +0 -17
- package/src/squad/execution/instance.ts +0 -186
- package/src/squad/execution/meeting.ts +0 -191
- package/src/squad/execution/pr.ts +0 -127
- package/src/squad/execution/runner.ts +0 -97
- package/src/squad/execution/tasks.ts +0 -111
- package/src/squad/execution/worktree.ts +0 -138
- package/src/squad/hiring.ts +0 -222
- package/src/squad/index.ts +0 -17
- package/src/squad/manager.ts +0 -337
- package/src/squad/name-generator.ts +0 -135
- package/src/squad/roles/templates.ts +0 -104
- package/src/squad/skill-parser.ts +0 -120
- package/src/squad/source-resolver.ts +0 -57
- package/src/store/activity.ts +0 -176
- package/src/store/db.ts +0 -237
- package/src/store/inbox.ts +0 -199
- package/src/store/schedules.ts +0 -199
- package/src/wiki/index.ts +0 -12
- package/src/wiki/store.ts +0 -139
- package/tsconfig.json +0 -9
package/src/skills/index.ts
DELETED
|
@@ -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';
|
package/src/skills/store.ts
DELETED
|
@@ -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
|
-
}
|
package/src/squad/agent.ts
DELETED
|
@@ -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
|
-
}
|
package/src/squad/autonomy.ts
DELETED
|
@@ -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
|
-
}
|
package/src/squad/event-bus.ts
DELETED
|
@@ -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';
|