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.
Files changed (63) hide show
  1. package/dist/api/server.js +1 -1
  2. package/dist/api/server.js.map +1 -1
  3. package/dist/logging/logger.d.ts.map +1 -1
  4. package/dist/logging/logger.js +13 -1
  5. package/dist/logging/logger.js.map +1 -1
  6. package/node_modules/@io/shared/package.json +1 -1
  7. package/package.json +7 -2
  8. package/public/assets/index-2RY89H3W.js +336 -0
  9. package/public/assets/index-2RY89H3W.js.map +1 -0
  10. package/public/assets/index-D3cGfBsj.css +1 -0
  11. package/public/index.html +14 -0
  12. package/src/api/middleware/auth.ts +0 -76
  13. package/src/api/notifications.ts +0 -122
  14. package/src/api/routes/activity.ts +0 -29
  15. package/src/api/routes/attachments.ts +0 -93
  16. package/src/api/routes/config.ts +0 -115
  17. package/src/api/routes/conversations.ts +0 -87
  18. package/src/api/routes/health.ts +0 -18
  19. package/src/api/routes/inbox.ts +0 -98
  20. package/src/api/routes/schedules.ts +0 -121
  21. package/src/api/routes/skills.ts +0 -105
  22. package/src/api/routes/squads.ts +0 -145
  23. package/src/api/routes/usage.ts +0 -57
  24. package/src/api/routes/wiki.ts +0 -49
  25. package/src/api/server.ts +0 -186
  26. package/src/config.ts +0 -3
  27. package/src/copilot/client.ts +0 -42
  28. package/src/copilot/health-monitor.ts +0 -85
  29. package/src/copilot/orchestrator.ts +0 -222
  30. package/src/copilot/tools.ts +0 -707
  31. package/src/index.ts +0 -113
  32. package/src/logging/logger.ts +0 -26
  33. package/src/models/index.ts +0 -11
  34. package/src/models/pricing.ts +0 -121
  35. package/src/models/registry.ts +0 -131
  36. package/src/models/token-tracker.ts +0 -151
  37. package/src/scheduler/engine.ts +0 -146
  38. package/src/skills/index.ts +0 -13
  39. package/src/skills/store.ts +0 -188
  40. package/src/squad/agent.ts +0 -326
  41. package/src/squad/autonomy.ts +0 -78
  42. package/src/squad/event-bus.ts +0 -71
  43. package/src/squad/execution/index.ts +0 -17
  44. package/src/squad/execution/instance.ts +0 -186
  45. package/src/squad/execution/meeting.ts +0 -191
  46. package/src/squad/execution/pr.ts +0 -127
  47. package/src/squad/execution/runner.ts +0 -97
  48. package/src/squad/execution/tasks.ts +0 -111
  49. package/src/squad/execution/worktree.ts +0 -138
  50. package/src/squad/hiring.ts +0 -222
  51. package/src/squad/index.ts +0 -17
  52. package/src/squad/manager.ts +0 -337
  53. package/src/squad/name-generator.ts +0 -135
  54. package/src/squad/roles/templates.ts +0 -104
  55. package/src/squad/skill-parser.ts +0 -120
  56. package/src/squad/source-resolver.ts +0 -57
  57. package/src/store/activity.ts +0 -176
  58. package/src/store/db.ts +0 -237
  59. package/src/store/inbox.ts +0 -199
  60. package/src/store/schedules.ts +0 -199
  61. package/src/wiki/index.ts +0 -12
  62. package/src/wiki/store.ts +0 -139
  63. package/tsconfig.json +0 -9
