heyhank 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -10
- package/bin/cli.ts +7 -7
- package/bin/ctl.ts +42 -42
- package/dist/assets/{AgentsPage-BPhirnCe.js → AgentsPage-B-AAmsMK.js} +3 -3
- package/dist/assets/AssistantPage-BV1Mfwdt.js +2 -0
- package/dist/assets/BusinessPage-tLpNEz19.js +1 -0
- package/dist/assets/{CronManager-DDbz-yiT.js → CronManager-B-K_n3Jg.js} +1 -1
- package/dist/assets/HelpPage-Bhf_j6Xr.js +1 -0
- package/dist/assets/{IntegrationsPage-CrOitCmJ.js → IntegrationsPage-DAMjs9tM.js} +1 -1
- package/dist/assets/JarvisHUD-C_TGXCCn.js +120 -0
- package/dist/assets/MediaPage-C48HTTrt.js +1 -0
- package/dist/assets/MemoryPage-JkC-qtgp.js +1 -0
- package/dist/assets/{PlatformDashboard-Do6F0O2p.js → PlatformDashboard-AUo7tNnE.js} +1 -1
- package/dist/assets/{Playground-Fc5cdc5p.js → Playground-AzNMsRBL.js} +1 -1
- package/dist/assets/{ProcessPanel-CslEiZkI.js → ProcessPanel-DpE_2sX3.js} +1 -1
- package/dist/assets/{PromptsPage-D2EhsdNO.js → PromptsPage-C2RQOs6p.js} +2 -2
- package/dist/assets/RunsPage-B9UOyO79.js +1 -0
- package/dist/assets/{SandboxManager-a1AVI5q2.js → SandboxManager-jHvYjwfh.js} +1 -1
- package/dist/assets/SettingsPage-BBJax6gt.js +51 -0
- package/dist/assets/SkillsMarketplace-IjmjfdjD.js +1 -0
- package/dist/assets/SocialMediaPage-DoPZHhr2.js +10 -0
- package/dist/assets/{TailscalePage-CHiFhZXF.js → TailscalePage-DDEY7ckO.js} +1 -1
- package/dist/assets/TelephonyPage-OPNBZYKt.js +9 -0
- package/dist/assets/{TerminalPage-Drwyrnfd.js → TerminalPage-BjMbHHW3.js} +1 -1
- package/dist/assets/{gemini-live-client-C7rqAW7G.js → gemini-live-client-C70FEtX2.js} +11 -8
- package/dist/assets/{index-CEqZnThB.js → index-BgYM4wXw.js} +94 -93
- package/dist/assets/index-BkjSoVgn.css +32 -0
- package/dist/assets/sw-register-C7NOHtIu.js +1 -0
- package/dist/assets/text-chat-client-BSbLJerZ.js +2 -0
- package/dist/index.html +2 -2
- package/dist/sw.js +1 -1
- package/package.json +6 -1
- package/server/agent-executor.ts +37 -2
- package/server/agent-store.ts +3 -3
- package/server/agent-types.ts +11 -0
- package/server/assistant-store.ts +232 -6
- package/server/auth-manager.ts +9 -0
- package/server/cache-headers.ts +1 -1
- package/server/calendar-service.ts +10 -0
- package/server/ceo/document-store.ts +129 -0
- package/server/ceo/finance-store.ts +343 -0
- package/server/ceo/kpi-store.ts +208 -0
- package/server/ceo/memory-import.ts +277 -0
- package/server/ceo/news-store.ts +208 -0
- package/server/ceo/template-store.ts +134 -0
- package/server/ceo/time-tracking-store.ts +227 -0
- package/server/claude-auth-monitor.ts +128 -0
- package/server/claude-code-worker.ts +86 -0
- package/server/claude-session-discovery.ts +74 -1
- package/server/cli-launcher.ts +32 -10
- package/server/codex-adapter.ts +2 -2
- package/server/codex-ws-proxy.cjs +1 -1
- package/server/container-manager.ts +4 -4
- package/server/content-intelligence/content-engine.ts +1112 -0
- package/server/content-intelligence/platform-knowledge.ts +870 -0
- package/server/cron-store.ts +3 -3
- package/server/embedding-service.ts +49 -0
- package/server/event-bus-types.ts +13 -0
- package/server/federation/node-store.ts +5 -4
- package/server/fs-utils.ts +28 -1
- package/server/hank-notifications-store.ts +91 -0
- package/server/hank-tool-executor.ts +1835 -0
- package/server/hank-tools.ts +2107 -0
- package/server/image-pull-manager.ts +2 -2
- package/server/index.ts +25 -2
- package/server/llm-providers-streaming.ts +541 -0
- package/server/llm-providers.ts +12 -0
- package/server/marketplace.ts +249 -0
- package/server/mcp-registry.ts +158 -0
- package/server/memory-service.ts +296 -0
- package/server/obsidian-sync.ts +184 -0
- package/server/provider-manager.ts +5 -2
- package/server/provider-registry.ts +12 -0
- package/server/reminder-scheduler.ts +37 -1
- package/server/routes/agent-routes.ts +2 -1
- package/server/routes/assistant-routes.ts +198 -5
- package/server/routes/ceo-finance-kpi-routes.ts +167 -0
- package/server/routes/ceo-news-time-routes.ts +137 -0
- package/server/routes/ceo-routes.ts +99 -0
- package/server/routes/content-routes.ts +116 -0
- package/server/routes/email-routes.ts +147 -0
- package/server/routes/env-routes.ts +3 -3
- package/server/routes/fs-routes.ts +12 -9
- package/server/routes/hank-chat-routes.ts +592 -0
- package/server/routes/llm-routes.ts +12 -0
- package/server/routes/marketplace-routes.ts +63 -0
- package/server/routes/media-routes.ts +1 -1
- package/server/routes/memory-routes.ts +127 -0
- package/server/routes/platform-routes.ts +14 -675
- package/server/routes/sandbox-routes.ts +1 -1
- package/server/routes/settings-routes.ts +51 -1
- package/server/routes/socialmedia-routes.ts +152 -2
- package/server/routes/system-routes.ts +2 -2
- package/server/routes/team-routes.ts +71 -0
- package/server/routes/telephony-routes.ts +98 -18
- package/server/routes.ts +36 -9
- package/server/session-creation-service.ts +2 -2
- package/server/session-orchestrator.ts +54 -2
- package/server/session-types.ts +2 -0
- package/server/settings-manager.ts +50 -2
- package/server/skill-discovery.ts +68 -0
- package/server/socialmedia/adapters/browser-adapter.ts +179 -0
- package/server/socialmedia/adapters/postiz-adapter.ts +291 -14
- package/server/socialmedia/manager.ts +234 -15
- package/server/socialmedia/store.ts +51 -1
- package/server/socialmedia/types.ts +35 -2
- package/server/socialview/browser-manager.ts +150 -0
- package/server/socialview/extractors.ts +1298 -0
- package/server/socialview/image-describe.ts +188 -0
- package/server/socialview/library.ts +119 -0
- package/server/socialview/poster.ts +276 -0
- package/server/socialview/routes.ts +371 -0
- package/server/socialview/style-analyzer.ts +187 -0
- package/server/socialview/style-profiles.ts +67 -0
- package/server/socialview/types.ts +166 -0
- package/server/socialview/vision.ts +127 -0
- package/server/socialview/vnc-manager.ts +110 -0
- package/server/style-injector.ts +135 -0
- package/server/team-service.ts +239 -0
- package/server/team-store.ts +75 -0
- package/server/team-types.ts +52 -0
- package/server/telephony/audio-bridge.ts +281 -35
- package/server/telephony/audio-recorder.ts +132 -0
- package/server/telephony/call-manager.ts +803 -104
- package/server/telephony/call-types.ts +67 -1
- package/server/telephony/esl-client.ts +319 -0
- package/server/telephony/freeswitch-sync.ts +155 -0
- package/server/telephony/phone-utils.ts +63 -0
- package/server/telephony/telephony-store.ts +9 -8
- package/server/url-validator.ts +82 -0
- package/server/vault-markdown.ts +317 -0
- package/server/vault-migration.ts +121 -0
- package/server/vault-store.ts +466 -0
- package/server/vault-watcher.ts +59 -0
- package/server/vector-store.ts +210 -0
- package/server/voice-pipeline/gemini-live-adapter.ts +97 -0
- package/server/voice-pipeline/greeting-cache.ts +200 -0
- package/server/voice-pipeline/manager.ts +249 -0
- package/server/voice-pipeline/pipeline.ts +335 -0
- package/server/voice-pipeline/providers/index.ts +47 -0
- package/server/voice-pipeline/providers/llm-internal.ts +527 -0
- package/server/voice-pipeline/providers/stt-google.ts +157 -0
- package/server/voice-pipeline/providers/tts-google.ts +126 -0
- package/server/voice-pipeline/types.ts +247 -0
- package/server/ws-bridge-types.ts +6 -1
- package/dist/assets/AssistantPage-DJ-cMQfb.js +0 -1
- package/dist/assets/HelpPage-DMfkzERp.js +0 -1
- package/dist/assets/MediaPage-CE5rdvkC.js +0 -1
- package/dist/assets/RunsPage-C5BZF5Rx.js +0 -1
- package/dist/assets/SettingsPage-DirhjQrJ.js +0 -51
- package/dist/assets/SocialMediaPage-DBuM28vD.js +0 -1
- package/dist/assets/TelephonyPage-x0VV0fOo.js +0 -1
- package/dist/assets/index-C8M_PUmX.css +0 -32
- package/dist/assets/sw-register-LSSpj6RU.js +0 -1
- package/server/socialmedia/adapters/ayrshare-adapter.ts +0 -169
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import * as teamStore from "./team-store.js";
|
|
3
|
+
import * as agentStore from "./agent-store.js";
|
|
4
|
+
import type { Team, TeamStatus, TeamState } from "./team-types.js";
|
|
5
|
+
|
|
6
|
+
const COORDINATOR_SYSTEM_PROMPT = `You are the Team Coordinator for HeyHank. You coordinate parallel agent teams to accomplish complex goals.
|
|
7
|
+
|
|
8
|
+
## Your Goal
|
|
9
|
+
{{goal}}
|
|
10
|
+
|
|
11
|
+
## Team Info
|
|
12
|
+
- Team ID: {{teamId}}
|
|
13
|
+
- Repo: {{repoRoot}}
|
|
14
|
+
- Base branch: {{baseBranch}}
|
|
15
|
+
- Team branch: {{teamBranch}}
|
|
16
|
+
- Suggested agents: {{suggestedAgents}}
|
|
17
|
+
|
|
18
|
+
## Available Agents
|
|
19
|
+
{{availableAgents}}
|
|
20
|
+
|
|
21
|
+
## API Reference
|
|
22
|
+
Base URL: {{apiBase}}
|
|
23
|
+
All requests need: Authorization: Bearer {{authToken}}
|
|
24
|
+
|
|
25
|
+
### List agents
|
|
26
|
+
curl -s -H "Authorization: Bearer {{authToken}}" {{apiBase}}/api/agents | jq '.[] | {id, name, description}'
|
|
27
|
+
|
|
28
|
+
### Create a new agent
|
|
29
|
+
curl -s -X POST -H "Authorization: Bearer {{authToken}}" -H "Content-Type: application/json" {{apiBase}}/api/agents -d '{
|
|
30
|
+
"name": "Agent Name",
|
|
31
|
+
"description": "What this agent does",
|
|
32
|
+
"backendType": "claude",
|
|
33
|
+
"model": "claude-sonnet-4-20250514",
|
|
34
|
+
"prompt": "Your system prompt here",
|
|
35
|
+
"cwd": "/path/to/worktree",
|
|
36
|
+
"permissionMode": "bypassPermissions"
|
|
37
|
+
}'
|
|
38
|
+
|
|
39
|
+
### Run an agent (start a task)
|
|
40
|
+
curl -s -X POST -H "Authorization: Bearer {{authToken}}" -H "Content-Type: application/json" {{apiBase}}/api/agents/{agentId}/run -d '{
|
|
41
|
+
"input": "The task description",
|
|
42
|
+
"cwd": "/path/to/worktree"
|
|
43
|
+
}'
|
|
44
|
+
Response: { "sessionId": "..." }
|
|
45
|
+
|
|
46
|
+
### Monitor an agent session
|
|
47
|
+
curl -s -H "Authorization: Bearer {{authToken}}" {{apiBase}}/api/sessions/{sessionId}/agent-status
|
|
48
|
+
Response: { "isCompleted": bool, "isWorking": bool, "needsInput": bool, "recentActivity": [...] }
|
|
49
|
+
|
|
50
|
+
### Update team task status
|
|
51
|
+
curl -s -X PUT -H "Authorization: Bearer {{authToken}}" -H "Content-Type: application/json" {{apiBase}}/api/teams/{{teamId}}/tasks/{taskId} -d '{
|
|
52
|
+
"state": "running|completed|failed",
|
|
53
|
+
"sessionId": "...",
|
|
54
|
+
"agentId": "...",
|
|
55
|
+
"agentName": "..."
|
|
56
|
+
}'
|
|
57
|
+
|
|
58
|
+
### Complete team
|
|
59
|
+
curl -s -X POST -H "Authorization: Bearer {{authToken}}" -H "Content-Type: application/json" {{apiBase}}/api/teams/{{teamId}}/complete -d '{
|
|
60
|
+
"result": "Summary of what was accomplished"
|
|
61
|
+
}'
|
|
62
|
+
|
|
63
|
+
### Fail team
|
|
64
|
+
curl -s -X POST -H "Authorization: Bearer {{authToken}}" -H "Content-Type: application/json" {{apiBase}}/api/teams/{{teamId}}/fail -d '{
|
|
65
|
+
"error": "What went wrong"
|
|
66
|
+
}'
|
|
67
|
+
|
|
68
|
+
## Your Workflow
|
|
69
|
+
|
|
70
|
+
### Step 1: PLAN
|
|
71
|
+
Analyze the goal. Break it into independent, parallelizable tasks. Consider:
|
|
72
|
+
- What needs to be done?
|
|
73
|
+
- Which tasks can run in parallel vs which have dependencies?
|
|
74
|
+
- Which existing agent fits each task? If none fits, create a new specialized agent.
|
|
75
|
+
|
|
76
|
+
### Step 2: PREPARE
|
|
77
|
+
For each task, create a git worktree for isolation:
|
|
78
|
+
\`\`\`bash
|
|
79
|
+
cd {{repoRoot}}
|
|
80
|
+
git worktree add -b task-{taskId} .worktrees/task-{taskId} {{baseBranch}}
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
### Step 3: ASSIGN & START
|
|
84
|
+
For each task:
|
|
85
|
+
1. Choose or create an agent
|
|
86
|
+
2. Start the agent with cwd set to the worktree path
|
|
87
|
+
3. Update the task status via the API
|
|
88
|
+
4. Record the sessionId
|
|
89
|
+
|
|
90
|
+
### Step 4: MONITOR
|
|
91
|
+
Poll each running session every 10 seconds:
|
|
92
|
+
\`\`\`bash
|
|
93
|
+
curl -s -H "Authorization: Bearer {{authToken}}" {{apiBase}}/api/sessions/{sessionId}/agent-status
|
|
94
|
+
\`\`\`
|
|
95
|
+
- If \`needsInput\` is true: check what the agent needs, provide it via send_message
|
|
96
|
+
- If \`isCompleted\` is true: mark task as completed
|
|
97
|
+
- If there's an error: mark task as failed
|
|
98
|
+
|
|
99
|
+
### Step 5: MERGE
|
|
100
|
+
When all tasks are completed:
|
|
101
|
+
\`\`\`bash
|
|
102
|
+
cd {{repoRoot}}
|
|
103
|
+
git checkout {{baseBranch}}
|
|
104
|
+
git checkout -b {{teamBranch}}
|
|
105
|
+
# Merge each task branch
|
|
106
|
+
git merge --no-ff task-{taskId1} -m "Merge task: {description}"
|
|
107
|
+
git merge --no-ff task-{taskId2} -m "Merge task: {description}"
|
|
108
|
+
# If merge conflict: attempt to resolve, or report in team failure
|
|
109
|
+
\`\`\`
|
|
110
|
+
|
|
111
|
+
### Step 6: CLEANUP & REPORT
|
|
112
|
+
\`\`\`bash
|
|
113
|
+
# Remove worktrees
|
|
114
|
+
git worktree remove .worktrees/task-{taskId1}
|
|
115
|
+
git worktree remove .worktrees/task-{taskId2}
|
|
116
|
+
# Delete task branches
|
|
117
|
+
git branch -d task-{taskId1}
|
|
118
|
+
git branch -d task-{taskId2}
|
|
119
|
+
\`\`\`
|
|
120
|
+
Then call the complete endpoint with a summary of what was accomplished.
|
|
121
|
+
|
|
122
|
+
## Important Rules
|
|
123
|
+
- Always use worktrees for isolation — never let two agents edit the same working directory
|
|
124
|
+
- Create new agents when no existing agent fits a task — don't force a mismatch
|
|
125
|
+
- Monitor agents actively — check every 10 seconds
|
|
126
|
+
- If an agent fails, try to recover (restart or reassign) before failing the team
|
|
127
|
+
- Keep the team status updated via the API so Hank can monitor progress
|
|
128
|
+
- After merging, verify the result compiles/builds if applicable
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
export function createTeam(opts: {
|
|
132
|
+
goal: string;
|
|
133
|
+
repoRoot: string;
|
|
134
|
+
baseBranch?: string;
|
|
135
|
+
suggestedAgents?: string[];
|
|
136
|
+
}): Team {
|
|
137
|
+
const teamId = `team-${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
138
|
+
const team: Team = {
|
|
139
|
+
id: teamId,
|
|
140
|
+
goal: opts.goal,
|
|
141
|
+
state: "pending",
|
|
142
|
+
coordinatorSessionId: null,
|
|
143
|
+
tasks: [],
|
|
144
|
+
suggestedAgents: opts.suggestedAgents || [],
|
|
145
|
+
repoRoot: opts.repoRoot,
|
|
146
|
+
baseBranch: opts.baseBranch || "main",
|
|
147
|
+
teamBranch: `team/${teamId}`,
|
|
148
|
+
createdAt: Date.now(),
|
|
149
|
+
updatedAt: Date.now(),
|
|
150
|
+
};
|
|
151
|
+
teamStore.createTeam(team);
|
|
152
|
+
return team;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function getTeam(teamId: string): Team | null {
|
|
156
|
+
return teamStore.getTeam(teamId);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function listTeams(): Team[] {
|
|
160
|
+
return teamStore.listTeams();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function deleteTeam(teamId: string): boolean {
|
|
164
|
+
return teamStore.deleteTeam(teamId);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function getTeamStatus(teamId: string): TeamStatus | null {
|
|
168
|
+
const team = teamStore.getTeam(teamId);
|
|
169
|
+
if (!team) return null;
|
|
170
|
+
return {
|
|
171
|
+
id: team.id,
|
|
172
|
+
goal: team.goal,
|
|
173
|
+
state: team.state,
|
|
174
|
+
tasksTotal: team.tasks.length,
|
|
175
|
+
tasksCompleted: team.tasks.filter((t) => t.state === "completed").length,
|
|
176
|
+
tasksFailed: team.tasks.filter((t) => t.state === "failed").length,
|
|
177
|
+
tasksRunning: team.tasks.filter((t) => t.state === "running").length,
|
|
178
|
+
tasks: team.tasks.map((t) => ({
|
|
179
|
+
id: t.id,
|
|
180
|
+
description: t.description,
|
|
181
|
+
agentName: t.agentName,
|
|
182
|
+
state: t.state,
|
|
183
|
+
})),
|
|
184
|
+
result: team.result,
|
|
185
|
+
error: team.error,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function updateTeamState(teamId: string, state: TeamState, extra?: Partial<Team>): Team | null {
|
|
190
|
+
return teamStore.updateTeam(teamId, { state, updatedAt: Date.now(), ...extra });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function updateTask(teamId: string, taskId: string, updates: Record<string, unknown>): Team | null {
|
|
194
|
+
const team = teamStore.getTeam(teamId);
|
|
195
|
+
if (!team) return null;
|
|
196
|
+
const task = team.tasks.find((t) => t.id === taskId);
|
|
197
|
+
if (!task) return null;
|
|
198
|
+
Object.assign(task, updates);
|
|
199
|
+
team.updatedAt = Date.now();
|
|
200
|
+
return teamStore.updateTeam(teamId, { tasks: team.tasks, updatedAt: team.updatedAt });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function buildCoordinatorPrompt(team: Team, apiBase: string, authToken: string): string {
|
|
204
|
+
const agents = agentStore.listAgents().filter((a) => a.enabled);
|
|
205
|
+
const agentList = agents
|
|
206
|
+
.map((a) => `- "${a.name}" (${a.backendType}): ${a.description || "no description"}`)
|
|
207
|
+
.join("\n");
|
|
208
|
+
|
|
209
|
+
return COORDINATOR_SYSTEM_PROMPT
|
|
210
|
+
.replace(/\{\{teamId\}\}/g, team.id)
|
|
211
|
+
.replace(/\{\{goal\}\}/g, team.goal)
|
|
212
|
+
.replace(/\{\{repoRoot\}\}/g, team.repoRoot)
|
|
213
|
+
.replace(/\{\{baseBranch\}\}/g, team.baseBranch)
|
|
214
|
+
.replace(/\{\{teamBranch\}\}/g, team.teamBranch)
|
|
215
|
+
.replace(/\{\{apiBase\}\}/g, apiBase)
|
|
216
|
+
.replace(/\{\{authToken\}\}/g, authToken)
|
|
217
|
+
.replace(/\{\{availableAgents\}\}/g, agentList)
|
|
218
|
+
.replace(/\{\{suggestedAgents\}\}/g, team.suggestedAgents.join(", ") || "none");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function ensureCoordinatorAgent(): string {
|
|
222
|
+
const agents = agentStore.listAgents();
|
|
223
|
+
const existing = agents.find((a) => a.name === "Team Coordinator");
|
|
224
|
+
if (existing) return existing.id;
|
|
225
|
+
|
|
226
|
+
const config = {
|
|
227
|
+
name: "Team Coordinator",
|
|
228
|
+
description: "Coordinates parallel agent teams. Breaks goals into tasks, creates worktrees, assigns agents, monitors progress, and merges results.",
|
|
229
|
+
backendType: "claude" as const,
|
|
230
|
+
model: "claude-sonnet-4-20250514",
|
|
231
|
+
permissionMode: "bypassPermissions",
|
|
232
|
+
prompt: "System prompt is injected at runtime via team-service.buildCoordinatorPrompt()",
|
|
233
|
+
enabled: true,
|
|
234
|
+
cwd: "",
|
|
235
|
+
version: 1 as const,
|
|
236
|
+
};
|
|
237
|
+
const agent = agentStore.createAgent(config);
|
|
238
|
+
return agent.id;
|
|
239
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
mkdirSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { HEYHANK_HOME } from "./paths.js";
|
|
10
|
+
import { atomicWriteFileSync } from "./fs-utils.js";
|
|
11
|
+
import type { Team } from "./team-types.js";
|
|
12
|
+
|
|
13
|
+
const TEAMS_DIR = join(HEYHANK_HOME, "teams");
|
|
14
|
+
|
|
15
|
+
function ensureDir(): void {
|
|
16
|
+
mkdirSync(TEAMS_DIR, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function filePath(id: string): string {
|
|
20
|
+
return join(TEAMS_DIR, `${id}.json`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createTeam(team: Team): void {
|
|
24
|
+
ensureDir();
|
|
25
|
+
atomicWriteFileSync(filePath(team.id), JSON.stringify(team, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getTeam(teamId: string): Team | null {
|
|
29
|
+
ensureDir();
|
|
30
|
+
try {
|
|
31
|
+
const raw = readFileSync(filePath(teamId), "utf-8");
|
|
32
|
+
return JSON.parse(raw) as Team;
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function updateTeam(teamId: string, updates: Partial<Team>): Team | null {
|
|
39
|
+
const existing = getTeam(teamId);
|
|
40
|
+
if (!existing) return null;
|
|
41
|
+
const updated = { ...existing, ...updates };
|
|
42
|
+
atomicWriteFileSync(filePath(teamId), JSON.stringify(updated, null, 2));
|
|
43
|
+
return updated;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function listTeams(): Team[] {
|
|
47
|
+
ensureDir();
|
|
48
|
+
try {
|
|
49
|
+
const files = readdirSync(TEAMS_DIR).filter((f) => f.endsWith(".json"));
|
|
50
|
+
const teams: Team[] = [];
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
try {
|
|
53
|
+
const raw = readFileSync(join(TEAMS_DIR, file), "utf-8");
|
|
54
|
+
teams.push(JSON.parse(raw));
|
|
55
|
+
} catch {
|
|
56
|
+
// Skip corrupt files
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
teams.sort((a, b) => b.createdAt - a.createdAt);
|
|
60
|
+
return teams;
|
|
61
|
+
} catch {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function deleteTeam(teamId: string): boolean {
|
|
67
|
+
ensureDir();
|
|
68
|
+
if (!existsSync(filePath(teamId))) return false;
|
|
69
|
+
try {
|
|
70
|
+
unlinkSync(filePath(teamId));
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type TeamState = "pending" | "planning" | "running" | "merging" | "completed" | "failed";
|
|
2
|
+
export type TaskState = "pending" | "assigned" | "running" | "completed" | "failed";
|
|
3
|
+
|
|
4
|
+
export interface TeamTask {
|
|
5
|
+
id: string;
|
|
6
|
+
description: string;
|
|
7
|
+
agentId: string | null;
|
|
8
|
+
agentName: string | null;
|
|
9
|
+
sessionId: string | null;
|
|
10
|
+
branch: string;
|
|
11
|
+
worktreePath: string | null;
|
|
12
|
+
state: TaskState;
|
|
13
|
+
dependsOn: string[];
|
|
14
|
+
startedAt?: number;
|
|
15
|
+
completedAt?: number;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface Team {
|
|
20
|
+
id: string;
|
|
21
|
+
goal: string;
|
|
22
|
+
state: TeamState;
|
|
23
|
+
coordinatorSessionId: string | null;
|
|
24
|
+
tasks: TeamTask[];
|
|
25
|
+
suggestedAgents: string[];
|
|
26
|
+
repoRoot: string;
|
|
27
|
+
baseBranch: string;
|
|
28
|
+
teamBranch: string;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
updatedAt: number;
|
|
31
|
+
completedAt?: number;
|
|
32
|
+
result?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TeamStatus {
|
|
37
|
+
id: string;
|
|
38
|
+
goal: string;
|
|
39
|
+
state: TeamState;
|
|
40
|
+
tasksTotal: number;
|
|
41
|
+
tasksCompleted: number;
|
|
42
|
+
tasksFailed: number;
|
|
43
|
+
tasksRunning: number;
|
|
44
|
+
tasks: Array<{
|
|
45
|
+
id: string;
|
|
46
|
+
description: string;
|
|
47
|
+
agentName: string | null;
|
|
48
|
+
state: TaskState;
|
|
49
|
+
}>;
|
|
50
|
+
result?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
}
|