@girardmedia/bootspring 1.2.0 → 2.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/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -0,0 +1,2057 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Agent Collaboration Framework
|
|
3
|
+
*
|
|
4
|
+
* Enables multi-agent task delegation with handoff protocol.
|
|
5
|
+
* Allows agents to collaborate on complex tasks by delegating
|
|
6
|
+
* subtasks to specialized agents.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Multi-agent task delegation with priority queues
|
|
10
|
+
* - Handoff protocol with context preservation
|
|
11
|
+
* - Primary agent leadership with supporting agent coordination
|
|
12
|
+
* - Dependency-aware task scheduling
|
|
13
|
+
* - Agent capability matching
|
|
14
|
+
* - Real-time collaboration state tracking
|
|
15
|
+
*
|
|
16
|
+
* @package bootspring
|
|
17
|
+
* @module intelligence/agent-collab
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
const telemetry = require('../core/telemetry');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Task priority levels
|
|
26
|
+
*/
|
|
27
|
+
const TASK_PRIORITY = {
|
|
28
|
+
CRITICAL: 'critical',
|
|
29
|
+
HIGH: 'high',
|
|
30
|
+
MEDIUM: 'medium',
|
|
31
|
+
LOW: 'low'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Task status values
|
|
36
|
+
*/
|
|
37
|
+
const TASK_STATUS = {
|
|
38
|
+
PENDING: 'pending',
|
|
39
|
+
IN_PROGRESS: 'in_progress',
|
|
40
|
+
BLOCKED: 'blocked',
|
|
41
|
+
COMPLETE: 'complete',
|
|
42
|
+
FAILED: 'failed'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Handoff status values
|
|
47
|
+
*/
|
|
48
|
+
const HANDOFF_STATUS = {
|
|
49
|
+
PENDING: 'pending',
|
|
50
|
+
ACCEPTED: 'accepted',
|
|
51
|
+
REJECTED: 'rejected',
|
|
52
|
+
COMPLETED: 'completed'
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Agent roles in collaboration
|
|
57
|
+
*/
|
|
58
|
+
const AGENT_ROLE = {
|
|
59
|
+
PRIMARY: 'primary',
|
|
60
|
+
SUPPORTING: 'supporting',
|
|
61
|
+
SPECIALIST: 'specialist'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get bootspring directory
|
|
66
|
+
*/
|
|
67
|
+
function getBootspringDir() {
|
|
68
|
+
let bootspringDir = __dirname;
|
|
69
|
+
while (!fs.existsSync(path.join(bootspringDir, 'package.json')) && bootspringDir !== '/') {
|
|
70
|
+
bootspringDir = path.dirname(bootspringDir);
|
|
71
|
+
}
|
|
72
|
+
return bootspringDir;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Collaboration state file path
|
|
77
|
+
*/
|
|
78
|
+
function getCollabPath() {
|
|
79
|
+
const projectRoot = process.cwd();
|
|
80
|
+
return path.join(projectRoot, '.bootspring', 'collab-state.json');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Emit telemetry event
|
|
85
|
+
*/
|
|
86
|
+
function emitCollabTelemetry(event, payload = {}) {
|
|
87
|
+
try {
|
|
88
|
+
telemetry.emitEvent(event, payload, { projectRoot: process.cwd() });
|
|
89
|
+
} catch {
|
|
90
|
+
// Telemetry should not block collaboration execution
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load collaboration state
|
|
96
|
+
*/
|
|
97
|
+
function loadCollabState() {
|
|
98
|
+
const statePath = getCollabPath();
|
|
99
|
+
if (fs.existsSync(statePath)) {
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
102
|
+
} catch {
|
|
103
|
+
return createDefaultCollabState();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return createDefaultCollabState();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Create default collaboration state
|
|
111
|
+
*/
|
|
112
|
+
function createDefaultCollabState() {
|
|
113
|
+
return {
|
|
114
|
+
activeSessions: {},
|
|
115
|
+
pendingHandoffs: [],
|
|
116
|
+
completedSessions: [],
|
|
117
|
+
taskQueues: {}, // Per-agent task queues
|
|
118
|
+
agentStates: {}, // Current state of each agent
|
|
119
|
+
blockedTasks: [], // Tasks waiting on dependencies
|
|
120
|
+
history: [],
|
|
121
|
+
metrics: {
|
|
122
|
+
totalSessions: 0,
|
|
123
|
+
successfulSessions: 0,
|
|
124
|
+
totalHandoffs: 0,
|
|
125
|
+
totalSubtasks: 0,
|
|
126
|
+
avgSessionDuration: 0
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Save collaboration state
|
|
133
|
+
*/
|
|
134
|
+
function saveCollabState(state) {
|
|
135
|
+
const statePath = getCollabPath();
|
|
136
|
+
const dir = path.dirname(statePath);
|
|
137
|
+
if (!fs.existsSync(dir)) {
|
|
138
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
state.lastUpdated = new Date().toISOString();
|
|
141
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate unique session ID
|
|
146
|
+
*/
|
|
147
|
+
function generateSessionId() {
|
|
148
|
+
return `collab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get agent registry for capability matching
|
|
153
|
+
* Thin client: agents module may not exist locally
|
|
154
|
+
* Agent contexts are now served from the API
|
|
155
|
+
*/
|
|
156
|
+
function getAgentRegistry() {
|
|
157
|
+
try {
|
|
158
|
+
const agentsModule = require('../agents');
|
|
159
|
+
return agentsModule.AGENTS || {};
|
|
160
|
+
} catch {
|
|
161
|
+
// Thin client fallback - use cli/agent.js AGENTS registry
|
|
162
|
+
try {
|
|
163
|
+
const agentCli = require('../cli/agent');
|
|
164
|
+
return agentCli.AGENTS || {};
|
|
165
|
+
} catch {
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Find best agent for a task based on expertise
|
|
173
|
+
* @param {string} taskDescription - Task description
|
|
174
|
+
* @param {Array<string>} availableAgents - List of available agents
|
|
175
|
+
*/
|
|
176
|
+
function findBestAgentForTask(taskDescription, availableAgents = []) {
|
|
177
|
+
const registry = getAgentRegistry();
|
|
178
|
+
const descLower = taskDescription.toLowerCase();
|
|
179
|
+
const scores = [];
|
|
180
|
+
|
|
181
|
+
const searchAgents = availableAgents.length > 0
|
|
182
|
+
? availableAgents
|
|
183
|
+
: Object.keys(registry);
|
|
184
|
+
|
|
185
|
+
for (const agentId of searchAgents) {
|
|
186
|
+
const agent = registry[agentId];
|
|
187
|
+
if (!agent) continue;
|
|
188
|
+
|
|
189
|
+
let score = 0;
|
|
190
|
+
const expertise = agent.expertise || [];
|
|
191
|
+
|
|
192
|
+
for (const skill of expertise) {
|
|
193
|
+
if (descLower.includes(skill.toLowerCase())) {
|
|
194
|
+
score += 10;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Check description match
|
|
199
|
+
if (agent.description && descLower.includes(agent.description.toLowerCase())) {
|
|
200
|
+
score += 5;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (score > 0) {
|
|
204
|
+
scores.push({ agentId, agent, score });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Sort by score descending
|
|
209
|
+
scores.sort((a, b) => b.score - a.score);
|
|
210
|
+
|
|
211
|
+
return scores.length > 0 ? scores[0] : null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Start a new collaboration session
|
|
216
|
+
* @param {object} options - Session options
|
|
217
|
+
* @param {string} options.task - Main task description
|
|
218
|
+
* @param {string} options.primaryAgent - Primary agent leading the collaboration
|
|
219
|
+
* @param {Array<string>} options.supportingAgents - Supporting agents
|
|
220
|
+
* @param {object} options.context - Initial context for the session
|
|
221
|
+
* @param {string} options.parentSessionId - Parent session if this is a nested collaboration
|
|
222
|
+
*/
|
|
223
|
+
function startSession(options) {
|
|
224
|
+
const {
|
|
225
|
+
task,
|
|
226
|
+
primaryAgent,
|
|
227
|
+
supportingAgents = [],
|
|
228
|
+
context = {},
|
|
229
|
+
parentSessionId = null
|
|
230
|
+
} = options;
|
|
231
|
+
|
|
232
|
+
if (!task || !primaryAgent) {
|
|
233
|
+
return { success: false, error: 'Task and primaryAgent are required' };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const state = loadCollabState();
|
|
237
|
+
const sessionId = generateSessionId();
|
|
238
|
+
|
|
239
|
+
// Initialize agent states for this session
|
|
240
|
+
const agentParticipants = {
|
|
241
|
+
[primaryAgent]: {
|
|
242
|
+
role: AGENT_ROLE.PRIMARY,
|
|
243
|
+
joinedAt: new Date().toISOString(),
|
|
244
|
+
tasksAssigned: 0,
|
|
245
|
+
tasksCompleted: 0,
|
|
246
|
+
status: 'active'
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
supportingAgents.forEach(agent => {
|
|
251
|
+
agentParticipants[agent] = {
|
|
252
|
+
role: AGENT_ROLE.SUPPORTING,
|
|
253
|
+
joinedAt: new Date().toISOString(),
|
|
254
|
+
tasksAssigned: 0,
|
|
255
|
+
tasksCompleted: 0,
|
|
256
|
+
status: 'standby'
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
const session = {
|
|
261
|
+
id: sessionId,
|
|
262
|
+
task,
|
|
263
|
+
primaryAgent,
|
|
264
|
+
supportingAgents,
|
|
265
|
+
agentParticipants,
|
|
266
|
+
status: 'active',
|
|
267
|
+
createdAt: new Date().toISOString(),
|
|
268
|
+
parentSessionId,
|
|
269
|
+
subtasks: [],
|
|
270
|
+
taskQueue: [], // Priority queue for tasks
|
|
271
|
+
handoffs: [],
|
|
272
|
+
results: {},
|
|
273
|
+
artifacts: [], // Shared artifacts between agents
|
|
274
|
+
context: {
|
|
275
|
+
initial: context,
|
|
276
|
+
current: { ...context },
|
|
277
|
+
history: []
|
|
278
|
+
},
|
|
279
|
+
checkpoints: [], // Session checkpoints for recovery
|
|
280
|
+
metrics: {
|
|
281
|
+
startTime: Date.now(),
|
|
282
|
+
handoffCount: 0,
|
|
283
|
+
delegationCount: 0,
|
|
284
|
+
blockedTime: 0
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
state.activeSessions[sessionId] = session;
|
|
289
|
+
state.metrics.totalSessions++;
|
|
290
|
+
|
|
291
|
+
// Initialize task queue for primary agent
|
|
292
|
+
if (!state.taskQueues[primaryAgent]) {
|
|
293
|
+
state.taskQueues[primaryAgent] = [];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
state.history.push({
|
|
297
|
+
action: 'session_started',
|
|
298
|
+
sessionId,
|
|
299
|
+
primaryAgent,
|
|
300
|
+
supportingAgents,
|
|
301
|
+
parentSessionId,
|
|
302
|
+
timestamp: new Date().toISOString()
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
saveCollabState(state);
|
|
306
|
+
|
|
307
|
+
emitCollabTelemetry('collab_session_started', {
|
|
308
|
+
sessionId,
|
|
309
|
+
primaryAgent,
|
|
310
|
+
supportingAgentCount: supportingAgents.length
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
sessionId,
|
|
316
|
+
session,
|
|
317
|
+
message: `Collaboration session started with ${primaryAgent} as primary agent`
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create a subtask and delegate to an agent
|
|
323
|
+
* @param {string} sessionId - Collaboration session ID
|
|
324
|
+
* @param {object} subtask - Subtask definition
|
|
325
|
+
*/
|
|
326
|
+
function delegateTask(sessionId, subtask) {
|
|
327
|
+
const state = loadCollabState();
|
|
328
|
+
const session = state.activeSessions[sessionId];
|
|
329
|
+
|
|
330
|
+
if (!session) {
|
|
331
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const {
|
|
335
|
+
title,
|
|
336
|
+
description,
|
|
337
|
+
delegatedTo,
|
|
338
|
+
delegatedBy = session.primaryAgent,
|
|
339
|
+
context = {},
|
|
340
|
+
priority = TASK_PRIORITY.MEDIUM,
|
|
341
|
+
dependencies = [],
|
|
342
|
+
expectedDuration = null,
|
|
343
|
+
acceptanceCriteria = [],
|
|
344
|
+
artifacts = []
|
|
345
|
+
} = subtask;
|
|
346
|
+
|
|
347
|
+
if (!title || !delegatedTo) {
|
|
348
|
+
return { success: false, error: 'Title and delegatedTo are required' };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const subtaskId = `subtask-${session.subtasks.length + 1}-${Date.now()}`;
|
|
352
|
+
|
|
353
|
+
// Check if dependencies are met
|
|
354
|
+
const unmetDependencies = dependencies.filter(depId => {
|
|
355
|
+
const dep = session.subtasks.find(t => t.id === depId);
|
|
356
|
+
return !dep || dep.status !== TASK_STATUS.COMPLETE;
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
const newSubtask = {
|
|
360
|
+
id: subtaskId,
|
|
361
|
+
title,
|
|
362
|
+
description,
|
|
363
|
+
delegatedTo,
|
|
364
|
+
delegatedBy,
|
|
365
|
+
context: {
|
|
366
|
+
...session.context.current,
|
|
367
|
+
...context
|
|
368
|
+
},
|
|
369
|
+
priority,
|
|
370
|
+
dependencies,
|
|
371
|
+
unmetDependencies,
|
|
372
|
+
expectedDuration,
|
|
373
|
+
acceptanceCriteria,
|
|
374
|
+
artifacts,
|
|
375
|
+
status: unmetDependencies.length > 0 ? TASK_STATUS.BLOCKED : TASK_STATUS.PENDING,
|
|
376
|
+
createdAt: new Date().toISOString(),
|
|
377
|
+
startedAt: null,
|
|
378
|
+
completedAt: null,
|
|
379
|
+
result: null,
|
|
380
|
+
feedback: null,
|
|
381
|
+
retryCount: 0
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
session.subtasks.push(newSubtask);
|
|
385
|
+
session.metrics.delegationCount++;
|
|
386
|
+
|
|
387
|
+
// Update agent participant stats
|
|
388
|
+
if (session.agentParticipants[delegatedTo]) {
|
|
389
|
+
session.agentParticipants[delegatedTo].tasksAssigned++;
|
|
390
|
+
} else {
|
|
391
|
+
// Add new specialist agent
|
|
392
|
+
session.agentParticipants[delegatedTo] = {
|
|
393
|
+
role: AGENT_ROLE.SPECIALIST,
|
|
394
|
+
joinedAt: new Date().toISOString(),
|
|
395
|
+
tasksAssigned: 1,
|
|
396
|
+
tasksCompleted: 0,
|
|
397
|
+
status: 'active'
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Add to priority queue
|
|
402
|
+
insertIntoTaskQueue(session.taskQueue, newSubtask);
|
|
403
|
+
|
|
404
|
+
// Add to agent's global task queue
|
|
405
|
+
if (!state.taskQueues[delegatedTo]) {
|
|
406
|
+
state.taskQueues[delegatedTo] = [];
|
|
407
|
+
}
|
|
408
|
+
state.taskQueues[delegatedTo].push({
|
|
409
|
+
sessionId,
|
|
410
|
+
subtaskId,
|
|
411
|
+
priority,
|
|
412
|
+
createdAt: newSubtask.createdAt
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Track blocked tasks
|
|
416
|
+
if (newSubtask.status === TASK_STATUS.BLOCKED) {
|
|
417
|
+
state.blockedTasks.push({
|
|
418
|
+
sessionId,
|
|
419
|
+
subtaskId,
|
|
420
|
+
dependencies: unmetDependencies
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
state.metrics.totalSubtasks++;
|
|
425
|
+
|
|
426
|
+
state.history.push({
|
|
427
|
+
action: 'task_delegated',
|
|
428
|
+
sessionId,
|
|
429
|
+
subtaskId,
|
|
430
|
+
from: delegatedBy,
|
|
431
|
+
to: delegatedTo,
|
|
432
|
+
priority,
|
|
433
|
+
hasDepedencies: dependencies.length > 0,
|
|
434
|
+
timestamp: new Date().toISOString()
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
saveCollabState(state);
|
|
438
|
+
|
|
439
|
+
emitCollabTelemetry('task_delegated', {
|
|
440
|
+
sessionId,
|
|
441
|
+
subtaskId,
|
|
442
|
+
delegatedTo,
|
|
443
|
+
priority
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
success: true,
|
|
448
|
+
sessionId,
|
|
449
|
+
subtaskId,
|
|
450
|
+
subtask: newSubtask,
|
|
451
|
+
isBlocked: newSubtask.status === TASK_STATUS.BLOCKED,
|
|
452
|
+
message: newSubtask.status === TASK_STATUS.BLOCKED
|
|
453
|
+
? `Task "${title}" delegated to ${delegatedTo} (blocked on dependencies)`
|
|
454
|
+
: `Task "${title}" delegated to ${delegatedTo}`
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Insert task into priority queue maintaining priority order
|
|
460
|
+
*/
|
|
461
|
+
function insertIntoTaskQueue(queue, task) {
|
|
462
|
+
const priorityOrder = {
|
|
463
|
+
[TASK_PRIORITY.CRITICAL]: 0,
|
|
464
|
+
[TASK_PRIORITY.HIGH]: 1,
|
|
465
|
+
[TASK_PRIORITY.MEDIUM]: 2,
|
|
466
|
+
[TASK_PRIORITY.LOW]: 3
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const taskPriority = priorityOrder[task.priority] ?? 2;
|
|
470
|
+
let inserted = false;
|
|
471
|
+
|
|
472
|
+
for (let i = 0; i < queue.length; i++) {
|
|
473
|
+
const queuePriority = priorityOrder[queue[i].priority] ?? 2;
|
|
474
|
+
if (taskPriority < queuePriority) {
|
|
475
|
+
queue.splice(i, 0, task);
|
|
476
|
+
inserted = true;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (!inserted) {
|
|
482
|
+
queue.push(task);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Start working on a subtask
|
|
488
|
+
* @param {string} sessionId - Collaboration session ID
|
|
489
|
+
* @param {string} subtaskId - Subtask ID
|
|
490
|
+
*/
|
|
491
|
+
function startSubtask(sessionId, subtaskId) {
|
|
492
|
+
const state = loadCollabState();
|
|
493
|
+
const session = state.activeSessions[sessionId];
|
|
494
|
+
|
|
495
|
+
if (!session) {
|
|
496
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const subtask = session.subtasks.find(s => s.id === subtaskId);
|
|
500
|
+
if (!subtask) {
|
|
501
|
+
return { success: false, error: `Subtask not found: ${subtaskId}` };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (subtask.status === TASK_STATUS.BLOCKED) {
|
|
505
|
+
return {
|
|
506
|
+
success: false,
|
|
507
|
+
error: 'Subtask is blocked on dependencies',
|
|
508
|
+
unmetDependencies: subtask.unmetDependencies
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (subtask.status === TASK_STATUS.IN_PROGRESS) {
|
|
513
|
+
return { success: false, error: 'Subtask already in progress' };
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
subtask.status = TASK_STATUS.IN_PROGRESS;
|
|
517
|
+
subtask.startedAt = new Date().toISOString();
|
|
518
|
+
|
|
519
|
+
// Update agent status
|
|
520
|
+
if (session.agentParticipants[subtask.delegatedTo]) {
|
|
521
|
+
session.agentParticipants[subtask.delegatedTo].status = 'working';
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
state.history.push({
|
|
525
|
+
action: 'subtask_started',
|
|
526
|
+
sessionId,
|
|
527
|
+
subtaskId,
|
|
528
|
+
agent: subtask.delegatedTo,
|
|
529
|
+
timestamp: new Date().toISOString()
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
saveCollabState(state);
|
|
533
|
+
|
|
534
|
+
return {
|
|
535
|
+
success: true,
|
|
536
|
+
sessionId,
|
|
537
|
+
subtaskId,
|
|
538
|
+
subtask,
|
|
539
|
+
message: `Started working on "${subtask.title}"`
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Update subtask progress
|
|
545
|
+
* @param {string} sessionId - Collaboration session ID
|
|
546
|
+
* @param {string} subtaskId - Subtask ID
|
|
547
|
+
* @param {object} update - Progress update
|
|
548
|
+
*/
|
|
549
|
+
function updateSubtaskProgress(sessionId, subtaskId, update) {
|
|
550
|
+
const state = loadCollabState();
|
|
551
|
+
const session = state.activeSessions[sessionId];
|
|
552
|
+
|
|
553
|
+
if (!session) {
|
|
554
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const subtask = session.subtasks.find(s => s.id === subtaskId);
|
|
558
|
+
if (!subtask) {
|
|
559
|
+
return { success: false, error: `Subtask not found: ${subtaskId}` };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const { progress, notes, artifacts, blockedReason } = update;
|
|
563
|
+
|
|
564
|
+
if (progress !== undefined) {
|
|
565
|
+
subtask.progress = progress;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (notes) {
|
|
569
|
+
subtask.progressNotes = subtask.progressNotes || [];
|
|
570
|
+
subtask.progressNotes.push({
|
|
571
|
+
note: notes,
|
|
572
|
+
timestamp: new Date().toISOString()
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (artifacts && artifacts.length > 0) {
|
|
577
|
+
subtask.artifacts = [...(subtask.artifacts || []), ...artifacts];
|
|
578
|
+
// Also add to session artifacts
|
|
579
|
+
session.artifacts.push(...artifacts.map(a => ({
|
|
580
|
+
...a,
|
|
581
|
+
subtaskId,
|
|
582
|
+
addedAt: new Date().toISOString()
|
|
583
|
+
})));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (blockedReason) {
|
|
587
|
+
subtask.status = TASK_STATUS.BLOCKED;
|
|
588
|
+
subtask.blockedReason = blockedReason;
|
|
589
|
+
state.blockedTasks.push({
|
|
590
|
+
sessionId,
|
|
591
|
+
subtaskId,
|
|
592
|
+
reason: blockedReason,
|
|
593
|
+
blockedAt: new Date().toISOString()
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
saveCollabState(state);
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
success: true,
|
|
601
|
+
sessionId,
|
|
602
|
+
subtaskId,
|
|
603
|
+
subtask,
|
|
604
|
+
message: 'Subtask progress updated'
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Complete a subtask
|
|
610
|
+
* @param {string} sessionId - Collaboration session ID
|
|
611
|
+
* @param {string} subtaskId - Subtask ID
|
|
612
|
+
* @param {object} result - Subtask result
|
|
613
|
+
*/
|
|
614
|
+
function completeSubtask(sessionId, subtaskId, result) {
|
|
615
|
+
const state = loadCollabState();
|
|
616
|
+
const session = state.activeSessions[sessionId];
|
|
617
|
+
|
|
618
|
+
if (!session) {
|
|
619
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const subtask = session.subtasks.find(s => s.id === subtaskId);
|
|
623
|
+
if (!subtask) {
|
|
624
|
+
return { success: false, error: `Subtask not found: ${subtaskId}` };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const {
|
|
628
|
+
output,
|
|
629
|
+
artifacts = [],
|
|
630
|
+
contextUpdates = {},
|
|
631
|
+
learnings = [],
|
|
632
|
+
success = true
|
|
633
|
+
} = result;
|
|
634
|
+
|
|
635
|
+
subtask.status = success ? TASK_STATUS.COMPLETE : TASK_STATUS.FAILED;
|
|
636
|
+
subtask.completedAt = new Date().toISOString();
|
|
637
|
+
subtask.result = {
|
|
638
|
+
output,
|
|
639
|
+
success,
|
|
640
|
+
duration: subtask.startedAt
|
|
641
|
+
? Date.now() - new Date(subtask.startedAt).getTime()
|
|
642
|
+
: null
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// Add artifacts to session
|
|
646
|
+
if (artifacts.length > 0) {
|
|
647
|
+
subtask.artifacts = [...(subtask.artifacts || []), ...artifacts];
|
|
648
|
+
session.artifacts.push(...artifacts.map(a => ({
|
|
649
|
+
...a,
|
|
650
|
+
subtaskId,
|
|
651
|
+
addedAt: new Date().toISOString()
|
|
652
|
+
})));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Update session context with learnings from this subtask
|
|
656
|
+
if (Object.keys(contextUpdates).length > 0) {
|
|
657
|
+
session.context.history.push({
|
|
658
|
+
update: contextUpdates,
|
|
659
|
+
source: subtaskId,
|
|
660
|
+
timestamp: new Date().toISOString()
|
|
661
|
+
});
|
|
662
|
+
session.context.current = {
|
|
663
|
+
...session.context.current,
|
|
664
|
+
...contextUpdates
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Store learnings
|
|
669
|
+
if (learnings.length > 0) {
|
|
670
|
+
session.learnings = session.learnings || [];
|
|
671
|
+
session.learnings.push(...learnings.map(l => ({
|
|
672
|
+
...l,
|
|
673
|
+
subtaskId,
|
|
674
|
+
agent: subtask.delegatedTo,
|
|
675
|
+
timestamp: new Date().toISOString()
|
|
676
|
+
})));
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
session.results[subtaskId] = result;
|
|
680
|
+
|
|
681
|
+
// Update agent participant stats
|
|
682
|
+
if (session.agentParticipants[subtask.delegatedTo]) {
|
|
683
|
+
session.agentParticipants[subtask.delegatedTo].tasksCompleted++;
|
|
684
|
+
session.agentParticipants[subtask.delegatedTo].status = 'standby';
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Remove from agent's task queue
|
|
688
|
+
if (state.taskQueues[subtask.delegatedTo]) {
|
|
689
|
+
state.taskQueues[subtask.delegatedTo] = state.taskQueues[subtask.delegatedTo]
|
|
690
|
+
.filter(t => !(t.sessionId === sessionId && t.subtaskId === subtaskId));
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Unblock dependent tasks
|
|
694
|
+
const unblockedTasks = [];
|
|
695
|
+
session.subtasks.forEach(t => {
|
|
696
|
+
if (t.status === TASK_STATUS.BLOCKED && t.dependencies.includes(subtaskId)) {
|
|
697
|
+
t.unmetDependencies = t.unmetDependencies.filter(d => d !== subtaskId);
|
|
698
|
+
if (t.unmetDependencies.length === 0) {
|
|
699
|
+
t.status = TASK_STATUS.PENDING;
|
|
700
|
+
unblockedTasks.push(t.id);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
// Remove from blocked tasks list
|
|
706
|
+
state.blockedTasks = state.blockedTasks.filter(
|
|
707
|
+
bt => !(bt.sessionId === sessionId && bt.subtaskId === subtaskId)
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
state.history.push({
|
|
711
|
+
action: 'subtask_completed',
|
|
712
|
+
sessionId,
|
|
713
|
+
subtaskId,
|
|
714
|
+
agent: subtask.delegatedTo,
|
|
715
|
+
success,
|
|
716
|
+
unblockedTasks,
|
|
717
|
+
timestamp: new Date().toISOString()
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
saveCollabState(state);
|
|
721
|
+
|
|
722
|
+
emitCollabTelemetry('subtask_completed', {
|
|
723
|
+
sessionId,
|
|
724
|
+
subtaskId,
|
|
725
|
+
agent: subtask.delegatedTo,
|
|
726
|
+
success,
|
|
727
|
+
unblockedCount: unblockedTasks.length
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
// Check if all subtasks are complete
|
|
731
|
+
const allComplete = session.subtasks.every(
|
|
732
|
+
s => s.status === TASK_STATUS.COMPLETE || s.status === TASK_STATUS.FAILED
|
|
733
|
+
);
|
|
734
|
+
const allSuccessful = session.subtasks.every(s => s.status === TASK_STATUS.COMPLETE);
|
|
735
|
+
|
|
736
|
+
return {
|
|
737
|
+
success: true,
|
|
738
|
+
sessionId,
|
|
739
|
+
subtaskId,
|
|
740
|
+
taskSuccess: success,
|
|
741
|
+
allSubtasksComplete: allComplete,
|
|
742
|
+
allSubtasksSuccessful: allSuccessful,
|
|
743
|
+
unblockedTasks,
|
|
744
|
+
message: allComplete
|
|
745
|
+
? allSuccessful
|
|
746
|
+
? 'All subtasks complete. Ready to finalize session.'
|
|
747
|
+
: 'All subtasks processed with some failures.'
|
|
748
|
+
: `Subtask "${subtask.title}" completed`
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Fail a subtask
|
|
754
|
+
* @param {string} sessionId - Collaboration session ID
|
|
755
|
+
* @param {string} subtaskId - Subtask ID
|
|
756
|
+
* @param {object} failure - Failure details
|
|
757
|
+
*/
|
|
758
|
+
function failSubtask(sessionId, subtaskId, failure) {
|
|
759
|
+
const state = loadCollabState();
|
|
760
|
+
const session = state.activeSessions[sessionId];
|
|
761
|
+
|
|
762
|
+
if (!session) {
|
|
763
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const subtask = session.subtasks.find(s => s.id === subtaskId);
|
|
767
|
+
if (!subtask) {
|
|
768
|
+
return { success: false, error: `Subtask not found: ${subtaskId}` };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const { reason, canRetry = false, suggestedAction = null } = failure;
|
|
772
|
+
|
|
773
|
+
subtask.status = TASK_STATUS.FAILED;
|
|
774
|
+
subtask.completedAt = new Date().toISOString();
|
|
775
|
+
subtask.failure = {
|
|
776
|
+
reason,
|
|
777
|
+
canRetry,
|
|
778
|
+
suggestedAction,
|
|
779
|
+
failedAt: new Date().toISOString()
|
|
780
|
+
};
|
|
781
|
+
subtask.retryCount++;
|
|
782
|
+
|
|
783
|
+
// Update agent status
|
|
784
|
+
if (session.agentParticipants[subtask.delegatedTo]) {
|
|
785
|
+
session.agentParticipants[subtask.delegatedTo].status = 'standby';
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
state.history.push({
|
|
789
|
+
action: 'subtask_failed',
|
|
790
|
+
sessionId,
|
|
791
|
+
subtaskId,
|
|
792
|
+
agent: subtask.delegatedTo,
|
|
793
|
+
reason,
|
|
794
|
+
canRetry,
|
|
795
|
+
timestamp: new Date().toISOString()
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
saveCollabState(state);
|
|
799
|
+
|
|
800
|
+
return {
|
|
801
|
+
success: true,
|
|
802
|
+
sessionId,
|
|
803
|
+
subtaskId,
|
|
804
|
+
canRetry,
|
|
805
|
+
suggestedAction,
|
|
806
|
+
message: `Subtask "${subtask.title}" failed: ${reason}`
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Retry a failed subtask
|
|
812
|
+
* @param {string} sessionId - Collaboration session ID
|
|
813
|
+
* @param {string} subtaskId - Subtask ID
|
|
814
|
+
* @param {object} options - Retry options
|
|
815
|
+
*/
|
|
816
|
+
function retrySubtask(sessionId, subtaskId, options = {}) {
|
|
817
|
+
const state = loadCollabState();
|
|
818
|
+
const session = state.activeSessions[sessionId];
|
|
819
|
+
|
|
820
|
+
if (!session) {
|
|
821
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
const subtask = session.subtasks.find(s => s.id === subtaskId);
|
|
825
|
+
if (!subtask) {
|
|
826
|
+
return { success: false, error: `Subtask not found: ${subtaskId}` };
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (subtask.status !== TASK_STATUS.FAILED) {
|
|
830
|
+
return { success: false, error: 'Can only retry failed subtasks' };
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
const { reassignTo, additionalContext = {} } = options;
|
|
834
|
+
|
|
835
|
+
// Reset subtask state
|
|
836
|
+
subtask.status = TASK_STATUS.PENDING;
|
|
837
|
+
subtask.completedAt = null;
|
|
838
|
+
subtask.startedAt = null;
|
|
839
|
+
subtask.result = null;
|
|
840
|
+
|
|
841
|
+
// Optionally reassign to different agent
|
|
842
|
+
if (reassignTo) {
|
|
843
|
+
subtask.delegatedTo = reassignTo;
|
|
844
|
+
subtask.reassignedFrom = subtask.delegatedTo;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// Add additional context
|
|
848
|
+
if (Object.keys(additionalContext).length > 0) {
|
|
849
|
+
subtask.context = {
|
|
850
|
+
...subtask.context,
|
|
851
|
+
...additionalContext,
|
|
852
|
+
previousAttemptFailed: true
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Re-add to task queue
|
|
857
|
+
insertIntoTaskQueue(session.taskQueue, subtask);
|
|
858
|
+
|
|
859
|
+
state.history.push({
|
|
860
|
+
action: 'subtask_retried',
|
|
861
|
+
sessionId,
|
|
862
|
+
subtaskId,
|
|
863
|
+
retryCount: subtask.retryCount,
|
|
864
|
+
reassignedTo: reassignTo || null,
|
|
865
|
+
timestamp: new Date().toISOString()
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
saveCollabState(state);
|
|
869
|
+
|
|
870
|
+
return {
|
|
871
|
+
success: true,
|
|
872
|
+
sessionId,
|
|
873
|
+
subtaskId,
|
|
874
|
+
retryCount: subtask.retryCount,
|
|
875
|
+
message: `Subtask "${subtask.title}" queued for retry`
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Request handoff from one agent to another
|
|
881
|
+
* @param {string} sessionId - Collaboration session ID
|
|
882
|
+
* @param {object} handoff - Handoff details
|
|
883
|
+
*/
|
|
884
|
+
function requestHandoff(sessionId, handoff) {
|
|
885
|
+
const state = loadCollabState();
|
|
886
|
+
const session = state.activeSessions[sessionId];
|
|
887
|
+
|
|
888
|
+
if (!session) {
|
|
889
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
const {
|
|
893
|
+
fromAgent,
|
|
894
|
+
toAgent,
|
|
895
|
+
reason,
|
|
896
|
+
context = {},
|
|
897
|
+
artifacts = [],
|
|
898
|
+
transferPrimaryRole = false,
|
|
899
|
+
subtaskId = null,
|
|
900
|
+
urgency = 'normal',
|
|
901
|
+
expectedDuration = null
|
|
902
|
+
} = handoff;
|
|
903
|
+
|
|
904
|
+
if (!fromAgent || !toAgent) {
|
|
905
|
+
return { success: false, error: 'fromAgent and toAgent are required' };
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const handoffId = `handoff-${session.handoffs.length + 1}-${Date.now()}`;
|
|
909
|
+
|
|
910
|
+
// Collect context for handoff
|
|
911
|
+
const handoffContext = {
|
|
912
|
+
...session.context.current,
|
|
913
|
+
...context,
|
|
914
|
+
handoffReason: reason,
|
|
915
|
+
previousAgent: fromAgent,
|
|
916
|
+
sessionProgress: {
|
|
917
|
+
completedSubtasks: session.subtasks.filter(s => s.status === TASK_STATUS.COMPLETE).length,
|
|
918
|
+
totalSubtasks: session.subtasks.length,
|
|
919
|
+
artifacts: session.artifacts.length
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// If transferring a specific subtask, include its context
|
|
924
|
+
let transferredSubtask = null;
|
|
925
|
+
if (subtaskId) {
|
|
926
|
+
transferredSubtask = session.subtasks.find(s => s.id === subtaskId);
|
|
927
|
+
if (transferredSubtask) {
|
|
928
|
+
handoffContext.subtask = {
|
|
929
|
+
id: transferredSubtask.id,
|
|
930
|
+
title: transferredSubtask.title,
|
|
931
|
+
description: transferredSubtask.description,
|
|
932
|
+
progress: transferredSubtask.progress,
|
|
933
|
+
progressNotes: transferredSubtask.progressNotes
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const newHandoff = {
|
|
939
|
+
id: handoffId,
|
|
940
|
+
fromAgent,
|
|
941
|
+
toAgent,
|
|
942
|
+
reason,
|
|
943
|
+
context: handoffContext,
|
|
944
|
+
artifacts,
|
|
945
|
+
transferPrimaryRole,
|
|
946
|
+
subtaskId,
|
|
947
|
+
urgency,
|
|
948
|
+
expectedDuration,
|
|
949
|
+
status: HANDOFF_STATUS.PENDING,
|
|
950
|
+
requestedAt: new Date().toISOString(),
|
|
951
|
+
acceptedAt: null,
|
|
952
|
+
completedAt: null,
|
|
953
|
+
rejectionReason: null
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
session.handoffs.push(newHandoff);
|
|
957
|
+
session.metrics.handoffCount++;
|
|
958
|
+
|
|
959
|
+
state.pendingHandoffs.push({
|
|
960
|
+
sessionId,
|
|
961
|
+
handoffId,
|
|
962
|
+
...newHandoff
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
state.metrics.totalHandoffs++;
|
|
966
|
+
|
|
967
|
+
state.history.push({
|
|
968
|
+
action: 'handoff_requested',
|
|
969
|
+
sessionId,
|
|
970
|
+
handoffId,
|
|
971
|
+
fromAgent,
|
|
972
|
+
toAgent,
|
|
973
|
+
transferPrimaryRole,
|
|
974
|
+
urgency,
|
|
975
|
+
timestamp: new Date().toISOString()
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
saveCollabState(state);
|
|
979
|
+
|
|
980
|
+
emitCollabTelemetry('handoff_requested', {
|
|
981
|
+
sessionId,
|
|
982
|
+
handoffId,
|
|
983
|
+
fromAgent,
|
|
984
|
+
toAgent,
|
|
985
|
+
transferPrimaryRole,
|
|
986
|
+
urgency
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
return {
|
|
990
|
+
success: true,
|
|
991
|
+
sessionId,
|
|
992
|
+
handoffId,
|
|
993
|
+
handoff: newHandoff,
|
|
994
|
+
message: transferPrimaryRole
|
|
995
|
+
? `Primary role handoff requested from ${fromAgent} to ${toAgent}`
|
|
996
|
+
: `Handoff requested from ${fromAgent} to ${toAgent}`
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Accept a handoff
|
|
1002
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1003
|
+
* @param {string} handoffId - Handoff ID
|
|
1004
|
+
* @param {object} options - Accept options
|
|
1005
|
+
*/
|
|
1006
|
+
function acceptHandoff(sessionId, handoffId, options = {}) {
|
|
1007
|
+
const state = loadCollabState();
|
|
1008
|
+
const session = state.activeSessions[sessionId];
|
|
1009
|
+
|
|
1010
|
+
if (!session) {
|
|
1011
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
const handoff = session.handoffs.find(h => h.id === handoffId);
|
|
1015
|
+
if (!handoff) {
|
|
1016
|
+
return { success: false, error: `Handoff not found: ${handoffId}` };
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (handoff.status !== HANDOFF_STATUS.PENDING) {
|
|
1020
|
+
return { success: false, error: `Handoff already ${handoff.status}` };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
const { acknowledgment = null, estimatedCompletion = null } = options;
|
|
1024
|
+
|
|
1025
|
+
handoff.status = HANDOFF_STATUS.ACCEPTED;
|
|
1026
|
+
handoff.acceptedAt = new Date().toISOString();
|
|
1027
|
+
handoff.acknowledgment = acknowledgment;
|
|
1028
|
+
handoff.estimatedCompletion = estimatedCompletion;
|
|
1029
|
+
|
|
1030
|
+
// Update primary agent if this was a primary role transfer
|
|
1031
|
+
const previousPrimary = session.primaryAgent;
|
|
1032
|
+
if (handoff.transferPrimaryRole) {
|
|
1033
|
+
session.primaryAgent = handoff.toAgent;
|
|
1034
|
+
|
|
1035
|
+
// Update agent roles
|
|
1036
|
+
if (session.agentParticipants[previousPrimary]) {
|
|
1037
|
+
session.agentParticipants[previousPrimary].role = AGENT_ROLE.SUPPORTING;
|
|
1038
|
+
}
|
|
1039
|
+
if (session.agentParticipants[handoff.toAgent]) {
|
|
1040
|
+
session.agentParticipants[handoff.toAgent].role = AGENT_ROLE.PRIMARY;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// Transfer subtask if specified
|
|
1045
|
+
if (handoff.subtaskId) {
|
|
1046
|
+
const subtask = session.subtasks.find(s => s.id === handoff.subtaskId);
|
|
1047
|
+
if (subtask) {
|
|
1048
|
+
subtask.delegatedTo = handoff.toAgent;
|
|
1049
|
+
subtask.transferHistory = subtask.transferHistory || [];
|
|
1050
|
+
subtask.transferHistory.push({
|
|
1051
|
+
from: handoff.fromAgent,
|
|
1052
|
+
to: handoff.toAgent,
|
|
1053
|
+
handoffId,
|
|
1054
|
+
timestamp: new Date().toISOString()
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// Update agent states
|
|
1060
|
+
if (session.agentParticipants[handoff.toAgent]) {
|
|
1061
|
+
session.agentParticipants[handoff.toAgent].status = 'active';
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Remove from pending
|
|
1065
|
+
state.pendingHandoffs = state.pendingHandoffs.filter(
|
|
1066
|
+
h => !(h.sessionId === sessionId && h.handoffId === handoffId)
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
state.history.push({
|
|
1070
|
+
action: 'handoff_accepted',
|
|
1071
|
+
sessionId,
|
|
1072
|
+
handoffId,
|
|
1073
|
+
byAgent: handoff.toAgent,
|
|
1074
|
+
transferPrimaryRole: handoff.transferPrimaryRole,
|
|
1075
|
+
previousPrimary: handoff.transferPrimaryRole ? previousPrimary : null,
|
|
1076
|
+
timestamp: new Date().toISOString()
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
saveCollabState(state);
|
|
1080
|
+
|
|
1081
|
+
emitCollabTelemetry('handoff_accepted', {
|
|
1082
|
+
sessionId,
|
|
1083
|
+
handoffId,
|
|
1084
|
+
byAgent: handoff.toAgent,
|
|
1085
|
+
transferPrimaryRole: handoff.transferPrimaryRole
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
return {
|
|
1089
|
+
success: true,
|
|
1090
|
+
sessionId,
|
|
1091
|
+
handoffId,
|
|
1092
|
+
newPrimaryAgent: session.primaryAgent,
|
|
1093
|
+
transferredSubtask: handoff.subtaskId,
|
|
1094
|
+
context: handoff.context,
|
|
1095
|
+
message: handoff.transferPrimaryRole
|
|
1096
|
+
? `Primary role transferred to ${handoff.toAgent}`
|
|
1097
|
+
: `Handoff accepted by ${handoff.toAgent}`
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Reject a handoff
|
|
1103
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1104
|
+
* @param {string} handoffId - Handoff ID
|
|
1105
|
+
* @param {string} reason - Rejection reason
|
|
1106
|
+
*/
|
|
1107
|
+
function rejectHandoff(sessionId, handoffId, reason) {
|
|
1108
|
+
const state = loadCollabState();
|
|
1109
|
+
const session = state.activeSessions[sessionId];
|
|
1110
|
+
|
|
1111
|
+
if (!session) {
|
|
1112
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
const handoff = session.handoffs.find(h => h.id === handoffId);
|
|
1116
|
+
if (!handoff) {
|
|
1117
|
+
return { success: false, error: `Handoff not found: ${handoffId}` };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
if (handoff.status !== HANDOFF_STATUS.PENDING) {
|
|
1121
|
+
return { success: false, error: `Handoff already ${handoff.status}` };
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
handoff.status = HANDOFF_STATUS.REJECTED;
|
|
1125
|
+
handoff.rejectionReason = reason;
|
|
1126
|
+
handoff.rejectedAt = new Date().toISOString();
|
|
1127
|
+
|
|
1128
|
+
// Remove from pending
|
|
1129
|
+
state.pendingHandoffs = state.pendingHandoffs.filter(
|
|
1130
|
+
h => !(h.sessionId === sessionId && h.handoffId === handoffId)
|
|
1131
|
+
);
|
|
1132
|
+
|
|
1133
|
+
state.history.push({
|
|
1134
|
+
action: 'handoff_rejected',
|
|
1135
|
+
sessionId,
|
|
1136
|
+
handoffId,
|
|
1137
|
+
byAgent: handoff.toAgent,
|
|
1138
|
+
reason,
|
|
1139
|
+
timestamp: new Date().toISOString()
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
saveCollabState(state);
|
|
1143
|
+
|
|
1144
|
+
return {
|
|
1145
|
+
success: true,
|
|
1146
|
+
sessionId,
|
|
1147
|
+
handoffId,
|
|
1148
|
+
rejectionReason: reason,
|
|
1149
|
+
message: `Handoff rejected by ${handoff.toAgent}: ${reason}`
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
/**
|
|
1154
|
+
* Complete a handoff (mark work as done)
|
|
1155
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1156
|
+
* @param {string} handoffId - Handoff ID
|
|
1157
|
+
* @param {object} result - Handoff result
|
|
1158
|
+
*/
|
|
1159
|
+
function completeHandoff(sessionId, handoffId, result = {}) {
|
|
1160
|
+
const state = loadCollabState();
|
|
1161
|
+
const session = state.activeSessions[sessionId];
|
|
1162
|
+
|
|
1163
|
+
if (!session) {
|
|
1164
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const handoff = session.handoffs.find(h => h.id === handoffId);
|
|
1168
|
+
if (!handoff) {
|
|
1169
|
+
return { success: false, error: `Handoff not found: ${handoffId}` };
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (handoff.status !== HANDOFF_STATUS.ACCEPTED) {
|
|
1173
|
+
return { success: false, error: 'Handoff must be accepted before completing' };
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
handoff.status = HANDOFF_STATUS.COMPLETED;
|
|
1177
|
+
handoff.completedAt = new Date().toISOString();
|
|
1178
|
+
handoff.result = result;
|
|
1179
|
+
|
|
1180
|
+
state.history.push({
|
|
1181
|
+
action: 'handoff_completed',
|
|
1182
|
+
sessionId,
|
|
1183
|
+
handoffId,
|
|
1184
|
+
byAgent: handoff.toAgent,
|
|
1185
|
+
timestamp: new Date().toISOString()
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
saveCollabState(state);
|
|
1189
|
+
|
|
1190
|
+
return {
|
|
1191
|
+
success: true,
|
|
1192
|
+
sessionId,
|
|
1193
|
+
handoffId,
|
|
1194
|
+
message: `Handoff completed by ${handoff.toAgent}`
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
/**
|
|
1199
|
+
* Create a checkpoint for session recovery
|
|
1200
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1201
|
+
* @param {string} description - Checkpoint description
|
|
1202
|
+
*/
|
|
1203
|
+
function createCheckpoint(sessionId, description = '') {
|
|
1204
|
+
const state = loadCollabState();
|
|
1205
|
+
const session = state.activeSessions[sessionId];
|
|
1206
|
+
|
|
1207
|
+
if (!session) {
|
|
1208
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const checkpointId = `checkpoint-${session.checkpoints.length + 1}`;
|
|
1212
|
+
const checkpoint = {
|
|
1213
|
+
id: checkpointId,
|
|
1214
|
+
description,
|
|
1215
|
+
createdAt: new Date().toISOString(),
|
|
1216
|
+
snapshot: {
|
|
1217
|
+
subtasks: JSON.parse(JSON.stringify(session.subtasks)),
|
|
1218
|
+
context: JSON.parse(JSON.stringify(session.context)),
|
|
1219
|
+
results: JSON.parse(JSON.stringify(session.results)),
|
|
1220
|
+
artifacts: JSON.parse(JSON.stringify(session.artifacts)),
|
|
1221
|
+
primaryAgent: session.primaryAgent
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
session.checkpoints.push(checkpoint);
|
|
1226
|
+
|
|
1227
|
+
state.history.push({
|
|
1228
|
+
action: 'checkpoint_created',
|
|
1229
|
+
sessionId,
|
|
1230
|
+
checkpointId,
|
|
1231
|
+
description,
|
|
1232
|
+
timestamp: new Date().toISOString()
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
saveCollabState(state);
|
|
1236
|
+
|
|
1237
|
+
return {
|
|
1238
|
+
success: true,
|
|
1239
|
+
sessionId,
|
|
1240
|
+
checkpointId,
|
|
1241
|
+
message: `Checkpoint created: ${description || checkpointId}`
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/**
|
|
1246
|
+
* Restore session from checkpoint
|
|
1247
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1248
|
+
* @param {string} checkpointId - Checkpoint ID
|
|
1249
|
+
*/
|
|
1250
|
+
function restoreFromCheckpoint(sessionId, checkpointId) {
|
|
1251
|
+
const state = loadCollabState();
|
|
1252
|
+
const session = state.activeSessions[sessionId];
|
|
1253
|
+
|
|
1254
|
+
if (!session) {
|
|
1255
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
const checkpoint = session.checkpoints.find(c => c.id === checkpointId);
|
|
1259
|
+
if (!checkpoint) {
|
|
1260
|
+
return { success: false, error: `Checkpoint not found: ${checkpointId}` };
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Restore from snapshot
|
|
1264
|
+
session.subtasks = JSON.parse(JSON.stringify(checkpoint.snapshot.subtasks));
|
|
1265
|
+
session.context = JSON.parse(JSON.stringify(checkpoint.snapshot.context));
|
|
1266
|
+
session.results = JSON.parse(JSON.stringify(checkpoint.snapshot.results));
|
|
1267
|
+
session.artifacts = JSON.parse(JSON.stringify(checkpoint.snapshot.artifacts));
|
|
1268
|
+
session.primaryAgent = checkpoint.snapshot.primaryAgent;
|
|
1269
|
+
|
|
1270
|
+
state.history.push({
|
|
1271
|
+
action: 'checkpoint_restored',
|
|
1272
|
+
sessionId,
|
|
1273
|
+
checkpointId,
|
|
1274
|
+
timestamp: new Date().toISOString()
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
saveCollabState(state);
|
|
1278
|
+
|
|
1279
|
+
return {
|
|
1280
|
+
success: true,
|
|
1281
|
+
sessionId,
|
|
1282
|
+
checkpointId,
|
|
1283
|
+
message: `Session restored to checkpoint: ${checkpoint.description || checkpointId}`
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/**
|
|
1288
|
+
* Finalize collaboration session
|
|
1289
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1290
|
+
* @param {object} summary - Final summary
|
|
1291
|
+
*/
|
|
1292
|
+
function finalizeSession(sessionId, summary = {}) {
|
|
1293
|
+
const state = loadCollabState();
|
|
1294
|
+
const session = state.activeSessions[sessionId];
|
|
1295
|
+
|
|
1296
|
+
if (!session) {
|
|
1297
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const endTime = Date.now();
|
|
1301
|
+
const duration = endTime - session.metrics.startTime;
|
|
1302
|
+
|
|
1303
|
+
const completedSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.COMPLETE);
|
|
1304
|
+
const failedSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.FAILED);
|
|
1305
|
+
|
|
1306
|
+
session.status = 'complete';
|
|
1307
|
+
session.completedAt = new Date().toISOString();
|
|
1308
|
+
session.summary = {
|
|
1309
|
+
...summary,
|
|
1310
|
+
task: session.task,
|
|
1311
|
+
primaryAgent: session.primaryAgent,
|
|
1312
|
+
subtasksCompleted: completedSubtasks.length,
|
|
1313
|
+
subtasksFailed: failedSubtasks.length,
|
|
1314
|
+
totalSubtasks: session.subtasks.length,
|
|
1315
|
+
handoffsCount: session.handoffs.length,
|
|
1316
|
+
acceptedHandoffs: session.handoffs.filter(h => h.status === HANDOFF_STATUS.ACCEPTED).length,
|
|
1317
|
+
rejectedHandoffs: session.handoffs.filter(h => h.status === HANDOFF_STATUS.REJECTED).length,
|
|
1318
|
+
duration,
|
|
1319
|
+
durationFormatted: formatDuration(duration),
|
|
1320
|
+
artifactsCount: session.artifacts.length,
|
|
1321
|
+
agentParticipation: Object.entries(session.agentParticipants).map(([agent, data]) => ({
|
|
1322
|
+
agent,
|
|
1323
|
+
role: data.role,
|
|
1324
|
+
tasksAssigned: data.tasksAssigned,
|
|
1325
|
+
tasksCompleted: data.tasksCompleted
|
|
1326
|
+
})),
|
|
1327
|
+
learnings: session.learnings || [],
|
|
1328
|
+
checkpointsCount: session.checkpoints.length
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
// Calculate success rate
|
|
1332
|
+
session.summary.successRate = session.subtasks.length > 0
|
|
1333
|
+
? Math.round((completedSubtasks.length / session.subtasks.length) * 100)
|
|
1334
|
+
: 100;
|
|
1335
|
+
|
|
1336
|
+
// Move to completed
|
|
1337
|
+
state.completedSessions.push(session);
|
|
1338
|
+
delete state.activeSessions[sessionId];
|
|
1339
|
+
|
|
1340
|
+
// Clean up agent task queues
|
|
1341
|
+
for (const agent of Object.keys(session.agentParticipants)) {
|
|
1342
|
+
if (state.taskQueues[agent]) {
|
|
1343
|
+
state.taskQueues[agent] = state.taskQueues[agent]
|
|
1344
|
+
.filter(t => t.sessionId !== sessionId);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
// Update global metrics
|
|
1349
|
+
state.metrics.successfulSessions++;
|
|
1350
|
+
const totalDuration = state.metrics.avgSessionDuration * (state.metrics.successfulSessions - 1);
|
|
1351
|
+
state.metrics.avgSessionDuration = (totalDuration + duration) / state.metrics.successfulSessions;
|
|
1352
|
+
|
|
1353
|
+
state.history.push({
|
|
1354
|
+
action: 'session_finalized',
|
|
1355
|
+
sessionId,
|
|
1356
|
+
duration,
|
|
1357
|
+
successRate: session.summary.successRate,
|
|
1358
|
+
timestamp: new Date().toISOString()
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
saveCollabState(state);
|
|
1362
|
+
|
|
1363
|
+
emitCollabTelemetry('collab_session_completed', {
|
|
1364
|
+
sessionId,
|
|
1365
|
+
duration,
|
|
1366
|
+
successRate: session.summary.successRate,
|
|
1367
|
+
subtaskCount: session.subtasks.length,
|
|
1368
|
+
handoffCount: session.handoffs.length
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
return {
|
|
1372
|
+
success: true,
|
|
1373
|
+
sessionId,
|
|
1374
|
+
session,
|
|
1375
|
+
summary: session.summary,
|
|
1376
|
+
message: 'Collaboration session finalized'
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Abort a collaboration session
|
|
1382
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1383
|
+
* @param {string} reason - Abort reason
|
|
1384
|
+
*/
|
|
1385
|
+
function abortSession(sessionId, reason = '') {
|
|
1386
|
+
const state = loadCollabState();
|
|
1387
|
+
const session = state.activeSessions[sessionId];
|
|
1388
|
+
|
|
1389
|
+
if (!session) {
|
|
1390
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
session.status = 'aborted';
|
|
1394
|
+
session.abortedAt = new Date().toISOString();
|
|
1395
|
+
session.abortReason = reason;
|
|
1396
|
+
|
|
1397
|
+
// Move to completed (as aborted)
|
|
1398
|
+
state.completedSessions.push(session);
|
|
1399
|
+
delete state.activeSessions[sessionId];
|
|
1400
|
+
|
|
1401
|
+
// Clean up pending handoffs
|
|
1402
|
+
state.pendingHandoffs = state.pendingHandoffs
|
|
1403
|
+
.filter(h => h.sessionId !== sessionId);
|
|
1404
|
+
|
|
1405
|
+
// Clean up agent task queues
|
|
1406
|
+
for (const agent of Object.keys(session.agentParticipants)) {
|
|
1407
|
+
if (state.taskQueues[agent]) {
|
|
1408
|
+
state.taskQueues[agent] = state.taskQueues[agent]
|
|
1409
|
+
.filter(t => t.sessionId !== sessionId);
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
state.history.push({
|
|
1414
|
+
action: 'session_aborted',
|
|
1415
|
+
sessionId,
|
|
1416
|
+
reason,
|
|
1417
|
+
timestamp: new Date().toISOString()
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
saveCollabState(state);
|
|
1421
|
+
|
|
1422
|
+
return {
|
|
1423
|
+
success: true,
|
|
1424
|
+
sessionId,
|
|
1425
|
+
reason,
|
|
1426
|
+
message: `Collaboration session aborted: ${reason}`
|
|
1427
|
+
};
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* Format duration in human-readable format
|
|
1432
|
+
*/
|
|
1433
|
+
function formatDuration(ms) {
|
|
1434
|
+
const seconds = Math.floor(ms / 1000);
|
|
1435
|
+
const minutes = Math.floor(seconds / 60);
|
|
1436
|
+
const hours = Math.floor(minutes / 60);
|
|
1437
|
+
|
|
1438
|
+
if (hours > 0) {
|
|
1439
|
+
return `${hours}h ${minutes % 60}m`;
|
|
1440
|
+
} else if (minutes > 0) {
|
|
1441
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
1442
|
+
}
|
|
1443
|
+
return `${seconds}s`;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* Get session status
|
|
1448
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1449
|
+
*/
|
|
1450
|
+
function getSessionStatus(sessionId) {
|
|
1451
|
+
const state = loadCollabState();
|
|
1452
|
+
const session = state.activeSessions[sessionId];
|
|
1453
|
+
|
|
1454
|
+
if (!session) {
|
|
1455
|
+
// Check completed sessions
|
|
1456
|
+
const completed = state.completedSessions.find(s => s.id === sessionId);
|
|
1457
|
+
if (completed) {
|
|
1458
|
+
return {
|
|
1459
|
+
found: true,
|
|
1460
|
+
session: completed,
|
|
1461
|
+
isActive: false,
|
|
1462
|
+
summary: completed.summary
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
return { found: false };
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const pendingSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.PENDING);
|
|
1469
|
+
const inProgressSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.IN_PROGRESS);
|
|
1470
|
+
const blockedSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.BLOCKED);
|
|
1471
|
+
const completedSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.COMPLETE);
|
|
1472
|
+
const failedSubtasks = session.subtasks.filter(s => s.status === TASK_STATUS.FAILED);
|
|
1473
|
+
const pendingHandoffs = session.handoffs.filter(h => h.status === HANDOFF_STATUS.PENDING);
|
|
1474
|
+
|
|
1475
|
+
const elapsed = Date.now() - session.metrics.startTime;
|
|
1476
|
+
|
|
1477
|
+
return {
|
|
1478
|
+
found: true,
|
|
1479
|
+
isActive: true,
|
|
1480
|
+
session: {
|
|
1481
|
+
id: session.id,
|
|
1482
|
+
task: session.task,
|
|
1483
|
+
status: session.status,
|
|
1484
|
+
primaryAgent: session.primaryAgent,
|
|
1485
|
+
supportingAgents: session.supportingAgents,
|
|
1486
|
+
createdAt: session.createdAt
|
|
1487
|
+
},
|
|
1488
|
+
progress: {
|
|
1489
|
+
subtasks: {
|
|
1490
|
+
pending: pendingSubtasks.length,
|
|
1491
|
+
inProgress: inProgressSubtasks.length,
|
|
1492
|
+
blocked: blockedSubtasks.length,
|
|
1493
|
+
completed: completedSubtasks.length,
|
|
1494
|
+
failed: failedSubtasks.length,
|
|
1495
|
+
total: session.subtasks.length,
|
|
1496
|
+
percent: session.subtasks.length > 0
|
|
1497
|
+
? Math.round((completedSubtasks.length / session.subtasks.length) * 100)
|
|
1498
|
+
: 0
|
|
1499
|
+
},
|
|
1500
|
+
handoffs: {
|
|
1501
|
+
pending: pendingHandoffs.length,
|
|
1502
|
+
total: session.handoffs.length,
|
|
1503
|
+
accepted: session.handoffs.filter(h => h.status === HANDOFF_STATUS.ACCEPTED).length
|
|
1504
|
+
},
|
|
1505
|
+
artifacts: session.artifacts.length,
|
|
1506
|
+
checkpoints: session.checkpoints.length
|
|
1507
|
+
},
|
|
1508
|
+
agents: Object.entries(session.agentParticipants).map(([agent, data]) => ({
|
|
1509
|
+
agent,
|
|
1510
|
+
role: data.role,
|
|
1511
|
+
status: data.status,
|
|
1512
|
+
tasksAssigned: data.tasksAssigned,
|
|
1513
|
+
tasksCompleted: data.tasksCompleted
|
|
1514
|
+
})),
|
|
1515
|
+
timing: {
|
|
1516
|
+
elapsed,
|
|
1517
|
+
elapsedFormatted: formatDuration(elapsed)
|
|
1518
|
+
},
|
|
1519
|
+
nextActions: getNextActions(session, pendingSubtasks, pendingHandoffs, blockedSubtasks)
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
/**
|
|
1524
|
+
* Get suggested next actions for a session
|
|
1525
|
+
*/
|
|
1526
|
+
function getNextActions(session, pendingSubtasks, pendingHandoffs, blockedSubtasks) {
|
|
1527
|
+
const actions = [];
|
|
1528
|
+
|
|
1529
|
+
// Pending handoffs need attention
|
|
1530
|
+
if (pendingHandoffs.length > 0) {
|
|
1531
|
+
actions.push({
|
|
1532
|
+
type: 'accept_handoffs',
|
|
1533
|
+
urgency: 'high',
|
|
1534
|
+
count: pendingHandoffs.length,
|
|
1535
|
+
message: `${pendingHandoffs.length} handoff(s) awaiting acceptance`
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// Blocked tasks need resolution
|
|
1540
|
+
if (blockedSubtasks.length > 0) {
|
|
1541
|
+
actions.push({
|
|
1542
|
+
type: 'resolve_blockers',
|
|
1543
|
+
urgency: 'medium',
|
|
1544
|
+
count: blockedSubtasks.length,
|
|
1545
|
+
message: `${blockedSubtasks.length} task(s) blocked on dependencies`
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
// Pending tasks ready to start
|
|
1550
|
+
if (pendingSubtasks.length > 0) {
|
|
1551
|
+
const highPriority = pendingSubtasks.filter(t => t.priority === TASK_PRIORITY.HIGH || t.priority === TASK_PRIORITY.CRITICAL);
|
|
1552
|
+
if (highPriority.length > 0) {
|
|
1553
|
+
actions.push({
|
|
1554
|
+
type: 'start_high_priority',
|
|
1555
|
+
urgency: 'high',
|
|
1556
|
+
count: highPriority.length,
|
|
1557
|
+
message: `${highPriority.length} high priority task(s) ready to start`
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Check if ready to finalize
|
|
1563
|
+
const allComplete = session.subtasks.length > 0 &&
|
|
1564
|
+
session.subtasks.every(s =>
|
|
1565
|
+
s.status === TASK_STATUS.COMPLETE || s.status === TASK_STATUS.FAILED
|
|
1566
|
+
);
|
|
1567
|
+
|
|
1568
|
+
if (allComplete) {
|
|
1569
|
+
actions.push({
|
|
1570
|
+
type: 'finalize_session',
|
|
1571
|
+
urgency: 'low',
|
|
1572
|
+
message: 'All tasks processed. Ready to finalize session.'
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
return actions;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
/**
|
|
1580
|
+
* Get the next task for an agent
|
|
1581
|
+
* @param {string} agentName - Agent name
|
|
1582
|
+
*/
|
|
1583
|
+
function getNextTaskForAgent(agentName) {
|
|
1584
|
+
const state = loadCollabState();
|
|
1585
|
+
const agentQueue = state.taskQueues[agentName] || [];
|
|
1586
|
+
|
|
1587
|
+
if (agentQueue.length === 0) {
|
|
1588
|
+
return { hasTask: false, message: 'No tasks in queue' };
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// Sort by priority and creation time
|
|
1592
|
+
const priorityOrder = {
|
|
1593
|
+
[TASK_PRIORITY.CRITICAL]: 0,
|
|
1594
|
+
[TASK_PRIORITY.HIGH]: 1,
|
|
1595
|
+
[TASK_PRIORITY.MEDIUM]: 2,
|
|
1596
|
+
[TASK_PRIORITY.LOW]: 3
|
|
1597
|
+
};
|
|
1598
|
+
|
|
1599
|
+
agentQueue.sort((a, b) => {
|
|
1600
|
+
const priorityDiff = (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);
|
|
1601
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
1602
|
+
return new Date(a.createdAt) - new Date(b.createdAt);
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
const nextItem = agentQueue[0];
|
|
1606
|
+
const session = state.activeSessions[nextItem.sessionId];
|
|
1607
|
+
|
|
1608
|
+
if (!session) {
|
|
1609
|
+
// Task from completed/aborted session, remove it
|
|
1610
|
+
state.taskQueues[agentName] = agentQueue.slice(1);
|
|
1611
|
+
saveCollabState(state);
|
|
1612
|
+
return getNextTaskForAgent(agentName); // Recurse
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const subtask = session.subtasks.find(s => s.id === nextItem.subtaskId);
|
|
1616
|
+
|
|
1617
|
+
if (!subtask || subtask.status === TASK_STATUS.BLOCKED) {
|
|
1618
|
+
// Skip blocked tasks
|
|
1619
|
+
return {
|
|
1620
|
+
hasTask: false,
|
|
1621
|
+
blocked: true,
|
|
1622
|
+
message: 'Next task is blocked on dependencies'
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
return {
|
|
1627
|
+
hasTask: true,
|
|
1628
|
+
sessionId: nextItem.sessionId,
|
|
1629
|
+
subtask,
|
|
1630
|
+
sessionContext: session.context.current,
|
|
1631
|
+
message: `Next task: ${subtask.title}`
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* List active sessions
|
|
1637
|
+
*/
|
|
1638
|
+
function listActiveSessions() {
|
|
1639
|
+
const state = loadCollabState();
|
|
1640
|
+
|
|
1641
|
+
return Object.values(state.activeSessions).map(session => ({
|
|
1642
|
+
id: session.id,
|
|
1643
|
+
task: session.task,
|
|
1644
|
+
primaryAgent: session.primaryAgent,
|
|
1645
|
+
status: session.status,
|
|
1646
|
+
subtaskCount: session.subtasks.length,
|
|
1647
|
+
createdAt: session.createdAt
|
|
1648
|
+
}));
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
/**
|
|
1652
|
+
* Get pending handoffs for an agent
|
|
1653
|
+
* @param {string} agentName - Agent name
|
|
1654
|
+
*/
|
|
1655
|
+
function getPendingHandoffsForAgent(agentName) {
|
|
1656
|
+
const state = loadCollabState();
|
|
1657
|
+
|
|
1658
|
+
return state.pendingHandoffs.filter(h => h.toAgent === agentName);
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* Collaboration patterns for common scenarios
|
|
1663
|
+
*/
|
|
1664
|
+
const COLLAB_PATTERNS = {
|
|
1665
|
+
'code-review': {
|
|
1666
|
+
name: 'Code Review',
|
|
1667
|
+
description: 'Multi-agent code review with security and performance focus',
|
|
1668
|
+
primaryAgent: 'code-review-expert',
|
|
1669
|
+
supportingAgents: ['security-expert', 'performance-expert'],
|
|
1670
|
+
subtaskTemplate: [
|
|
1671
|
+
{ title: 'Security Review', delegatedTo: 'security-expert', priority: TASK_PRIORITY.HIGH },
|
|
1672
|
+
{ title: 'Performance Review', delegatedTo: 'performance-expert', priority: TASK_PRIORITY.MEDIUM }
|
|
1673
|
+
]
|
|
1674
|
+
},
|
|
1675
|
+
'feature-build': {
|
|
1676
|
+
name: 'Feature Build',
|
|
1677
|
+
description: 'Collaborative feature development with parallel tracks',
|
|
1678
|
+
primaryAgent: 'architecture-expert',
|
|
1679
|
+
supportingAgents: ['backend-expert', 'frontend-expert', 'database-expert', 'testing-expert'],
|
|
1680
|
+
subtaskTemplate: [
|
|
1681
|
+
{ title: 'Architecture Design', delegatedTo: 'architecture-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1682
|
+
{ title: 'Database Schema', delegatedTo: 'database-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1683
|
+
{ title: 'API Implementation', delegatedTo: 'backend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-2'] },
|
|
1684
|
+
{ title: 'UI Implementation', delegatedTo: 'frontend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1685
|
+
{ title: 'Integration Tests', delegatedTo: 'testing-expert', priority: TASK_PRIORITY.MEDIUM, dependencies: ['subtask-3', 'subtask-4'] }
|
|
1686
|
+
]
|
|
1687
|
+
},
|
|
1688
|
+
'security-hardening': {
|
|
1689
|
+
name: 'Security Hardening',
|
|
1690
|
+
description: 'Security-focused collaboration',
|
|
1691
|
+
primaryAgent: 'security-expert',
|
|
1692
|
+
supportingAgents: ['backend-expert', 'devops-expert'],
|
|
1693
|
+
subtaskTemplate: [
|
|
1694
|
+
{ title: 'Vulnerability Assessment', delegatedTo: 'security-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1695
|
+
{ title: 'Code Security Fixes', delegatedTo: 'backend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1696
|
+
{ title: 'Infrastructure Hardening', delegatedTo: 'devops-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1697
|
+
{ title: 'Security Verification', delegatedTo: 'security-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-2', 'subtask-3'] }
|
|
1698
|
+
]
|
|
1699
|
+
},
|
|
1700
|
+
'api-development': {
|
|
1701
|
+
name: 'API Development',
|
|
1702
|
+
description: 'End-to-end API development with documentation',
|
|
1703
|
+
primaryAgent: 'api-expert',
|
|
1704
|
+
supportingAgents: ['database-expert', 'security-expert', 'testing-expert'],
|
|
1705
|
+
subtaskTemplate: [
|
|
1706
|
+
{ title: 'API Design', delegatedTo: 'api-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1707
|
+
{ title: 'Data Model', delegatedTo: 'database-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1708
|
+
{ title: 'API Implementation', delegatedTo: 'api-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-2'] },
|
|
1709
|
+
{ title: 'Security Review', delegatedTo: 'security-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-3'] },
|
|
1710
|
+
{ title: 'API Tests', delegatedTo: 'testing-expert', priority: TASK_PRIORITY.MEDIUM, dependencies: ['subtask-3'] }
|
|
1711
|
+
]
|
|
1712
|
+
},
|
|
1713
|
+
'full-stack-feature': {
|
|
1714
|
+
name: 'Full Stack Feature',
|
|
1715
|
+
description: 'Complete full-stack feature with frontend, backend, and testing',
|
|
1716
|
+
primaryAgent: 'architecture-expert',
|
|
1717
|
+
supportingAgents: ['frontend-expert', 'backend-expert', 'database-expert', 'testing-expert', 'ui-ux-expert'],
|
|
1718
|
+
subtaskTemplate: [
|
|
1719
|
+
{ title: 'Feature Specification', delegatedTo: 'architecture-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1720
|
+
{ title: 'UI/UX Design', delegatedTo: 'ui-ux-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1721
|
+
{ title: 'Data Model', delegatedTo: 'database-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1722
|
+
{ title: 'Backend API', delegatedTo: 'backend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-3'] },
|
|
1723
|
+
{ title: 'Frontend UI', delegatedTo: 'frontend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-2', 'subtask-4'] },
|
|
1724
|
+
{ title: 'Integration Testing', delegatedTo: 'testing-expert', priority: TASK_PRIORITY.MEDIUM, dependencies: ['subtask-4', 'subtask-5'] }
|
|
1725
|
+
]
|
|
1726
|
+
},
|
|
1727
|
+
'performance-optimization': {
|
|
1728
|
+
name: 'Performance Optimization',
|
|
1729
|
+
description: 'Performance analysis and optimization across the stack',
|
|
1730
|
+
primaryAgent: 'performance-expert',
|
|
1731
|
+
supportingAgents: ['database-expert', 'frontend-expert', 'backend-expert'],
|
|
1732
|
+
subtaskTemplate: [
|
|
1733
|
+
{ title: 'Performance Audit', delegatedTo: 'performance-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1734
|
+
{ title: 'Database Optimization', delegatedTo: 'database-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1735
|
+
{ title: 'Backend Optimization', delegatedTo: 'backend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1736
|
+
{ title: 'Frontend Optimization', delegatedTo: 'frontend-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1'] },
|
|
1737
|
+
{ title: 'Performance Verification', delegatedTo: 'performance-expert', priority: TASK_PRIORITY.MEDIUM, dependencies: ['subtask-2', 'subtask-3', 'subtask-4'] }
|
|
1738
|
+
]
|
|
1739
|
+
},
|
|
1740
|
+
'launch-prep': {
|
|
1741
|
+
name: 'Launch Preparation',
|
|
1742
|
+
description: 'Pre-launch checklist with security, performance, and deployment',
|
|
1743
|
+
primaryAgent: 'devops-expert',
|
|
1744
|
+
supportingAgents: ['security-expert', 'performance-expert', 'testing-expert'],
|
|
1745
|
+
subtaskTemplate: [
|
|
1746
|
+
{ title: 'Security Audit', delegatedTo: 'security-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1747
|
+
{ title: 'Performance Testing', delegatedTo: 'performance-expert', priority: TASK_PRIORITY.CRITICAL },
|
|
1748
|
+
{ title: 'End-to-End Tests', delegatedTo: 'testing-expert', priority: TASK_PRIORITY.HIGH },
|
|
1749
|
+
{ title: 'Deployment Config', delegatedTo: 'devops-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-1', 'subtask-2'] },
|
|
1750
|
+
{ title: 'Launch Verification', delegatedTo: 'devops-expert', priority: TASK_PRIORITY.HIGH, dependencies: ['subtask-3', 'subtask-4'] }
|
|
1751
|
+
]
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
/**
|
|
1756
|
+
* Start session from pattern
|
|
1757
|
+
* @param {string} patternName - Pattern name
|
|
1758
|
+
* @param {string} task - Task description
|
|
1759
|
+
* @param {object} options - Additional options
|
|
1760
|
+
*/
|
|
1761
|
+
function startFromPattern(patternName, task, options = {}) {
|
|
1762
|
+
const pattern = COLLAB_PATTERNS[patternName];
|
|
1763
|
+
if (!pattern) {
|
|
1764
|
+
return {
|
|
1765
|
+
success: false,
|
|
1766
|
+
error: `Unknown pattern: ${patternName}`,
|
|
1767
|
+
availablePatterns: Object.keys(COLLAB_PATTERNS)
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const { context = {}, autoStart = false } = options;
|
|
1772
|
+
|
|
1773
|
+
// Start session
|
|
1774
|
+
const sessionResult = startSession({
|
|
1775
|
+
task,
|
|
1776
|
+
primaryAgent: pattern.primaryAgent,
|
|
1777
|
+
supportingAgents: pattern.supportingAgents,
|
|
1778
|
+
context: {
|
|
1779
|
+
pattern: patternName,
|
|
1780
|
+
...context
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
|
|
1784
|
+
if (!sessionResult.success) {
|
|
1785
|
+
return sessionResult;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// Create subtasks from template, mapping template dependencies to real IDs
|
|
1789
|
+
const subtaskResults = [];
|
|
1790
|
+
const idMapping = {};
|
|
1791
|
+
|
|
1792
|
+
for (let i = 0; i < pattern.subtaskTemplate.length; i++) {
|
|
1793
|
+
const template = pattern.subtaskTemplate[i];
|
|
1794
|
+
const templateId = `subtask-${i + 1}`;
|
|
1795
|
+
|
|
1796
|
+
// Map template dependencies to actual subtask IDs
|
|
1797
|
+
const mappedDependencies = (template.dependencies || []).map(depId => {
|
|
1798
|
+
return idMapping[depId] || depId;
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
const result = delegateTask(sessionResult.sessionId, {
|
|
1802
|
+
...template,
|
|
1803
|
+
description: `${template.title} for: ${task}`,
|
|
1804
|
+
dependencies: mappedDependencies
|
|
1805
|
+
});
|
|
1806
|
+
|
|
1807
|
+
if (result.success) {
|
|
1808
|
+
idMapping[templateId] = result.subtaskId;
|
|
1809
|
+
}
|
|
1810
|
+
subtaskResults.push(result);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// Auto-start first task if requested
|
|
1814
|
+
if (autoStart && subtaskResults.length > 0 && subtaskResults[0].success) {
|
|
1815
|
+
startSubtask(sessionResult.sessionId, subtaskResults[0].subtaskId);
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
return {
|
|
1819
|
+
success: true,
|
|
1820
|
+
sessionId: sessionResult.sessionId,
|
|
1821
|
+
pattern: patternName,
|
|
1822
|
+
patternName: pattern.name,
|
|
1823
|
+
primaryAgent: pattern.primaryAgent,
|
|
1824
|
+
subtasks: subtaskResults.map(r => ({
|
|
1825
|
+
id: r.subtaskId,
|
|
1826
|
+
title: r.subtask?.title,
|
|
1827
|
+
delegatedTo: r.subtask?.delegatedTo,
|
|
1828
|
+
status: r.subtask?.status
|
|
1829
|
+
})),
|
|
1830
|
+
message: `Started collaboration from "${pattern.name}" pattern`
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
/**
|
|
1835
|
+
* Get collaboration statistics
|
|
1836
|
+
*/
|
|
1837
|
+
function getCollabStats() {
|
|
1838
|
+
const state = loadCollabState();
|
|
1839
|
+
|
|
1840
|
+
const activeSessions = Object.values(state.activeSessions);
|
|
1841
|
+
const totalActive = activeSessions.length;
|
|
1842
|
+
const totalSubtasksActive = activeSessions.reduce((sum, s) => sum + s.subtasks.length, 0);
|
|
1843
|
+
const pendingHandoffs = state.pendingHandoffs.length;
|
|
1844
|
+
|
|
1845
|
+
return {
|
|
1846
|
+
activeSessions: totalActive,
|
|
1847
|
+
completedSessions: state.completedSessions.length,
|
|
1848
|
+
totalSessions: state.metrics.totalSessions,
|
|
1849
|
+
successfulSessions: state.metrics.successfulSessions,
|
|
1850
|
+
totalSubtasks: state.metrics.totalSubtasks,
|
|
1851
|
+
totalHandoffs: state.metrics.totalHandoffs,
|
|
1852
|
+
activeSubtasks: totalSubtasksActive,
|
|
1853
|
+
pendingHandoffs,
|
|
1854
|
+
avgSessionDuration: state.metrics.avgSessionDuration,
|
|
1855
|
+
avgSessionDurationFormatted: formatDuration(state.metrics.avgSessionDuration || 0),
|
|
1856
|
+
blockedTasks: state.blockedTasks.length
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* Get session history
|
|
1862
|
+
* @param {string} sessionId - Optional session ID for filtered history
|
|
1863
|
+
*/
|
|
1864
|
+
function getSessionHistory(sessionId = null) {
|
|
1865
|
+
const state = loadCollabState();
|
|
1866
|
+
|
|
1867
|
+
if (sessionId) {
|
|
1868
|
+
return state.history.filter(h => h.sessionId === sessionId);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
return state.history.slice(-100); // Last 100 events
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* List available patterns
|
|
1876
|
+
*/
|
|
1877
|
+
function listPatterns() {
|
|
1878
|
+
return Object.entries(COLLAB_PATTERNS).map(([key, pattern]) => ({
|
|
1879
|
+
key,
|
|
1880
|
+
name: pattern.name,
|
|
1881
|
+
description: pattern.description,
|
|
1882
|
+
primaryAgent: pattern.primaryAgent,
|
|
1883
|
+
supportingAgents: pattern.supportingAgents,
|
|
1884
|
+
subtaskCount: pattern.subtaskTemplate.length
|
|
1885
|
+
}));
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Broadcast message to all agents in a session
|
|
1890
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1891
|
+
* @param {object} message - Message to broadcast
|
|
1892
|
+
*/
|
|
1893
|
+
function broadcastToSession(sessionId, message) {
|
|
1894
|
+
const state = loadCollabState();
|
|
1895
|
+
const session = state.activeSessions[sessionId];
|
|
1896
|
+
|
|
1897
|
+
if (!session) {
|
|
1898
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
const { type, content, fromAgent, priority = 'normal' } = message;
|
|
1902
|
+
|
|
1903
|
+
const broadcast = {
|
|
1904
|
+
id: `broadcast-${Date.now()}`,
|
|
1905
|
+
type,
|
|
1906
|
+
content,
|
|
1907
|
+
fromAgent,
|
|
1908
|
+
priority,
|
|
1909
|
+
timestamp: new Date().toISOString(),
|
|
1910
|
+
recipients: Object.keys(session.agentParticipants)
|
|
1911
|
+
};
|
|
1912
|
+
|
|
1913
|
+
session.broadcasts = session.broadcasts || [];
|
|
1914
|
+
session.broadcasts.push(broadcast);
|
|
1915
|
+
|
|
1916
|
+
state.history.push({
|
|
1917
|
+
action: 'broadcast_sent',
|
|
1918
|
+
sessionId,
|
|
1919
|
+
broadcastId: broadcast.id,
|
|
1920
|
+
fromAgent,
|
|
1921
|
+
recipientCount: broadcast.recipients.length,
|
|
1922
|
+
timestamp: broadcast.timestamp
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
saveCollabState(state);
|
|
1926
|
+
|
|
1927
|
+
return {
|
|
1928
|
+
success: true,
|
|
1929
|
+
broadcastId: broadcast.id,
|
|
1930
|
+
recipients: broadcast.recipients,
|
|
1931
|
+
message: `Broadcast sent to ${broadcast.recipients.length} agents`
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/**
|
|
1936
|
+
* Share an artifact with the session
|
|
1937
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1938
|
+
* @param {object} artifact - Artifact to share
|
|
1939
|
+
*/
|
|
1940
|
+
function shareArtifact(sessionId, artifact) {
|
|
1941
|
+
const state = loadCollabState();
|
|
1942
|
+
const session = state.activeSessions[sessionId];
|
|
1943
|
+
|
|
1944
|
+
if (!session) {
|
|
1945
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
const { name, type, content, fromAgent, description = '' } = artifact;
|
|
1949
|
+
|
|
1950
|
+
if (!name || !type || !fromAgent) {
|
|
1951
|
+
return { success: false, error: 'name, type, and fromAgent are required' };
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const sharedArtifact = {
|
|
1955
|
+
id: `artifact-${Date.now()}`,
|
|
1956
|
+
name,
|
|
1957
|
+
type,
|
|
1958
|
+
content,
|
|
1959
|
+
description,
|
|
1960
|
+
fromAgent,
|
|
1961
|
+
sharedAt: new Date().toISOString()
|
|
1962
|
+
};
|
|
1963
|
+
|
|
1964
|
+
session.artifacts.push(sharedArtifact);
|
|
1965
|
+
|
|
1966
|
+
state.history.push({
|
|
1967
|
+
action: 'artifact_shared',
|
|
1968
|
+
sessionId,
|
|
1969
|
+
artifactId: sharedArtifact.id,
|
|
1970
|
+
name,
|
|
1971
|
+
type,
|
|
1972
|
+
fromAgent,
|
|
1973
|
+
timestamp: sharedArtifact.sharedAt
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
saveCollabState(state);
|
|
1977
|
+
|
|
1978
|
+
return {
|
|
1979
|
+
success: true,
|
|
1980
|
+
artifactId: sharedArtifact.id,
|
|
1981
|
+
message: `Artifact "${name}" shared with session`
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/**
|
|
1986
|
+
* Get all artifacts from a session
|
|
1987
|
+
* @param {string} sessionId - Collaboration session ID
|
|
1988
|
+
*/
|
|
1989
|
+
function getSessionArtifacts(sessionId) {
|
|
1990
|
+
const state = loadCollabState();
|
|
1991
|
+
const session = state.activeSessions[sessionId] ||
|
|
1992
|
+
state.completedSessions.find(s => s.id === sessionId);
|
|
1993
|
+
|
|
1994
|
+
if (!session) {
|
|
1995
|
+
return { success: false, error: `Session not found: ${sessionId}` };
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
return {
|
|
1999
|
+
success: true,
|
|
2000
|
+
sessionId,
|
|
2001
|
+
artifacts: session.artifacts || [],
|
|
2002
|
+
count: (session.artifacts || []).length
|
|
2003
|
+
};
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
module.exports = {
|
|
2007
|
+
// Session management
|
|
2008
|
+
startSession,
|
|
2009
|
+
finalizeSession,
|
|
2010
|
+
abortSession,
|
|
2011
|
+
getSessionStatus,
|
|
2012
|
+
listActiveSessions,
|
|
2013
|
+
getSessionHistory,
|
|
2014
|
+
|
|
2015
|
+
// Task delegation
|
|
2016
|
+
delegateTask,
|
|
2017
|
+
startSubtask,
|
|
2018
|
+
updateSubtaskProgress,
|
|
2019
|
+
completeSubtask,
|
|
2020
|
+
failSubtask,
|
|
2021
|
+
retrySubtask,
|
|
2022
|
+
getNextTaskForAgent,
|
|
2023
|
+
|
|
2024
|
+
// Handoff protocol
|
|
2025
|
+
requestHandoff,
|
|
2026
|
+
acceptHandoff,
|
|
2027
|
+
rejectHandoff,
|
|
2028
|
+
completeHandoff,
|
|
2029
|
+
getPendingHandoffsForAgent,
|
|
2030
|
+
|
|
2031
|
+
// Checkpoints and recovery
|
|
2032
|
+
createCheckpoint,
|
|
2033
|
+
restoreFromCheckpoint,
|
|
2034
|
+
|
|
2035
|
+
// Patterns
|
|
2036
|
+
startFromPattern,
|
|
2037
|
+
listPatterns,
|
|
2038
|
+
COLLAB_PATTERNS,
|
|
2039
|
+
|
|
2040
|
+
// Communication
|
|
2041
|
+
broadcastToSession,
|
|
2042
|
+
shareArtifact,
|
|
2043
|
+
getSessionArtifacts,
|
|
2044
|
+
|
|
2045
|
+
// Agent capabilities
|
|
2046
|
+
findBestAgentForTask,
|
|
2047
|
+
|
|
2048
|
+
// Statistics and state
|
|
2049
|
+
getCollabStats,
|
|
2050
|
+
loadCollabState,
|
|
2051
|
+
|
|
2052
|
+
// Constants
|
|
2053
|
+
TASK_PRIORITY,
|
|
2054
|
+
TASK_STATUS,
|
|
2055
|
+
HANDOFF_STATUS,
|
|
2056
|
+
AGENT_ROLE
|
|
2057
|
+
};
|