@@ -1,186 +0,0 @@
1
- import type { InstanceStatus, Squad } from '@io/shared';
2
- import { createChildLogger } from '../../logging/logger.js';
3
- import { getDatabase } from '../../store/db.js';
4
- import { getEventBus } from '../event-bus.js';
5
- import { type SquadRuntime, getSquadMembers, getSquadRuntime } from '../manager.js';
6
- import { type WorktreeInfo, createWorktree, removeWorktree } from './worktree.js';
7
-
8
- const logger = () => createChildLogger('instance');
9
-
10
- export interface InstanceTask {
11
- id: string;
12
- description: string;
13
- assignedTo: string; // agent role
14
- status: 'pending' | 'in_progress' | 'done' | 'failed';
15
- result?: string;
16
- }
17
-
18
- export interface Instance {
19
- id: string;
20
- squadId: string;
21
- issueRef?: string;
22
- worktree: WorktreeInfo | null;
23
- branch: string | null;
24
- status: InstanceStatus;
25
- tasks: InstanceTask[];
26
- meetingLog: string[];
27
- }
28
-
29
- const activeInstances = new Map<string, Instance>();
30
-
31
- /**
32
- * Create a new instance for a squad. Enforces max 3 per squad.
33
- */
34
- export async function createInstance(params: {
35
- squad: Squad;
36
- issueRef?: string;
37
- objective: string;
38
- }): Promise<Instance> {
39
- const log = logger();
40
- const db = getDatabase();
41
-
42
- // Enforce instance limit
43
- const existingResult = await db.execute({
44
- sql: "SELECT COUNT(*) as cnt FROM squad_instances WHERE squad_id = ? AND status NOT IN ('complete', 'failed')",
45
- args: [params.squad.id],
46
- });
47
- const count = (existingResult.rows[0]?.cnt as number) ?? 0;
48
- if (count >= 3) {
49
- throw new Error(`Squad '${params.squad.name}' already has ${count} active instances (max 3)`);
50
- }
51
-
52
- const id = crypto.randomUUID();
53
-
54
- // Create worktree if project has git
55
- let worktree: WorktreeInfo | null = null;
56
- try {
57
- worktree = createWorktree({
58
- repoPath: params.squad.projectPath,
59
- squadName: params.squad.name,
60
- instanceId: id,
61
- });
62
- } catch (err) {
63
- log.warn({ err }, 'Could not create worktree, proceeding without isolation');
64
- }
65
-
66
- // Persist to DB
67
- await db.execute({
68
- sql: `INSERT INTO squad_instances (id, squad_id, issue_ref, worktree_path, branch_name, status)
69
- VALUES (?, ?, ?, ?, ?, 'planning')`,
70
- args: [
71
- id,
72
- params.squad.id,
73
- params.issueRef ?? null,
74
- worktree?.path ?? null,
75
- worktree?.branch ?? null,
76
- ],
77
- });
78
-
79
- const instance: Instance = {
80
- id,
81
- squadId: params.squad.id,
82
- issueRef: params.issueRef,
83
- worktree,
84
- branch: worktree?.branch ?? null,
85
- status: 'planning',
86
- tasks: [],
87
- meetingLog: [],
88
- };
89
-
90
- activeInstances.set(id, instance);
91
-
92
- await getEventBus().emit({
93
- id: crypto.randomUUID(),
94
- timestamp: new Date(),
95
- type: 'instance:created',
96
- squadId: params.squad.id,
97
- instanceId: id,
98
- data: { issueRef: params.issueRef, objective: params.objective },
99
- });
100
-
101
- log.info({ instanceId: id, squadId: params.squad.id }, 'Instance created');
102
- return instance;
103
- }
104
-
105
- /**
106
- * Transition instance to a new status.
107
- */
108
- export async function transitionInstance(
109
- instanceId: string,
110
- newStatus: InstanceStatus,
111
- ): Promise<void> {
112
- const db = getDatabase();
113
- const instance = activeInstances.get(instanceId);
114
- if (!instance) throw new Error(`Instance ${instanceId} not found`);
115
-
116
- const validTransitions: Record<InstanceStatus, InstanceStatus[]> = {
117
- planning: ['meeting', 'failed'],
118
- meeting: ['working', 'failed'],
119
- working: ['reviewing', 'failed'],
120
- reviewing: ['complete', 'working', 'failed'],
121
- complete: [],
122
- failed: [],
123
- };
124
-
125
- const allowed = validTransitions[instance.status];
126
- if (!allowed.includes(newStatus)) {
127
- throw new Error(`Invalid transition: ${instance.status} → ${newStatus}`);
128
- }
129
-
130
- instance.status = newStatus;
131
- const completedAt =
132
- newStatus === 'complete' || newStatus === 'failed' ? new Date().toISOString() : null;
133
-
134
- await db.execute({
135
- sql: 'UPDATE squad_instances SET status = ?, completed_at = COALESCE(?, completed_at) WHERE id = ?',
136
- args: [newStatus, completedAt, instanceId],
137
- });
138
-
139
- const eventType =
140
- newStatus === 'meeting'
141
- ? 'instance:meeting_started'
142
- : newStatus === 'working'
143
- ? 'instance:work_started'
144
- : newStatus === 'complete'
145
- ? 'instance:complete'
146
- : newStatus === 'failed'
147
- ? 'instance:failed'
148
- : ('instance:created' as const);
149
-
150
- await getEventBus().emit({
151
- id: crypto.randomUUID(),
152
- timestamp: new Date(),
153
- type: eventType,
154
- squadId: instance.squadId,
155
- instanceId,
156
- data: { from: instance.status, to: newStatus },
157
- });
158
- }
159
-
160
- /**
161
- * Clean up a completed/failed instance (remove worktree).
162
- */
163
- export async function cleanupInstance(instanceId: string, squad: Squad): Promise<void> {
164
- const instance = activeInstances.get(instanceId);
165
- if (!instance) return;
166
-
167
- if (instance.worktree && instance.branch) {
168
- removeWorktree({
169
- repoPath: squad.projectPath,
170
- worktreePath: instance.worktree.path,
171
- branch: instance.branch,
172
- });
173
- }
174
-
175
- activeInstances.delete(instanceId);
176
- }
177
-
178
- /** Get an active instance */
179
- export function getInstance(instanceId: string): Instance | undefined {
180
- return activeInstances.get(instanceId);
181
- }
182
-
183
- /** Get all active instances for a squad */
184
- export function getSquadInstances(squadId: string): Instance[] {
185
- return [...activeInstances.values()].filter((i) => i.squadId === squadId);
186
- }
@@ -1,191 +0,0 @@
1
- import { createChildLogger } from '../../logging/logger.js';
2
- import type { Agent } from '../agent.js';
3
- import { getEventBus } from '../event-bus.js';
4
- import type { SquadRuntime } from '../manager.js';
5
- import type { Instance, InstanceTask } from './instance.js';
6
- import { transitionInstance } from './instance.js';
7
-
8
- const logger = () => createChildLogger('meeting');
9
-
10
- export interface MeetingResult {
11
- consensus: boolean;
12
- tasks: InstanceTask[];
13
- log: string[];
14
- vetoReason?: string;
15
- }
16
-
17
- const MAX_ROUNDS = 5;
18
-
19
- /**
20
- * Run a round-table meeting for an instance.
21
- * Protocol:
22
- * 1. Team Lead presents context
23
- * 2. Each agent speaks in round-robin order
24
- * 3. Agents respond to previous speakers
25
- * 4. Team Lead calls for consensus
26
- * 5. Veto members can block
27
- * 6. If blocked, discussion continues (up to MAX_ROUNDS)
28
- * 7. On consensus, Team Lead formalizes task list
29
- */
30
- export async function runMeeting(params: {
31
- instance: Instance;
32
- runtime: SquadRuntime;
33
- objective: string;
34
- }): Promise<MeetingResult> {
35
- const log = logger();
36
- const { instance, runtime, objective } = params;
37
- const meetingLog: string[] = [];
38
-
39
- await transitionInstance(instance.id, 'meeting');
40
-
41
- await getEventBus().emit({
42
- id: crypto.randomUUID(),
43
- timestamp: new Date(),
44
- type: 'instance:meeting_started',
45
- squadId: instance.squadId,
46
- instanceId: instance.id,
47
- data: { objective },
48
- });
49
-
50
- const teamLead = runtime.members.get('team-lead');
51
- if (!teamLead) throw new Error('No team lead available for meeting');
52
-
53
- // Get non-lead agents for discussion
54
- const participants = [...runtime.members.entries()].filter(([role]) => role !== 'team-lead');
55
-
56
- // Step 1: Team lead presents the objective
57
- const presentation = await teamLead.send(
58
- `You are starting a round-table meeting. Present the following objective to your team and ask for input:\n\nObjective: ${objective}\n\nProvide a brief summary of the work needed and what expertise is required. Then ask each team member for their perspective.`,
59
- );
60
- meetingLog.push(`[team-lead] ${presentation}`);
61
-
62
- await emitContribution(instance, 'team-lead', presentation);
63
-
64
- // Step 2-3: Round-robin discussion
65
- let round = 0;
66
- let consensusReached = false;
67
- let vetoReason: string | undefined;
68
-
69
- while (round < MAX_ROUNDS && !consensusReached) {
70
- round++;
71
- log.info({ instanceId: instance.id, round }, 'Meeting round');
72
-
73
- // Each participant speaks
74
- for (const [role, agent] of participants) {
75
- const context = meetingLog.slice(-5).join('\n\n');
76
- const response = await agent.send(
77
- `You are in a team meeting (round ${round}/${MAX_ROUNDS}). Here's the recent discussion:\n\n${context}\n\nProvide your professional input on the objective. Focus on your area of expertise. If you're QA, focus on testing concerns. If you have concerns, state them clearly.`,
78
- );
79
- meetingLog.push(`[${role}] ${response}`);
80
- await emitContribution(instance, role, response);
81
- }
82
-
83
- // Step 4: Team lead calls for consensus
84
- const consensusCheck = await teamLead.send(
85
- `The discussion so far:\n\n${meetingLog.slice(-participants.length - 1).join('\n\n')}\n\nBased on this discussion, do we have consensus to proceed? Consider all concerns raised. Reply with either:\n- "CONSENSUS: <brief summary of agreed plan>"\n- "NEED_DISCUSSION: <what needs to be resolved>"`,
86
- );
87
- meetingLog.push(`[team-lead] ${consensusCheck}`);
88
-
89
- if (consensusCheck.toUpperCase().includes('CONSENSUS:')) {
90
- // Step 5: Check veto members
91
- const vetoMembers = [...runtime.members.entries()].filter(([role]) => {
92
- const skill = runtime.skills.get(role);
93
- return skill?.veto;
94
- });
95
-
96
- let vetoed = false;
97
- for (const [role, agent] of vetoMembers) {
98
- if (role === 'team-lead') continue; // team lead already agreed
99
- const vetoCheck = await agent.send(
100
- `The team has reached consensus on the following plan:\n\n${consensusCheck}\n\nAs a veto-holding member, do you approve this plan? Reply with:\n- "APPROVE" if you agree\n- "VETO: <reason>" if you have critical concerns that must be addressed`,
101
- );
102
-
103
- if (vetoCheck.toUpperCase().includes('VETO:')) {
104
- vetoed = true;
105
- vetoReason = vetoCheck;
106
- meetingLog.push(`[${role}] VETO: ${vetoCheck}`);
107
- await emitVeto(instance, role, vetoCheck);
108
- break;
109
- }
110
- meetingLog.push(`[${role}] Approved`);
111
- }
112
-
113
- if (!vetoed) {
114
- consensusReached = true;
115
- }
116
- }
117
- }
118
-
119
- // Step 7: Formalize task list
120
- let tasks: InstanceTask[] = [];
121
- if (consensusReached) {
122
- const taskPlan = await teamLead.send(
123
- `Consensus reached. Now formalize the work into specific tasks. For each task, specify:\n1. A brief description\n2. Which team member role should do it\n\nFormat each task as: "TASK: <description> | ASSIGN: <role>"\n\nList all tasks needed to complete the objective.`,
124
- );
125
- meetingLog.push(`[team-lead] Task plan: ${taskPlan}`);
126
- tasks = parseTaskList(taskPlan, instance.id);
127
- }
128
-
129
- await getEventBus().emit({
130
- id: crypto.randomUUID(),
131
- timestamp: new Date(),
132
- type: 'instance:meeting_complete',
133
- squadId: instance.squadId,
134
- instanceId: instance.id,
135
- data: { consensus: consensusReached, taskCount: tasks.length, rounds: round },
136
- });
137
-
138
- instance.meetingLog = meetingLog;
139
- instance.tasks = tasks;
140
-
141
- log.info(
142
- { instanceId: instance.id, consensus: consensusReached, tasks: tasks.length },
143
- 'Meeting complete',
144
- );
145
-
146
- return { consensus: consensusReached, tasks, log: meetingLog, vetoReason };
147
- }
148
-
149
- /** Parse the team lead's task list into structured tasks */
150
- function parseTaskList(taskPlan: string, instanceId: string): InstanceTask[] {
151
- const tasks: InstanceTask[] = [];
152
- const lines = taskPlan.split('\n');
153
-
154
- for (const line of lines) {
155
- const match = line.match(/TASK:\s*(.+?)\s*\|\s*ASSIGN:\s*(.+)/i);
156
- if (match) {
157
- tasks.push({
158
- id: crypto.randomUUID(),
159
- description: match[1].trim(),
160
- assignedTo: match[2].trim().toLowerCase().replace(/\s+/g, '-'),
161
- status: 'pending',
162
- });
163
- }
164
- }
165
-
166
- return tasks;
167
- }
168
-
169
- async function emitContribution(instance: Instance, role: string, content: string) {
170
- await getEventBus().emit({
171
- id: crypto.randomUUID(),
172
- timestamp: new Date(),
173
- type: 'meeting:contribution',
174
- squadId: instance.squadId,
175
- instanceId: instance.id,
176
- agentRole: role,
177
- content: content.slice(0, 500),
178
- });
179
- }
180
-
181
- async function emitVeto(instance: Instance, role: string, reason: string) {
182
- await getEventBus().emit({
183
- id: crypto.randomUUID(),
184
- timestamp: new Date(),
185
- type: 'meeting:veto',
186
- squadId: instance.squadId,
187
- instanceId: instance.id,
188
- agentRole: role,
189
- content: reason,
190
- });
191
- }
@@ -1,127 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { createChildLogger } from '../../logging/logger.js';
3
- import type { Instance } from './instance.js';
4
- import { transitionInstance } from './instance.js';
5
-
6
- const logger = () => createChildLogger('pr');
7
-
8
- export interface PrResult {
9
- url: string;
10
- number: number;
11
- }
12
-
13
- /**
14
- * Create a pull request from the instance's worktree branch.
15
- * Commits any uncommitted changes, pushes the branch, then creates a PR.
16
- */
17
- export async function createPullRequest(params: {
18
- instance: Instance;
19
- title: string;
20
- squadName: string;
21
- }): Promise<PrResult | null> {
22
- const log = logger();
23
- const { instance, title, squadName } = params;
24
-
25
- if (!instance.worktree || !instance.branch) {
26
- log.warn({ instanceId: instance.id }, 'No worktree/branch, skipping PR creation');
27
- await transitionInstance(instance.id, 'complete');
28
- return null;
29
- }
30
-
31
- const cwd = instance.worktree.path;
32
-
33
- try {
34
- // Check if there are changes to commit
35
- const status = execSync('git status --porcelain', { cwd, encoding: 'utf-8' }).trim();
36
- if (!status) {
37
- log.info({ instanceId: instance.id }, 'No changes to commit');
38
- await transitionInstance(instance.id, 'complete');
39
- return null;
40
- }
41
-
42
- // Stage and commit all changes
43
- execSync('git add -A', { cwd, stdio: 'pipe' });
44
-
45
- const commitMessage = buildCommitMessage(title, instance, squadName);
46
- execSync(`git commit -m "${escapeShell(commitMessage)}"`, { cwd, stdio: 'pipe' });
47
-
48
- // Push the branch
49
- execSync(`git push -u origin "${instance.branch}"`, { cwd, stdio: 'pipe' });
50
-
51
- // Create the PR using gh CLI
52
- const prBody = buildPrBody(instance, squadName);
53
- const prOutput = execSync(
54
- `gh pr create --title "${escapeShell(title)}" --body "${escapeShell(prBody)}" --head "${instance.branch}"`,
55
- { cwd, encoding: 'utf-8', stdio: 'pipe' },
56
- ).trim();
57
-
58
- // Parse the PR URL to get the number
59
- const prUrl = prOutput;
60
- const prNumber = Number.parseInt(prUrl.split('/').pop() ?? '0', 10);
61
-
62
- await transitionInstance(instance.id, 'complete');
63
- log.info({ instanceId: instance.id, prUrl, prNumber }, 'PR created');
64
-
65
- return { url: prUrl, number: prNumber };
66
- } catch (err) {
67
- log.error({ err, instanceId: instance.id }, 'Failed to create PR');
68
- await transitionInstance(instance.id, 'failed');
69
- return null;
70
- }
71
- }
72
-
73
- function buildCommitMessage(title: string, instance: Instance, squadName: string): string {
74
- const lines = [`feat: ${title}`, '', `Squad: ${squadName}`, `Instance: ${instance.id}`];
75
-
76
- if (instance.issueRef) {
77
- lines.push(`Closes ${instance.issueRef}`);
78
- }
79
-
80
- const completedTasks = instance.tasks.filter((t) => t.status === 'done');
81
- if (completedTasks.length > 0) {
82
- lines.push('', 'Tasks completed:');
83
- for (const task of completedTasks) {
84
- lines.push(`- ${task.description} (${task.assignedTo})`);
85
- }
86
- }
87
-
88
- return lines.join('\n');
89
- }
90
-
91
- function buildPrBody(instance: Instance, squadName: string): string {
92
- const sections: string[] = [];
93
-
94
- sections.push(`## 🤖 Generated by IO Squad: ${squadName}`);
95
- sections.push('');
96
-
97
- if (instance.issueRef) {
98
- sections.push(`Closes ${instance.issueRef}`);
99
- sections.push('');
100
- }
101
-
102
- // Tasks section
103
- sections.push('### Tasks Completed');
104
- for (const task of instance.tasks) {
105
- const icon = task.status === 'done' ? '✅' : task.status === 'failed' ? '❌' : '⏳';
106
- sections.push(`${icon} ${task.description} — *${task.assignedTo}*`);
107
- }
108
-
109
- // Meeting highlights (last few entries)
110
- if (instance.meetingLog.length > 0) {
111
- sections.push('');
112
- sections.push('<details><summary>Meeting Discussion</summary>');
113
- sections.push('');
114
- const recentLog = instance.meetingLog.slice(-10);
115
- for (const entry of recentLog) {
116
- sections.push(`> ${entry.slice(0, 200)}`);
117
- sections.push('');
118
- }
119
- sections.push('</details>');
120
- }
121
-
122
- return sections.join('\n');
123
- }
124
-
125
- function escapeShell(str: string): string {
126
- return str.replace(/"/g, '\\"').replace(/\n/g, '\\n');
127
- }
@@ -1,97 +0,0 @@
1
- import type { Squad } from '@io/shared';
2
- import { createChildLogger } from '../../logging/logger.js';
3
- import { type SquadRuntime, bootSquad, getSquadRuntime } from '../manager.js';
4
- import {
5
- type Instance,
6
- type PrResult,
7
- cleanupInstance,
8
- createInstance,
9
- createPullRequest,
10
- executeTasks,
11
- runMeeting,
12
- } from './index.js';
13
-
14
- const logger = () => createChildLogger('runner');
15
-
16
- export interface RunResult {
17
- instanceId: string;
18
- success: boolean;
19
- pr?: PrResult | null;
20
- error?: string;
21
- }
22
-
23
- /**
24
- * Run a full instance lifecycle:
25
- * 1. Create instance (with worktree)
26
- * 2. Hold round-table meeting
27
- * 3. Execute tasks
28
- * 4. Create PR
29
- * 5. Clean up
30
- */
31
- export async function runInstance(params: {
32
- squad: Squad;
33
- objective: string;
34
- issueRef?: string;
35
- }): Promise<RunResult> {
36
- const log = logger();
37
- const { squad, objective, issueRef } = params;
38
-
39
- // Ensure squad is booted
40
- let runtime: SquadRuntime | undefined = getSquadRuntime(squad.id);
41
- if (!runtime) {
42
- runtime = await bootSquad(squad);
43
- }
44
-
45
- // 1. Create instance
46
- const instance = await createInstance({ squad, issueRef, objective });
47
- log.info({ instanceId: instance.id }, 'Starting instance run');
48
-
49
- try {
50
- // 2. Run meeting
51
- const meetingResult = await runMeeting({ instance, runtime, objective });
52
-
53
- if (!meetingResult.consensus) {
54
- log.warn({ instanceId: instance.id }, 'Meeting did not reach consensus');
55
- return {
56
- instanceId: instance.id,
57
- success: false,
58
- error: `Meeting failed to reach consensus${meetingResult.vetoReason ? `: ${meetingResult.vetoReason}` : ''}`,
59
- };
60
- }
61
-
62
- if (instance.tasks.length === 0) {
63
- log.warn({ instanceId: instance.id }, 'No tasks generated from meeting');
64
- return {
65
- instanceId: instance.id,
66
- success: false,
67
- error: 'Meeting produced no actionable tasks',
68
- };
69
- }
70
-
71
- // 3. Execute tasks
72
- await executeTasks({ instance, runtime });
73
-
74
- // 4. Create PR
75
- const prTitle = objective.slice(0, 72);
76
- const pr = await createPullRequest({ instance, title: prTitle, squadName: squad.name });
77
-
78
- // 5. Cleanup (if no PR was created — otherwise keep the branch for review)
79
- if (!pr) {
80
- await cleanupInstance(instance.id, squad);
81
- }
82
-
83
- return {
84
- instanceId: instance.id,
85
- success: true,
86
- pr,
87
- };
88
- } catch (err) {
89
- log.error({ err, instanceId: instance.id }, 'Instance run failed');
90
- await cleanupInstance(instance.id, squad);
91
- return {
92
- instanceId: instance.id,
93
- success: false,
94
- error: err instanceof Error ? err.message : String(err),
95
- };
96
- }
97
- }