tide-commander 0.52.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 +364 -0
- package/dist/assets/characters/Textures/colormap.png +0 -0
- package/dist/assets/characters/character-female-a.glb +0 -0
- package/dist/assets/characters/character-female-b.glb +0 -0
- package/dist/assets/characters/character-female-c.glb +0 -0
- package/dist/assets/characters/character-female-d.glb +0 -0
- package/dist/assets/characters/character-female-e.glb +0 -0
- package/dist/assets/characters/character-female-f.glb +0 -0
- package/dist/assets/characters/character-male-a-processed.gltf +11862 -0
- package/dist/assets/characters/character-male-a.glb +0 -0
- package/dist/assets/characters/character-male-b.glb +0 -0
- package/dist/assets/characters/character-male-c.glb +0 -0
- package/dist/assets/characters/character-male-d.glb +0 -0
- package/dist/assets/characters/character-male-e.glb +0 -0
- package/dist/assets/characters/character-male-f.glb +0 -0
- package/dist/assets/icons/icon-192.png +0 -0
- package/dist/assets/icons/icon-512.png +0 -0
- package/dist/assets/landing-Cc0MDBAK.css +1 -0
- package/dist/assets/main-BIpLsrUu.css +1 -0
- package/dist/assets/main-DMTRw3br.js +276 -0
- package/dist/assets/textures/concrete_floor_worn_001_diff_1k.jpg +0 -0
- package/dist/assets/textures/logo-blanco.png +0 -0
- package/dist/assets/vendor-react-uS-d4TUT.js +17 -0
- package/dist/assets/vendor-three-4iQNXcoo.js +3828 -0
- package/dist/assets/web-BZdi2lG9.js +1 -0
- package/dist/assets/web-yHsOO1Qb.js +1 -0
- package/dist/index.html +38 -0
- package/dist/manifest.json +39 -0
- package/dist/src/packages/landing/index.html +463 -0
- package/dist/src/packages/server/app.js +87 -0
- package/dist/src/packages/server/auth/index.js +121 -0
- package/dist/src/packages/server/claude/backend.js +578 -0
- package/dist/src/packages/server/claude/index.js +8 -0
- package/dist/src/packages/server/claude/runner/internal-events.js +22 -0
- package/dist/src/packages/server/claude/runner/process-lifecycle.js +208 -0
- package/dist/src/packages/server/claude/runner/recovery-store.js +72 -0
- package/dist/src/packages/server/claude/runner/resource-monitor.js +51 -0
- package/dist/src/packages/server/claude/runner/restart-policy.js +69 -0
- package/dist/src/packages/server/claude/runner/stdout-pipeline.js +153 -0
- package/dist/src/packages/server/claude/runner/watchdog.js +114 -0
- package/dist/src/packages/server/claude/runner.js +310 -0
- package/dist/src/packages/server/claude/session-loader.js +898 -0
- package/dist/src/packages/server/claude/types.js +5 -0
- package/dist/src/packages/server/cli.js +113 -0
- package/dist/src/packages/server/codex/backend.js +119 -0
- package/dist/src/packages/server/codex/index.js +2 -0
- package/dist/src/packages/server/codex/json-event-parser.js +612 -0
- package/dist/src/packages/server/data/builtin-skills/bitbucket-pr.js +298 -0
- package/dist/src/packages/server/data/builtin-skills/full-notifications.js +49 -0
- package/dist/src/packages/server/data/builtin-skills/git-captain.js +304 -0
- package/dist/src/packages/server/data/builtin-skills/index.js +61 -0
- package/dist/src/packages/server/data/builtin-skills/pm2-logs.js +354 -0
- package/dist/src/packages/server/data/builtin-skills/send-message-to-agent.js +51 -0
- package/dist/src/packages/server/data/builtin-skills/server-logs.js +124 -0
- package/dist/src/packages/server/data/builtin-skills/streaming-exec.js +94 -0
- package/dist/src/packages/server/data/builtin-skills/types.js +4 -0
- package/dist/src/packages/server/data/builtin-skills.js +6 -0
- package/dist/src/packages/server/data/index.js +890 -0
- package/dist/src/packages/server/data/snapshots.js +371 -0
- package/dist/src/packages/server/index.js +96 -0
- package/dist/src/packages/server/prompts/tide-commander.js +13 -0
- package/dist/src/packages/server/routes/agents.js +406 -0
- package/dist/src/packages/server/routes/config.js +347 -0
- package/dist/src/packages/server/routes/custom-models.js +170 -0
- package/dist/src/packages/server/routes/exec.js +269 -0
- package/dist/src/packages/server/routes/files.js +995 -0
- package/dist/src/packages/server/routes/index.js +38 -0
- package/dist/src/packages/server/routes/notifications.js +81 -0
- package/dist/src/packages/server/routes/permissions.js +115 -0
- package/dist/src/packages/server/routes/snapshots.js +224 -0
- package/dist/src/packages/server/routes/stt.js +99 -0
- package/dist/src/packages/server/routes/tts.js +166 -0
- package/dist/src/packages/server/routes/voice-assistant.js +310 -0
- package/dist/src/packages/server/runtime/claude-runtime-provider.js +10 -0
- package/dist/src/packages/server/runtime/codex-runtime-provider.js +11 -0
- package/dist/src/packages/server/runtime/index.js +2 -0
- package/dist/src/packages/server/runtime/types.js +6 -0
- package/dist/src/packages/server/services/agent-lifecycle-service.js +82 -0
- package/dist/src/packages/server/services/agent-service.js +410 -0
- package/dist/src/packages/server/services/boss-message-service.js +430 -0
- package/dist/src/packages/server/services/boss-service.js +553 -0
- package/dist/src/packages/server/services/building-service.js +867 -0
- package/dist/src/packages/server/services/claude-service.js +5 -0
- package/dist/src/packages/server/services/custom-class-service.js +323 -0
- package/dist/src/packages/server/services/database-service.js +914 -0
- package/dist/src/packages/server/services/docker-service.js +865 -0
- package/dist/src/packages/server/services/fileTracker.js +242 -0
- package/dist/src/packages/server/services/index.js +21 -0
- package/dist/src/packages/server/services/permission-service.js +258 -0
- package/dist/src/packages/server/services/pm2-service.js +435 -0
- package/dist/src/packages/server/services/runtime-command-execution.js +168 -0
- package/dist/src/packages/server/services/runtime-events.js +357 -0
- package/dist/src/packages/server/services/runtime-service.js +308 -0
- package/dist/src/packages/server/services/runtime-status-sync.js +104 -0
- package/dist/src/packages/server/services/runtime-subagents.js +50 -0
- package/dist/src/packages/server/services/runtime-watchdog.js +74 -0
- package/dist/src/packages/server/services/secrets-service.js +206 -0
- package/dist/src/packages/server/services/skill-service.js +508 -0
- package/dist/src/packages/server/services/subordinate-context-service.js +223 -0
- package/dist/src/packages/server/services/supervisor-claude.js +132 -0
- package/dist/src/packages/server/services/supervisor-prompts.js +80 -0
- package/dist/src/packages/server/services/supervisor-service.js +659 -0
- package/dist/src/packages/server/services/work-plan-service.js +476 -0
- package/dist/src/packages/server/setup.js +86 -0
- package/dist/src/packages/server/utils/index.js +4 -0
- package/dist/src/packages/server/utils/logger.js +302 -0
- package/dist/src/packages/server/utils/string.js +39 -0
- package/dist/src/packages/server/utils/tool-formatting.js +139 -0
- package/dist/src/packages/server/utils/unicode.js +46 -0
- package/dist/src/packages/server/websocket/handler.js +290 -0
- package/dist/src/packages/server/websocket/handlers/agent-handler.js +515 -0
- package/dist/src/packages/server/websocket/handlers/boss-handler.js +116 -0
- package/dist/src/packages/server/websocket/handlers/boss-response-handler.js +250 -0
- package/dist/src/packages/server/websocket/handlers/building-handler.js +298 -0
- package/dist/src/packages/server/websocket/handlers/command-handler.js +217 -0
- package/dist/src/packages/server/websocket/handlers/custom-class-handler.js +68 -0
- package/dist/src/packages/server/websocket/handlers/database-handler.js +223 -0
- package/dist/src/packages/server/websocket/handlers/notification-handler.js +25 -0
- package/dist/src/packages/server/websocket/handlers/permission-handler.js +21 -0
- package/dist/src/packages/server/websocket/handlers/secrets-handler.js +61 -0
- package/dist/src/packages/server/websocket/handlers/skill-handler.js +148 -0
- package/dist/src/packages/server/websocket/handlers/supervisor-handler.js +44 -0
- package/dist/src/packages/server/websocket/handlers/sync-handler.js +19 -0
- package/dist/src/packages/server/websocket/handlers/types.js +4 -0
- package/dist/src/packages/server/websocket/listeners/boss-listeners.js +21 -0
- package/dist/src/packages/server/websocket/listeners/index.js +32 -0
- package/dist/src/packages/server/websocket/listeners/permission-listeners.js +19 -0
- package/dist/src/packages/server/websocket/listeners/runtime-listeners.js +196 -0
- package/dist/src/packages/server/websocket/listeners/skill-listeners.js +51 -0
- package/dist/src/packages/server/websocket/listeners/supervisor-listeners.js +37 -0
- package/dist/src/packages/shared/agent-types.js +54 -0
- package/dist/src/packages/shared/building-types.js +43 -0
- package/dist/src/packages/shared/common-types.js +1 -0
- package/dist/src/packages/shared/database-types.js +8 -0
- package/dist/src/packages/shared/types/snapshot.js +7 -0
- package/dist/src/packages/shared/types.js +12 -0
- package/dist/src/packages/shared/websocket-messages.js +1 -0
- package/dist/sw.js +37 -0
- package/package.json +90 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work Plan Service
|
|
3
|
+
* Manages work plans created by boss agents, including parsing, storage, and execution
|
|
4
|
+
*/
|
|
5
|
+
import * as agentService from './agent-service.js';
|
|
6
|
+
import * as bossService from './boss-service.js';
|
|
7
|
+
import { logger, generateId } from '../utils/index.js';
|
|
8
|
+
const log = logger.boss || console;
|
|
9
|
+
// In-memory storage for work plans and analysis requests
|
|
10
|
+
const workPlans = new Map();
|
|
11
|
+
const analysisRequests = new Map();
|
|
12
|
+
const listeners = new Set();
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Event System
|
|
15
|
+
// ============================================================================
|
|
16
|
+
export function subscribe(listener) {
|
|
17
|
+
listeners.add(listener);
|
|
18
|
+
return () => listeners.delete(listener);
|
|
19
|
+
}
|
|
20
|
+
function emit(event, data) {
|
|
21
|
+
listeners.forEach((listener) => listener(event, data));
|
|
22
|
+
}
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Work Plan Management
|
|
25
|
+
// ============================================================================
|
|
26
|
+
/**
|
|
27
|
+
* Create a work plan from a draft (parsed from boss response)
|
|
28
|
+
*/
|
|
29
|
+
export function createWorkPlan(bossId, draft) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const planId = generateId();
|
|
32
|
+
// Convert draft phases to full phases with proper status
|
|
33
|
+
const phases = draft.phases.map((phase) => ({
|
|
34
|
+
id: phase.id,
|
|
35
|
+
name: phase.name,
|
|
36
|
+
execution: phase.execution,
|
|
37
|
+
dependsOn: phase.dependsOn,
|
|
38
|
+
status: 'pending',
|
|
39
|
+
tasks: phase.tasks.map((task) => ({
|
|
40
|
+
id: task.id,
|
|
41
|
+
description: task.description,
|
|
42
|
+
suggestedClass: task.suggestedClass,
|
|
43
|
+
assignedAgentId: task.assignToAgent,
|
|
44
|
+
priority: task.priority,
|
|
45
|
+
blockedBy: task.blockedBy,
|
|
46
|
+
status: 'pending',
|
|
47
|
+
})),
|
|
48
|
+
}));
|
|
49
|
+
// Calculate total tasks and parallelizable tasks
|
|
50
|
+
const allTasks = phases.flatMap((p) => p.tasks);
|
|
51
|
+
const parallelPhases = phases.filter((p) => p.execution === 'parallel');
|
|
52
|
+
const parallelizableTasks = parallelPhases.flatMap((p) => p.tasks.map((t) => t.id));
|
|
53
|
+
const workPlan = {
|
|
54
|
+
id: planId,
|
|
55
|
+
name: draft.name,
|
|
56
|
+
description: draft.description,
|
|
57
|
+
phases,
|
|
58
|
+
createdBy: bossId,
|
|
59
|
+
createdAt: now,
|
|
60
|
+
updatedAt: now,
|
|
61
|
+
status: 'draft',
|
|
62
|
+
totalTasks: allTasks.length,
|
|
63
|
+
completedTasks: 0,
|
|
64
|
+
parallelizableTasks,
|
|
65
|
+
};
|
|
66
|
+
workPlans.set(planId, workPlan);
|
|
67
|
+
log.log?.(`📋 Created work plan "${workPlan.name}" with ${workPlan.totalTasks} tasks`);
|
|
68
|
+
emit('work_plan_created', workPlan);
|
|
69
|
+
return workPlan;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get a work plan by ID
|
|
73
|
+
*/
|
|
74
|
+
export function getWorkPlan(planId) {
|
|
75
|
+
return workPlans.get(planId) || null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get all work plans for a boss
|
|
79
|
+
*/
|
|
80
|
+
export function getWorkPlansForBoss(bossId) {
|
|
81
|
+
return Array.from(workPlans.values()).filter((p) => p.createdBy === bossId);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Get all work plans
|
|
85
|
+
*/
|
|
86
|
+
export function getAllWorkPlans() {
|
|
87
|
+
return Array.from(workPlans.values());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Approve a work plan for execution
|
|
91
|
+
*/
|
|
92
|
+
export function approveWorkPlan(planId) {
|
|
93
|
+
const plan = workPlans.get(planId);
|
|
94
|
+
if (!plan)
|
|
95
|
+
return null;
|
|
96
|
+
if (plan.status !== 'draft') {
|
|
97
|
+
log.log?.(`⚠️ Cannot approve plan "${plan.name}" - status is ${plan.status}`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
plan.status = 'approved';
|
|
101
|
+
plan.updatedAt = Date.now();
|
|
102
|
+
emit('work_plan_updated', plan);
|
|
103
|
+
log.log?.(`✅ Approved work plan "${plan.name}"`);
|
|
104
|
+
return plan;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Start executing a work plan
|
|
108
|
+
*/
|
|
109
|
+
export function executeWorkPlan(planId) {
|
|
110
|
+
const plan = workPlans.get(planId);
|
|
111
|
+
if (!plan)
|
|
112
|
+
return null;
|
|
113
|
+
if (plan.status !== 'approved') {
|
|
114
|
+
log.log?.(`⚠️ Cannot execute plan "${plan.name}" - status is ${plan.status}`);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
plan.status = 'executing';
|
|
118
|
+
plan.updatedAt = Date.now();
|
|
119
|
+
// Start first phase(s) that have no dependencies
|
|
120
|
+
const readyPhases = plan.phases.filter((p) => p.dependsOn.length === 0);
|
|
121
|
+
for (const phase of readyPhases) {
|
|
122
|
+
startPhase(plan, phase);
|
|
123
|
+
}
|
|
124
|
+
emit('work_plan_updated', plan);
|
|
125
|
+
log.log?.(`🚀 Started executing work plan "${plan.name}"`);
|
|
126
|
+
return plan;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Pause a work plan
|
|
130
|
+
*/
|
|
131
|
+
export function pauseWorkPlan(planId) {
|
|
132
|
+
const plan = workPlans.get(planId);
|
|
133
|
+
if (!plan)
|
|
134
|
+
return null;
|
|
135
|
+
if (plan.status !== 'executing') {
|
|
136
|
+
log.log?.(`⚠️ Cannot pause plan "${plan.name}" - status is ${plan.status}`);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
plan.status = 'paused';
|
|
140
|
+
plan.updatedAt = Date.now();
|
|
141
|
+
emit('work_plan_updated', plan);
|
|
142
|
+
log.log?.(`⏸️ Paused work plan "${plan.name}"`);
|
|
143
|
+
return plan;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Cancel a work plan
|
|
147
|
+
*/
|
|
148
|
+
export function cancelWorkPlan(planId) {
|
|
149
|
+
const plan = workPlans.get(planId);
|
|
150
|
+
if (!plan)
|
|
151
|
+
return null;
|
|
152
|
+
plan.status = 'cancelled';
|
|
153
|
+
plan.updatedAt = Date.now();
|
|
154
|
+
// Cancel all pending/in_progress tasks
|
|
155
|
+
for (const phase of plan.phases) {
|
|
156
|
+
if (phase.status === 'pending' || phase.status === 'in_progress') {
|
|
157
|
+
phase.status = 'cancelled';
|
|
158
|
+
}
|
|
159
|
+
for (const task of phase.tasks) {
|
|
160
|
+
if (task.status === 'pending' || task.status === 'in_progress') {
|
|
161
|
+
task.status = 'cancelled';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
emit('work_plan_updated', plan);
|
|
166
|
+
log.log?.(`❌ Cancelled work plan "${plan.name}"`);
|
|
167
|
+
return plan;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Delete a work plan
|
|
171
|
+
*/
|
|
172
|
+
export function deleteWorkPlan(planId) {
|
|
173
|
+
const plan = workPlans.get(planId);
|
|
174
|
+
if (!plan)
|
|
175
|
+
return false;
|
|
176
|
+
workPlans.delete(planId);
|
|
177
|
+
emit('work_plan_deleted', { id: planId });
|
|
178
|
+
log.log?.(`🗑️ Deleted work plan "${plan.name}"`);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Start a phase in a work plan
|
|
183
|
+
*/
|
|
184
|
+
function startPhase(plan, phase) {
|
|
185
|
+
phase.status = 'in_progress';
|
|
186
|
+
phase.startedAt = Date.now();
|
|
187
|
+
// Start tasks that have no blockers
|
|
188
|
+
const readyTasks = phase.tasks.filter((t) => t.blockedBy.length === 0);
|
|
189
|
+
if (phase.execution === 'parallel') {
|
|
190
|
+
// Start all ready tasks in parallel
|
|
191
|
+
for (const task of readyTasks) {
|
|
192
|
+
startTask(plan, phase, task);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// Sequential - start only the first ready task
|
|
197
|
+
if (readyTasks.length > 0) {
|
|
198
|
+
startTask(plan, phase, readyTasks[0]);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Start a task - auto-assign agent if needed and delegate
|
|
204
|
+
*/
|
|
205
|
+
function startTask(plan, phase, task) {
|
|
206
|
+
task.status = 'in_progress';
|
|
207
|
+
task.startedAt = Date.now();
|
|
208
|
+
// Auto-assign agent if not already assigned
|
|
209
|
+
if (!task.assignedAgentId) {
|
|
210
|
+
const agent = findBestAgentForTask(plan.createdBy, task);
|
|
211
|
+
if (agent) {
|
|
212
|
+
task.assignedAgentId = agent.id;
|
|
213
|
+
task.assignedAgentName = agent.name;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (task.assignedAgentId) {
|
|
217
|
+
const agent = agentService.getAgent(task.assignedAgentId);
|
|
218
|
+
task.assignedAgentName = agent?.name;
|
|
219
|
+
log.log?.(`🎯 Started task "${task.description}" → ${task.assignedAgentName || task.assignedAgentId}`);
|
|
220
|
+
// Emit delegation event for the handler to pick up
|
|
221
|
+
emit('task_started', {
|
|
222
|
+
planId: plan.id,
|
|
223
|
+
phaseId: phase.id,
|
|
224
|
+
task,
|
|
225
|
+
agentId: task.assignedAgentId,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
log.log?.(`⚠️ No agent available for task "${task.description}"`);
|
|
230
|
+
task.status = 'blocked';
|
|
231
|
+
}
|
|
232
|
+
plan.updatedAt = Date.now();
|
|
233
|
+
emit('work_plan_updated', plan);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Find the best available agent for a task based on class and availability
|
|
237
|
+
*/
|
|
238
|
+
function findBestAgentForTask(bossId, task) {
|
|
239
|
+
const subordinates = bossService.getSubordinates(bossId);
|
|
240
|
+
// First, try to find an idle agent of the suggested class
|
|
241
|
+
const idealAgent = subordinates.find((a) => a.class === task.suggestedClass && a.status === 'idle');
|
|
242
|
+
if (idealAgent) {
|
|
243
|
+
return { id: idealAgent.id, name: idealAgent.name };
|
|
244
|
+
}
|
|
245
|
+
// If no idle agent of the right class, find any idle agent
|
|
246
|
+
const anyIdleAgent = subordinates.find((a) => a.status === 'idle');
|
|
247
|
+
if (anyIdleAgent) {
|
|
248
|
+
return { id: anyIdleAgent.id, name: anyIdleAgent.name };
|
|
249
|
+
}
|
|
250
|
+
// If all agents are busy, find the one with lowest context usage of the right class
|
|
251
|
+
const sameClassAgents = subordinates.filter((a) => a.class === task.suggestedClass);
|
|
252
|
+
if (sameClassAgents.length > 0) {
|
|
253
|
+
const lowestContext = sameClassAgents.reduce((a, b) => (a.contextUsed / a.contextLimit) < (b.contextUsed / b.contextLimit) ? a : b);
|
|
254
|
+
return { id: lowestContext.id, name: lowestContext.name };
|
|
255
|
+
}
|
|
256
|
+
// Fallback to any agent with lowest context
|
|
257
|
+
if (subordinates.length > 0) {
|
|
258
|
+
const lowestContext = subordinates.reduce((a, b) => (a.contextUsed / a.contextLimit) < (b.contextUsed / b.contextLimit) ? a : b);
|
|
259
|
+
return { id: lowestContext.id, name: lowestContext.name };
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Mark a task as completed and check for next tasks/phases
|
|
265
|
+
*/
|
|
266
|
+
export function completeTask(planId, taskId, result) {
|
|
267
|
+
const plan = workPlans.get(planId);
|
|
268
|
+
if (!plan)
|
|
269
|
+
return null;
|
|
270
|
+
// Find the task
|
|
271
|
+
let targetTask = null;
|
|
272
|
+
let targetPhase = null;
|
|
273
|
+
for (const phase of plan.phases) {
|
|
274
|
+
const task = phase.tasks.find((t) => t.id === taskId);
|
|
275
|
+
if (task) {
|
|
276
|
+
targetTask = task;
|
|
277
|
+
targetPhase = phase;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (!targetTask || !targetPhase) {
|
|
282
|
+
log.log?.(`⚠️ Task ${taskId} not found in plan ${planId}`);
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
// Mark task complete
|
|
286
|
+
targetTask.status = 'completed';
|
|
287
|
+
targetTask.completedAt = Date.now();
|
|
288
|
+
targetTask.result = result;
|
|
289
|
+
plan.completedTasks++;
|
|
290
|
+
log.log?.(`✅ Completed task "${targetTask.description}"`);
|
|
291
|
+
// Check for tasks that were blocked by this one
|
|
292
|
+
for (const phase of plan.phases) {
|
|
293
|
+
for (const task of phase.tasks) {
|
|
294
|
+
if (task.status === 'pending' && task.blockedBy.includes(taskId)) {
|
|
295
|
+
// Remove this task from blockers
|
|
296
|
+
task.blockedBy = task.blockedBy.filter((id) => id !== taskId);
|
|
297
|
+
// If no more blockers and phase is in progress, start the task
|
|
298
|
+
if (task.blockedBy.length === 0 && phase.status === 'in_progress') {
|
|
299
|
+
if (phase.execution === 'parallel') {
|
|
300
|
+
startTask(plan, phase, task);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Sequential - only start if no other task is in progress
|
|
304
|
+
const inProgressTask = phase.tasks.find((t) => t.status === 'in_progress');
|
|
305
|
+
if (!inProgressTask) {
|
|
306
|
+
startTask(plan, phase, task);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Check if phase is complete
|
|
314
|
+
const allTasksComplete = targetPhase.tasks.every((t) => t.status === 'completed');
|
|
315
|
+
if (allTasksComplete) {
|
|
316
|
+
targetPhase.status = 'completed';
|
|
317
|
+
targetPhase.completedAt = Date.now();
|
|
318
|
+
log.log?.(`✅ Completed phase "${targetPhase.name}"`);
|
|
319
|
+
// Check for phases that depend on this one
|
|
320
|
+
for (const phase of plan.phases) {
|
|
321
|
+
if (phase.status === 'pending' && phase.dependsOn.includes(targetPhase.id)) {
|
|
322
|
+
// Remove this phase from dependencies
|
|
323
|
+
phase.dependsOn = phase.dependsOn.filter((id) => id !== targetPhase.id);
|
|
324
|
+
// If no more dependencies, start the phase
|
|
325
|
+
if (phase.dependsOn.length === 0) {
|
|
326
|
+
startPhase(plan, phase);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// Check if entire plan is complete
|
|
332
|
+
const allPhasesComplete = plan.phases.every((p) => p.status === 'completed');
|
|
333
|
+
if (allPhasesComplete) {
|
|
334
|
+
plan.status = 'completed';
|
|
335
|
+
log.log?.(`🎉 Completed work plan "${plan.name}"`);
|
|
336
|
+
}
|
|
337
|
+
plan.updatedAt = Date.now();
|
|
338
|
+
emit('work_plan_updated', plan);
|
|
339
|
+
return plan;
|
|
340
|
+
}
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Analysis Request Management
|
|
343
|
+
// ============================================================================
|
|
344
|
+
/**
|
|
345
|
+
* Create an analysis request from a draft (parsed from boss response)
|
|
346
|
+
*/
|
|
347
|
+
export function createAnalysisRequest(bossId, draft) {
|
|
348
|
+
const now = Date.now();
|
|
349
|
+
const requestId = generateId();
|
|
350
|
+
const agent = agentService.getAgent(draft.targetAgent);
|
|
351
|
+
const request = {
|
|
352
|
+
id: requestId,
|
|
353
|
+
targetAgentId: draft.targetAgent,
|
|
354
|
+
targetAgentName: agent?.name,
|
|
355
|
+
query: draft.query,
|
|
356
|
+
focus: draft.focus,
|
|
357
|
+
status: 'pending',
|
|
358
|
+
requestedAt: now,
|
|
359
|
+
};
|
|
360
|
+
analysisRequests.set(requestId, request);
|
|
361
|
+
log.log?.(`🔍 Created analysis request for ${request.targetAgentName || request.targetAgentId}`);
|
|
362
|
+
emit('analysis_request_created', request);
|
|
363
|
+
return request;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get an analysis request by ID
|
|
367
|
+
*/
|
|
368
|
+
export function getAnalysisRequest(requestId) {
|
|
369
|
+
return analysisRequests.get(requestId) || null;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Start an analysis request (send to agent)
|
|
373
|
+
*/
|
|
374
|
+
export function startAnalysisRequest(requestId) {
|
|
375
|
+
const request = analysisRequests.get(requestId);
|
|
376
|
+
if (!request)
|
|
377
|
+
return null;
|
|
378
|
+
request.status = 'in_progress';
|
|
379
|
+
emit('analysis_request_started', request);
|
|
380
|
+
return request;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Complete an analysis request with results
|
|
384
|
+
*/
|
|
385
|
+
export function completeAnalysisRequest(requestId, result) {
|
|
386
|
+
const request = analysisRequests.get(requestId);
|
|
387
|
+
if (!request)
|
|
388
|
+
return null;
|
|
389
|
+
request.status = 'completed';
|
|
390
|
+
request.result = result;
|
|
391
|
+
request.completedAt = Date.now();
|
|
392
|
+
log.log?.(`✅ Completed analysis request from ${request.targetAgentName || request.targetAgentId}`);
|
|
393
|
+
emit('analysis_request_completed', request);
|
|
394
|
+
return request;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get all pending analysis requests for a boss's subordinates
|
|
398
|
+
*/
|
|
399
|
+
export function getPendingAnalysisRequests(bossId) {
|
|
400
|
+
const subordinateIds = bossService.getSubordinates(bossId).map((a) => a.id);
|
|
401
|
+
return Array.from(analysisRequests.values()).filter((r) => r.status === 'pending' && subordinateIds.includes(r.targetAgentId));
|
|
402
|
+
}
|
|
403
|
+
// ============================================================================
|
|
404
|
+
// Parsing Utilities
|
|
405
|
+
// ============================================================================
|
|
406
|
+
/**
|
|
407
|
+
* Parse work-plan block from boss response
|
|
408
|
+
*/
|
|
409
|
+
export function parseWorkPlanBlock(content) {
|
|
410
|
+
const match = content.match(/```work-plan\s*([\s\S]*?)```/);
|
|
411
|
+
if (!match)
|
|
412
|
+
return null;
|
|
413
|
+
try {
|
|
414
|
+
const json = match[1].trim();
|
|
415
|
+
const draft = JSON.parse(json);
|
|
416
|
+
// Validate required fields
|
|
417
|
+
if (!draft.name || !draft.phases || !Array.isArray(draft.phases)) {
|
|
418
|
+
log.log?.('⚠️ Invalid work-plan: missing required fields');
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
return draft;
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
log.log?.('⚠️ Failed to parse work-plan JSON:', err);
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Parse analysis-request block from boss response
|
|
430
|
+
*/
|
|
431
|
+
export function parseAnalysisRequestBlock(content) {
|
|
432
|
+
const match = content.match(/```analysis-request\s*([\s\S]*?)```/);
|
|
433
|
+
if (!match)
|
|
434
|
+
return [];
|
|
435
|
+
try {
|
|
436
|
+
const json = match[1].trim();
|
|
437
|
+
const drafts = JSON.parse(json);
|
|
438
|
+
if (!Array.isArray(drafts)) {
|
|
439
|
+
return [drafts];
|
|
440
|
+
}
|
|
441
|
+
// Validate each request
|
|
442
|
+
return drafts.filter((d) => d.targetAgent && d.query);
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
log.log?.('⚠️ Failed to parse analysis-request JSON:', err);
|
|
446
|
+
return [];
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// ============================================================================
|
|
450
|
+
// Conversion Utilities
|
|
451
|
+
// ============================================================================
|
|
452
|
+
/**
|
|
453
|
+
* Convert work plan tasks to delegation format for immediate execution
|
|
454
|
+
*/
|
|
455
|
+
export function convertTasksToDelegations(plan) {
|
|
456
|
+
const delegations = [];
|
|
457
|
+
// Get tasks that are ready to execute (in phases with no dependencies)
|
|
458
|
+
for (const phase of plan.phases) {
|
|
459
|
+
if (phase.dependsOn.length > 0)
|
|
460
|
+
continue; // Skip phases with dependencies
|
|
461
|
+
for (const task of phase.tasks) {
|
|
462
|
+
if (task.blockedBy.length > 0)
|
|
463
|
+
continue; // Skip tasks with blockers
|
|
464
|
+
if (!task.assignedAgentId)
|
|
465
|
+
continue; // Skip unassigned tasks
|
|
466
|
+
delegations.push({
|
|
467
|
+
selectedAgentId: task.assignedAgentId,
|
|
468
|
+
selectedAgentName: task.assignedAgentName || task.assignedAgentId,
|
|
469
|
+
taskCommand: task.description,
|
|
470
|
+
reasoning: `Work plan task: ${phase.name} (${phase.execution})`,
|
|
471
|
+
confidence: task.priority === 'high' ? 'high' : task.priority === 'medium' ? 'medium' : 'low',
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return delegations;
|
|
476
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Tide Commander - Setup Script
|
|
4
|
+
* Run with: npm run setup
|
|
5
|
+
*/
|
|
6
|
+
import { execSync } from 'child_process';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
const TIDE_HOOKS_DIR = path.join(os.homedir(), '.tide-commander', 'hooks');
|
|
11
|
+
const CLAUDE_SETTINGS_DIR = path.join(os.homedir(), '.claude');
|
|
12
|
+
const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
|
13
|
+
console.log('🌊 Tide Commander - Setup');
|
|
14
|
+
console.log('='.repeat(40));
|
|
15
|
+
// Check for dependencies
|
|
16
|
+
console.log('\nChecking dependencies...');
|
|
17
|
+
// Check jq (for hook script)
|
|
18
|
+
try {
|
|
19
|
+
execSync('which jq', { stdio: 'pipe' });
|
|
20
|
+
console.log('✓ jq is installed');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
console.error('✗ jq is not installed');
|
|
24
|
+
console.error(' Install with: sudo apt install jq (or brew install jq)');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
// Check Claude Code
|
|
28
|
+
try {
|
|
29
|
+
execSync('which claude', { stdio: 'pipe' });
|
|
30
|
+
console.log('✓ Claude Code CLI is installed');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
console.error('✗ Claude Code CLI is not installed');
|
|
34
|
+
console.error(' Install from: https://github.com/anthropics/claude-code');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// Create directories
|
|
38
|
+
console.log('\nSetting up directories...');
|
|
39
|
+
fs.mkdirSync(TIDE_HOOKS_DIR, { recursive: true });
|
|
40
|
+
console.log(`✓ Created ${TIDE_HOOKS_DIR}`);
|
|
41
|
+
fs.mkdirSync(CLAUDE_SETTINGS_DIR, { recursive: true });
|
|
42
|
+
console.log(`✓ Created ${CLAUDE_SETTINGS_DIR}`);
|
|
43
|
+
// Copy hook script
|
|
44
|
+
console.log('\nInstalling hooks...');
|
|
45
|
+
const hookScriptSource = path.join(process.cwd(), 'hooks', 'tide-hook.sh');
|
|
46
|
+
const hookScriptDest = path.join(TIDE_HOOKS_DIR, 'tide-hook.sh');
|
|
47
|
+
if (fs.existsSync(hookScriptSource)) {
|
|
48
|
+
fs.copyFileSync(hookScriptSource, hookScriptDest);
|
|
49
|
+
fs.chmodSync(hookScriptDest, '755');
|
|
50
|
+
console.log(`✓ Installed hook script to ${hookScriptDest}`);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
console.error(`✗ Hook script not found at ${hookScriptSource}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
// Update Claude settings
|
|
57
|
+
console.log('\nConfiguring Claude Code...');
|
|
58
|
+
let settings = {};
|
|
59
|
+
if (fs.existsSync(CLAUDE_SETTINGS_FILE)) {
|
|
60
|
+
// Backup existing settings
|
|
61
|
+
const backupFile = `${CLAUDE_SETTINGS_FILE}.backup`;
|
|
62
|
+
fs.copyFileSync(CLAUDE_SETTINGS_FILE, backupFile);
|
|
63
|
+
console.log(`✓ Backed up settings to ${backupFile}`);
|
|
64
|
+
try {
|
|
65
|
+
settings = JSON.parse(fs.readFileSync(CLAUDE_SETTINGS_FILE, 'utf-8'));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.warn(' Warning: Could not parse existing settings, starting fresh');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Add hooks
|
|
72
|
+
settings.hooks = {
|
|
73
|
+
...settings.hooks,
|
|
74
|
+
PreToolUse: hookScriptDest,
|
|
75
|
+
PostToolUse: hookScriptDest,
|
|
76
|
+
Stop: hookScriptDest,
|
|
77
|
+
UserPromptSubmit: hookScriptDest,
|
|
78
|
+
};
|
|
79
|
+
fs.writeFileSync(CLAUDE_SETTINGS_FILE, JSON.stringify(settings, null, 2));
|
|
80
|
+
console.log(`✓ Updated ${CLAUDE_SETTINGS_FILE}`);
|
|
81
|
+
// Done
|
|
82
|
+
console.log('\n' + '='.repeat(40));
|
|
83
|
+
console.log('🎉 Setup complete!\n');
|
|
84
|
+
console.log('To start Tide Commander:');
|
|
85
|
+
console.log(' npm run dev\n');
|
|
86
|
+
console.log('Then open http://localhost:5173 in your browser (or your configured VITE_PORT)');
|