@desplega.ai/agent-swarm 1.2.0 → 1.9.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/.claude/settings.local.json +20 -1
- package/.dockerignore +3 -0
- package/.env.docker.example +22 -1
- package/.env.example +17 -0
- package/.github/workflows/docker-publish.yml +92 -0
- package/CONTRIBUTING.md +270 -0
- package/DEPLOYMENT.md +391 -0
- package/Dockerfile.worker +29 -1
- package/FAQ.md +19 -0
- package/LICENSE +21 -0
- package/MCP.md +249 -0
- package/README.md +105 -185
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/assets/agent-swarm.png +0 -0
- package/deploy/docker-push.ts +30 -0
- package/docker-compose.example.yml +137 -0
- package/docker-entrypoint.sh +223 -7
- package/package.json +13 -4
- package/{cc-plugin → plugin}/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/codebase-analyzer.md +143 -0
- package/plugin/agents/codebase-locator.md +122 -0
- package/plugin/agents/codebase-pattern-finder.md +227 -0
- package/plugin/agents/web-search-researcher.md +109 -0
- package/plugin/commands/create-plan.md +415 -0
- package/plugin/commands/implement-plan.md +89 -0
- package/plugin/commands/research.md +200 -0
- package/plugin/commands/start-leader.md +101 -0
- package/plugin/commands/start-worker.md +56 -0
- package/plugin/commands/swarm-chat.md +78 -0
- package/plugin/commands/todos.md +66 -0
- package/plugin/commands/work-on-task.md +44 -0
- package/plugin/skills/.gitkeep +0 -0
- package/scripts/generate-mcp-docs.ts +415 -0
- package/slack-manifest.json +69 -0
- package/src/be/db.ts +1431 -25
- package/src/cli.tsx +135 -11
- package/src/commands/lead.ts +13 -0
- package/src/commands/runner.ts +255 -0
- package/src/commands/setup.tsx +5 -5
- package/src/commands/worker.ts +8 -220
- package/src/hooks/hook.ts +108 -14
- package/src/http.ts +361 -5
- package/src/prompts/base-prompt.ts +131 -0
- package/src/server.ts +56 -0
- package/src/slack/app.ts +73 -0
- package/src/slack/commands.ts +88 -0
- package/src/slack/handlers.ts +281 -0
- package/src/slack/index.ts +3 -0
- package/src/slack/responses.ts +175 -0
- package/src/slack/router.ts +170 -0
- package/src/slack/types.ts +20 -0
- package/src/slack/watcher.ts +119 -0
- package/src/tools/create-channel.ts +80 -0
- package/src/tools/get-tasks.ts +54 -21
- package/src/tools/join-swarm.ts +28 -4
- package/src/tools/list-channels.ts +37 -0
- package/src/tools/list-services.ts +110 -0
- package/src/tools/poll-task.ts +47 -3
- package/src/tools/post-message.ts +87 -0
- package/src/tools/read-messages.ts +192 -0
- package/src/tools/register-service.ts +118 -0
- package/src/tools/send-task.ts +80 -7
- package/src/tools/store-progress.ts +9 -3
- package/src/tools/task-action.ts +211 -0
- package/src/tools/unregister-service.ts +110 -0
- package/src/tools/update-profile.ts +105 -0
- package/src/tools/update-service-status.ts +118 -0
- package/src/types.ts +110 -3
- package/src/utils/pretty-print.ts +224 -0
- package/thoughts/shared/plans/.gitkeep +0 -0
- package/thoughts/shared/plans/2025-12-18-inverse-teleport.md +1142 -0
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +1195 -0
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +732 -0
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +361 -0
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +501 -0
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +560 -0
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-12-18-slack-integration.md +442 -0
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +339 -0
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +390 -0
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +376 -0
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +264 -0
- package/tsconfig.json +3 -1
- package/ui/bun.lock +692 -0
- package/ui/index.html +22 -0
- package/ui/package.json +32 -0
- package/ui/pnpm-lock.yaml +3034 -0
- package/ui/postcss.config.js +6 -0
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +43 -0
- package/ui/src/components/ActivityFeed.tsx +415 -0
- package/ui/src/components/AgentDetailPanel.tsx +534 -0
- package/ui/src/components/AgentsPanel.tsx +549 -0
- package/ui/src/components/ChatPanel.tsx +1820 -0
- package/ui/src/components/ConfigModal.tsx +232 -0
- package/ui/src/components/Dashboard.tsx +534 -0
- package/ui/src/components/Header.tsx +168 -0
- package/ui/src/components/ServicesPanel.tsx +612 -0
- package/ui/src/components/StatsBar.tsx +288 -0
- package/ui/src/components/StatusBadge.tsx +124 -0
- package/ui/src/components/TaskDetailPanel.tsx +807 -0
- package/ui/src/components/TasksPanel.tsx +575 -0
- package/ui/src/hooks/queries.ts +170 -0
- package/ui/src/index.css +235 -0
- package/ui/src/lib/api.ts +161 -0
- package/ui/src/lib/config.ts +35 -0
- package/ui/src/lib/theme.ts +214 -0
- package/ui/src/lib/utils.ts +48 -0
- package/ui/src/main.tsx +32 -0
- package/ui/src/types/api.ts +164 -0
- package/ui/src/vite-env.d.ts +1 -0
- package/ui/tailwind.config.js +35 -0
- package/ui/tsconfig.json +31 -0
- package/ui/vite.config.ts +22 -0
- package/cc-plugin/README.md +0 -49
- package/cc-plugin/commands/setup-leader.md +0 -73
- package/cc-plugin/commands/start-worker.md +0 -64
- package/docker-compose.worker.yml +0 -35
- package/example-req-meta.json +0 -24
- /package/{cc-plugin → plugin}/hooks/hooks.json +0 -0
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
# Inverse Teleport Implementation Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Implement "teleport-out" feature: a local Claude Code session can transfer its context to a distributed worker agent to continue the work. This is the inverse of Claude Code Web's teleport feature (which brings web sessions to local CLI).
|
|
6
|
+
|
|
7
|
+
## Current State Analysis
|
|
8
|
+
|
|
9
|
+
The Agent Swarm MCP currently provides:
|
|
10
|
+
- HTTP server with REST API and MCP transport (`src/http.ts`)
|
|
11
|
+
- SQLite database with `agents`, `agent_tasks`, and `agent_log` tables (`src/be/db.ts`)
|
|
12
|
+
- 8 MCP tools for agent coordination (`src/tools/*.ts`)
|
|
13
|
+
- CLI runners for lead/worker agents (`src/commands/*.ts`)
|
|
14
|
+
- Hook system for session lifecycle events (`src/hooks/hook.ts`)
|
|
15
|
+
|
|
16
|
+
**What's Missing:**
|
|
17
|
+
- No mechanism for local sessions to export context
|
|
18
|
+
- Tasks are simple text strings, not rich context packages
|
|
19
|
+
- Workers have no way to receive session context beyond task descriptions
|
|
20
|
+
- No teleport request tracking or lifecycle management
|
|
21
|
+
|
|
22
|
+
### Key Discoveries:
|
|
23
|
+
- Tasks flow through `send-task` tool with simple string payload (`src/tools/send-task.ts`)
|
|
24
|
+
- Workers poll for tasks via `poll-task` with 60-second timeout (`src/tools/poll-task.ts`)
|
|
25
|
+
- Hook system can provide guidance to workers on session start (`src/hooks/hook.ts:44-76`)
|
|
26
|
+
- Database uses transactions for atomic operations (`src/be/db.ts`)
|
|
27
|
+
|
|
28
|
+
## Desired End State
|
|
29
|
+
|
|
30
|
+
After implementation:
|
|
31
|
+
1. Local Claude Code session calls `teleport-out` with summary and context
|
|
32
|
+
2. Teleport request is stored in database with rich context package
|
|
33
|
+
3. Worker polls for teleports via `poll-teleport` and claims one atomically
|
|
34
|
+
4. Worker continues the work with full context understanding
|
|
35
|
+
5. Teleport lifecycle is tracked (pending → claimed → started → completed/failed)
|
|
36
|
+
|
|
37
|
+
### Verification:
|
|
38
|
+
- Local session can call `teleport-out` and receive teleport ID
|
|
39
|
+
- Worker receives rich context package via `poll-teleport`
|
|
40
|
+
- Teleport status visible in dashboard/API
|
|
41
|
+
- Worker completes task with proper tracking
|
|
42
|
+
|
|
43
|
+
## What We're NOT Doing
|
|
44
|
+
|
|
45
|
+
- **Full conversation history transfer** - Summary + context is sufficient
|
|
46
|
+
- **Claude CLI session file sync** - Workers are distributed (no shared filesystem)
|
|
47
|
+
- **Native `--resume` integration** - Would require session file transfer
|
|
48
|
+
- **Multi-teleport fan-out** - One teleport goes to one worker
|
|
49
|
+
- **Teleport cancellation UI** - Can be added later
|
|
50
|
+
- **File content sync** - Workers must have access to the same repo/codebase
|
|
51
|
+
|
|
52
|
+
## Implementation Approach
|
|
53
|
+
|
|
54
|
+
Add a new `teleport_requests` table with rich context schema. Create 5 new MCP tools for the teleport lifecycle. Workers check for teleports before regular tasks. Context is provided as initial prompt material.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Phase 1: Database Schema Updates
|
|
59
|
+
|
|
60
|
+
### Overview
|
|
61
|
+
Add teleport request storage and tracking.
|
|
62
|
+
|
|
63
|
+
### Changes Required:
|
|
64
|
+
|
|
65
|
+
#### 1. Types (`src/types.ts`)
|
|
66
|
+
|
|
67
|
+
Add teleport request schema after line 66 (after AgentLogSchema):
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Teleport Request Types
|
|
71
|
+
export const TeleportRequestStatusSchema = z.enum([
|
|
72
|
+
"pending", // Created, waiting for worker
|
|
73
|
+
"claimed", // Worker claimed it
|
|
74
|
+
"started", // Worker began work
|
|
75
|
+
"completed", // Work finished successfully
|
|
76
|
+
"failed" // Work failed
|
|
77
|
+
]);
|
|
78
|
+
export type TeleportRequestStatus = z.infer<typeof TeleportRequestStatusSchema>;
|
|
79
|
+
|
|
80
|
+
export const RelevantFileSchema = z.object({
|
|
81
|
+
path: z.string(),
|
|
82
|
+
summary: z.string().optional(),
|
|
83
|
+
content: z.string().optional(),
|
|
84
|
+
});
|
|
85
|
+
export type RelevantFile = z.infer<typeof RelevantFileSchema>;
|
|
86
|
+
|
|
87
|
+
export const TeleportRequestSchema = z.object({
|
|
88
|
+
id: z.string().uuid(),
|
|
89
|
+
sourceAgentId: z.string().optional(), // Who sent it (may be null for non-swarm sessions)
|
|
90
|
+
targetAgentId: z.string().optional(), // Specific worker or null for any
|
|
91
|
+
status: TeleportRequestStatusSchema,
|
|
92
|
+
|
|
93
|
+
// Context Package
|
|
94
|
+
summary: z.string().min(1), // Required: AI summary of session
|
|
95
|
+
currentGoal: z.string().optional(), // What to accomplish
|
|
96
|
+
relevantFiles: z.string().optional(), // JSON array of RelevantFile
|
|
97
|
+
contextNotes: z.string().optional(), // Additional context
|
|
98
|
+
workingDirectory: z.string().optional(), // CWD of original session
|
|
99
|
+
projectPath: z.string().optional(), // Project root
|
|
100
|
+
|
|
101
|
+
// Timestamps
|
|
102
|
+
createdAt: z.string(),
|
|
103
|
+
claimedAt: z.string().optional(),
|
|
104
|
+
claimedBy: z.string().optional(), // Agent ID that claimed it
|
|
105
|
+
startedAt: z.string().optional(),
|
|
106
|
+
finishedAt: z.string().optional(),
|
|
107
|
+
|
|
108
|
+
// Result
|
|
109
|
+
resultTaskId: z.string().optional(), // Links to agent_tasks when work begins
|
|
110
|
+
output: z.string().optional(),
|
|
111
|
+
failureReason: z.string().optional(),
|
|
112
|
+
});
|
|
113
|
+
export type TeleportRequest = z.infer<typeof TeleportRequestSchema>;
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### 2. Database Schema (`src/be/db.ts`)
|
|
117
|
+
|
|
118
|
+
Add table creation after `agent_log` table (around line 60):
|
|
119
|
+
|
|
120
|
+
```sql
|
|
121
|
+
CREATE TABLE IF NOT EXISTS teleport_requests (
|
|
122
|
+
id TEXT PRIMARY KEY,
|
|
123
|
+
sourceAgentId TEXT,
|
|
124
|
+
targetAgentId TEXT,
|
|
125
|
+
status TEXT NOT NULL CHECK(status IN ('pending', 'claimed', 'started', 'completed', 'failed')),
|
|
126
|
+
|
|
127
|
+
-- Context Package
|
|
128
|
+
summary TEXT NOT NULL,
|
|
129
|
+
currentGoal TEXT,
|
|
130
|
+
relevantFiles TEXT,
|
|
131
|
+
contextNotes TEXT,
|
|
132
|
+
workingDirectory TEXT,
|
|
133
|
+
projectPath TEXT,
|
|
134
|
+
|
|
135
|
+
-- Timestamps
|
|
136
|
+
createdAt TEXT NOT NULL,
|
|
137
|
+
claimedAt TEXT,
|
|
138
|
+
claimedBy TEXT,
|
|
139
|
+
startedAt TEXT,
|
|
140
|
+
finishedAt TEXT,
|
|
141
|
+
|
|
142
|
+
-- Result
|
|
143
|
+
resultTaskId TEXT,
|
|
144
|
+
output TEXT,
|
|
145
|
+
failureReason TEXT,
|
|
146
|
+
|
|
147
|
+
FOREIGN KEY (sourceAgentId) REFERENCES agents(id) ON DELETE SET NULL,
|
|
148
|
+
FOREIGN KEY (targetAgentId) REFERENCES agents(id) ON DELETE SET NULL,
|
|
149
|
+
FOREIGN KEY (claimedBy) REFERENCES agents(id) ON DELETE SET NULL,
|
|
150
|
+
FOREIGN KEY (resultTaskId) REFERENCES agent_tasks(id) ON DELETE SET NULL
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
CREATE INDEX IF NOT EXISTS idx_teleport_requests_status ON teleport_requests(status);
|
|
154
|
+
CREATE INDEX IF NOT EXISTS idx_teleport_requests_targetAgentId ON teleport_requests(targetAgentId);
|
|
155
|
+
CREATE INDEX IF NOT EXISTS idx_teleport_requests_claimedBy ON teleport_requests(claimedBy);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### 3. Database Functions (`src/be/db.ts`)
|
|
159
|
+
|
|
160
|
+
Add type and CRUD functions after existing task functions:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// --- Teleport Request Types ---
|
|
164
|
+
type TeleportRequestRow = {
|
|
165
|
+
id: string;
|
|
166
|
+
sourceAgentId: string | null;
|
|
167
|
+
targetAgentId: string | null;
|
|
168
|
+
status: TeleportRequestStatus;
|
|
169
|
+
summary: string;
|
|
170
|
+
currentGoal: string | null;
|
|
171
|
+
relevantFiles: string | null;
|
|
172
|
+
contextNotes: string | null;
|
|
173
|
+
workingDirectory: string | null;
|
|
174
|
+
projectPath: string | null;
|
|
175
|
+
createdAt: string;
|
|
176
|
+
claimedAt: string | null;
|
|
177
|
+
claimedBy: string | null;
|
|
178
|
+
startedAt: string | null;
|
|
179
|
+
finishedAt: string | null;
|
|
180
|
+
resultTaskId: string | null;
|
|
181
|
+
output: string | null;
|
|
182
|
+
failureReason: string | null;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
function rowToTeleportRequest(row: TeleportRequestRow): TeleportRequest {
|
|
186
|
+
return {
|
|
187
|
+
id: row.id,
|
|
188
|
+
sourceAgentId: row.sourceAgentId ?? undefined,
|
|
189
|
+
targetAgentId: row.targetAgentId ?? undefined,
|
|
190
|
+
status: row.status,
|
|
191
|
+
summary: row.summary,
|
|
192
|
+
currentGoal: row.currentGoal ?? undefined,
|
|
193
|
+
relevantFiles: row.relevantFiles ?? undefined,
|
|
194
|
+
contextNotes: row.contextNotes ?? undefined,
|
|
195
|
+
workingDirectory: row.workingDirectory ?? undefined,
|
|
196
|
+
projectPath: row.projectPath ?? undefined,
|
|
197
|
+
createdAt: row.createdAt,
|
|
198
|
+
claimedAt: row.claimedAt ?? undefined,
|
|
199
|
+
claimedBy: row.claimedBy ?? undefined,
|
|
200
|
+
startedAt: row.startedAt ?? undefined,
|
|
201
|
+
finishedAt: row.finishedAt ?? undefined,
|
|
202
|
+
resultTaskId: row.resultTaskId ?? undefined,
|
|
203
|
+
output: row.output ?? undefined,
|
|
204
|
+
failureReason: row.failureReason ?? undefined,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- Teleport Request CRUD ---
|
|
209
|
+
export function createTeleportRequest(data: {
|
|
210
|
+
summary: string;
|
|
211
|
+
currentGoal?: string;
|
|
212
|
+
relevantFiles?: string;
|
|
213
|
+
contextNotes?: string;
|
|
214
|
+
workingDirectory?: string;
|
|
215
|
+
projectPath?: string;
|
|
216
|
+
sourceAgentId?: string;
|
|
217
|
+
targetAgentId?: string;
|
|
218
|
+
}): TeleportRequest {
|
|
219
|
+
const id = crypto.randomUUID();
|
|
220
|
+
const row = getDb()
|
|
221
|
+
.prepare<TeleportRequestRow, [string, string | null, string | null, string, string, string | null, string | null, string | null, string | null, string | null]>(
|
|
222
|
+
`INSERT INTO teleport_requests
|
|
223
|
+
(id, sourceAgentId, targetAgentId, status, summary, currentGoal, relevantFiles, contextNotes, workingDirectory, projectPath, createdAt)
|
|
224
|
+
VALUES (?, ?, ?, 'pending', ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
225
|
+
RETURNING *`
|
|
226
|
+
)
|
|
227
|
+
.get(
|
|
228
|
+
id,
|
|
229
|
+
data.sourceAgentId ?? null,
|
|
230
|
+
data.targetAgentId ?? null,
|
|
231
|
+
data.summary,
|
|
232
|
+
data.currentGoal ?? null,
|
|
233
|
+
data.relevantFiles ?? null,
|
|
234
|
+
data.contextNotes ?? null,
|
|
235
|
+
data.workingDirectory ?? null,
|
|
236
|
+
data.projectPath ?? null
|
|
237
|
+
);
|
|
238
|
+
if (!row) throw new Error("Failed to create teleport request");
|
|
239
|
+
return rowToTeleportRequest(row);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function getTeleportRequestById(id: string): TeleportRequest | null {
|
|
243
|
+
const row = getDb()
|
|
244
|
+
.prepare<TeleportRequestRow, [string]>("SELECT * FROM teleport_requests WHERE id = ?")
|
|
245
|
+
.get(id);
|
|
246
|
+
return row ? rowToTeleportRequest(row) : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function getPendingTeleportForAgent(agentId: string): TeleportRequest | null {
|
|
250
|
+
// Find pending teleport targeted to this agent OR any unassigned pending teleport
|
|
251
|
+
const row = getDb()
|
|
252
|
+
.prepare<TeleportRequestRow, [string]>(
|
|
253
|
+
`SELECT * FROM teleport_requests
|
|
254
|
+
WHERE status = 'pending'
|
|
255
|
+
AND (targetAgentId = ? OR targetAgentId IS NULL)
|
|
256
|
+
ORDER BY createdAt ASC
|
|
257
|
+
LIMIT 1`
|
|
258
|
+
)
|
|
259
|
+
.get(agentId);
|
|
260
|
+
return row ? rowToTeleportRequest(row) : null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function claimTeleportRequest(teleportId: string, agentId: string): TeleportRequest | null {
|
|
264
|
+
const row = getDb()
|
|
265
|
+
.prepare<TeleportRequestRow, [string, string]>(
|
|
266
|
+
`UPDATE teleport_requests
|
|
267
|
+
SET status = 'claimed', claimedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), claimedBy = ?
|
|
268
|
+
WHERE id = ? AND status = 'pending'
|
|
269
|
+
RETURNING *`
|
|
270
|
+
)
|
|
271
|
+
.get(agentId, teleportId);
|
|
272
|
+
return row ? rowToTeleportRequest(row) : null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function startTeleportRequest(teleportId: string, taskId?: string): TeleportRequest | null {
|
|
276
|
+
const row = getDb()
|
|
277
|
+
.prepare<TeleportRequestRow, [string | null, string]>(
|
|
278
|
+
`UPDATE teleport_requests
|
|
279
|
+
SET status = 'started', startedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), resultTaskId = ?
|
|
280
|
+
WHERE id = ? AND status = 'claimed'
|
|
281
|
+
RETURNING *`
|
|
282
|
+
)
|
|
283
|
+
.get(taskId ?? null, teleportId);
|
|
284
|
+
return row ? rowToTeleportRequest(row) : null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function completeTeleportRequest(teleportId: string, output?: string): TeleportRequest | null {
|
|
288
|
+
const row = getDb()
|
|
289
|
+
.prepare<TeleportRequestRow, [string | null, string]>(
|
|
290
|
+
`UPDATE teleport_requests
|
|
291
|
+
SET status = 'completed', finishedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), output = ?
|
|
292
|
+
WHERE id = ? AND status IN ('claimed', 'started')
|
|
293
|
+
RETURNING *`
|
|
294
|
+
)
|
|
295
|
+
.get(output ?? null, teleportId);
|
|
296
|
+
return row ? rowToTeleportRequest(row) : null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function failTeleportRequest(teleportId: string, failureReason: string): TeleportRequest | null {
|
|
300
|
+
const row = getDb()
|
|
301
|
+
.prepare<TeleportRequestRow, [string, string]>(
|
|
302
|
+
`UPDATE teleport_requests
|
|
303
|
+
SET status = 'failed', finishedAt = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), failureReason = ?
|
|
304
|
+
WHERE id = ? AND status IN ('claimed', 'started')
|
|
305
|
+
RETURNING *`
|
|
306
|
+
)
|
|
307
|
+
.get(failureReason, teleportId);
|
|
308
|
+
return row ? rowToTeleportRequest(row) : null;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function getAllTeleportRequests(options?: { status?: TeleportRequestStatus }): TeleportRequest[] {
|
|
312
|
+
if (options?.status) {
|
|
313
|
+
return getDb()
|
|
314
|
+
.prepare<TeleportRequestRow, [string]>(
|
|
315
|
+
"SELECT * FROM teleport_requests WHERE status = ? ORDER BY createdAt DESC"
|
|
316
|
+
)
|
|
317
|
+
.all(options.status)
|
|
318
|
+
.map(rowToTeleportRequest);
|
|
319
|
+
}
|
|
320
|
+
return getDb()
|
|
321
|
+
.prepare<TeleportRequestRow, []>("SELECT * FROM teleport_requests ORDER BY createdAt DESC")
|
|
322
|
+
.all()
|
|
323
|
+
.map(rowToTeleportRequest);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Success Criteria:
|
|
328
|
+
|
|
329
|
+
#### Automated Verification:
|
|
330
|
+
- [ ] TypeScript compiles: `bun run tsc:check`
|
|
331
|
+
- [ ] Server starts without errors: `bun run dev:http`
|
|
332
|
+
- [ ] Database table created (check with sqlite3 CLI)
|
|
333
|
+
|
|
334
|
+
#### Manual Verification:
|
|
335
|
+
- [ ] Can manually insert/query teleport_requests table
|
|
336
|
+
|
|
337
|
+
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation that the database changes work correctly before proceeding to the next phase.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Phase 2: Teleport MCP Tools
|
|
342
|
+
|
|
343
|
+
### Overview
|
|
344
|
+
Add 5 new MCP tools for teleport lifecycle management.
|
|
345
|
+
|
|
346
|
+
### Changes Required:
|
|
347
|
+
|
|
348
|
+
#### 1. teleport-out Tool (`src/tools/teleport-out.ts`)
|
|
349
|
+
|
|
350
|
+
**File**: `src/tools/teleport-out.ts` (NEW)
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { z } from "zod/v4";
|
|
354
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
355
|
+
import { createToolRegistrar, type RequestInfo } from "./utils";
|
|
356
|
+
import {
|
|
357
|
+
createTeleportRequest,
|
|
358
|
+
getAgentById,
|
|
359
|
+
getAllAgents,
|
|
360
|
+
} from "../be/db";
|
|
361
|
+
import { RelevantFileSchema } from "../types";
|
|
362
|
+
|
|
363
|
+
const inputSchema = z.object({
|
|
364
|
+
summary: z.string().min(1).describe("AI-generated summary of current session and work accomplished"),
|
|
365
|
+
currentGoal: z.string().optional().describe("What you're trying to accomplish"),
|
|
366
|
+
relevantFiles: z.array(RelevantFileSchema).optional().describe("Files relevant to the task"),
|
|
367
|
+
contextNotes: z.string().optional().describe("Additional context for the receiving agent"),
|
|
368
|
+
targetAgentId: z.string().uuid().optional().describe("Specific worker agent ID, or omit for any available worker"),
|
|
369
|
+
workingDirectory: z.string().optional().describe("Current working directory"),
|
|
370
|
+
projectPath: z.string().optional().describe("Project root path"),
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const outputSchema = z.object({
|
|
374
|
+
success: z.boolean(),
|
|
375
|
+
teleportId: z.string().uuid().optional(),
|
|
376
|
+
message: z.string(),
|
|
377
|
+
targetAgent: z.object({
|
|
378
|
+
id: z.string(),
|
|
379
|
+
name: z.string(),
|
|
380
|
+
status: z.string(),
|
|
381
|
+
}).optional(),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
export function registerTeleportOut(server: McpServer): void {
|
|
385
|
+
createToolRegistrar(server)(
|
|
386
|
+
"teleport-out",
|
|
387
|
+
{
|
|
388
|
+
description: "Transfer current session context to a worker agent to continue the work",
|
|
389
|
+
inputSchema,
|
|
390
|
+
outputSchema,
|
|
391
|
+
},
|
|
392
|
+
async (args, requestInfo: RequestInfo) => {
|
|
393
|
+
// Validate target agent if specified
|
|
394
|
+
if (args.targetAgentId) {
|
|
395
|
+
const targetAgent = getAgentById(args.targetAgentId);
|
|
396
|
+
if (!targetAgent) {
|
|
397
|
+
return {
|
|
398
|
+
success: false,
|
|
399
|
+
message: `Target agent ${args.targetAgentId} not found`,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (targetAgent.isLead) {
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
message: "Cannot teleport to lead agent. Choose a worker agent.",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
if (targetAgent.status === "offline") {
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
message: `Target agent "${targetAgent.name}" is offline`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
// Check if any workers are available
|
|
416
|
+
const workers = getAllAgents().filter(a => !a.isLead && a.status !== "offline");
|
|
417
|
+
if (workers.length === 0) {
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
message: "No worker agents available to receive teleport",
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Serialize relevantFiles if provided
|
|
426
|
+
const relevantFilesJson = args.relevantFiles
|
|
427
|
+
? JSON.stringify(args.relevantFiles)
|
|
428
|
+
: undefined;
|
|
429
|
+
|
|
430
|
+
// Create teleport request
|
|
431
|
+
const teleport = createTeleportRequest({
|
|
432
|
+
summary: args.summary,
|
|
433
|
+
currentGoal: args.currentGoal,
|
|
434
|
+
relevantFiles: relevantFilesJson,
|
|
435
|
+
contextNotes: args.contextNotes,
|
|
436
|
+
workingDirectory: args.workingDirectory,
|
|
437
|
+
projectPath: args.projectPath,
|
|
438
|
+
sourceAgentId: requestInfo.agentId,
|
|
439
|
+
targetAgentId: args.targetAgentId,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
const targetAgent = args.targetAgentId
|
|
443
|
+
? getAgentById(args.targetAgentId)
|
|
444
|
+
: undefined;
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
success: true,
|
|
448
|
+
teleportId: teleport.id,
|
|
449
|
+
message: args.targetAgentId
|
|
450
|
+
? `Session teleported to ${targetAgent?.name}. Teleport ID: ${teleport.id}`
|
|
451
|
+
: `Session teleported to swarm. Any available worker will pick it up. Teleport ID: ${teleport.id}`,
|
|
452
|
+
targetAgent: targetAgent
|
|
453
|
+
? { id: targetAgent.id, name: targetAgent.name, status: targetAgent.status }
|
|
454
|
+
: undefined,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### 2. poll-teleport Tool (`src/tools/poll-teleport.ts`)
|
|
462
|
+
|
|
463
|
+
**File**: `src/tools/poll-teleport.ts` (NEW)
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { z } from "zod/v4";
|
|
467
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
468
|
+
import { createToolRegistrar, type RequestInfo } from "./utils";
|
|
469
|
+
import {
|
|
470
|
+
getPendingTeleportForAgent,
|
|
471
|
+
claimTeleportRequest,
|
|
472
|
+
getAgentById,
|
|
473
|
+
updateAgentStatus,
|
|
474
|
+
} from "../be/db";
|
|
475
|
+
import { TeleportRequestSchema } from "../types";
|
|
476
|
+
|
|
477
|
+
const inputSchema = z.object({});
|
|
478
|
+
|
|
479
|
+
const outputSchema = z.object({
|
|
480
|
+
success: z.boolean(),
|
|
481
|
+
message: z.string(),
|
|
482
|
+
teleport: TeleportRequestSchema.optional(),
|
|
483
|
+
waitedForSeconds: z.number(),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const POLL_TIMEOUT_MS = 30_000; // 30 seconds (shorter than poll-task)
|
|
487
|
+
const POLL_INTERVAL_MS = 2_000; // 2 seconds
|
|
488
|
+
|
|
489
|
+
export function registerPollTeleport(server: McpServer): void {
|
|
490
|
+
createToolRegistrar(server)(
|
|
491
|
+
"poll-teleport",
|
|
492
|
+
{
|
|
493
|
+
description: "Poll for pending teleport requests to continue another session's work",
|
|
494
|
+
inputSchema,
|
|
495
|
+
outputSchema,
|
|
496
|
+
},
|
|
497
|
+
async (args, requestInfo: RequestInfo) => {
|
|
498
|
+
const agentId = requestInfo.agentId;
|
|
499
|
+
if (!agentId) {
|
|
500
|
+
return {
|
|
501
|
+
success: false,
|
|
502
|
+
message: "Agent ID required. Set X-Agent-ID header.",
|
|
503
|
+
waitedForSeconds: 0,
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const agent = getAgentById(agentId);
|
|
508
|
+
if (!agent) {
|
|
509
|
+
return {
|
|
510
|
+
success: false,
|
|
511
|
+
message: `Agent ${agentId} not found`,
|
|
512
|
+
waitedForSeconds: 0,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (agent.isLead) {
|
|
517
|
+
return {
|
|
518
|
+
success: false,
|
|
519
|
+
message: "Lead agents cannot poll for teleports",
|
|
520
|
+
waitedForSeconds: 0,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const startTime = Date.now();
|
|
525
|
+
let elapsedMs = 0;
|
|
526
|
+
|
|
527
|
+
while (elapsedMs < POLL_TIMEOUT_MS) {
|
|
528
|
+
// Try to claim a pending teleport atomically
|
|
529
|
+
const pending = getPendingTeleportForAgent(agentId);
|
|
530
|
+
|
|
531
|
+
if (pending) {
|
|
532
|
+
const claimed = claimTeleportRequest(pending.id, agentId);
|
|
533
|
+
|
|
534
|
+
if (claimed) {
|
|
535
|
+
// Update agent status to busy
|
|
536
|
+
updateAgentStatus(agentId, "busy");
|
|
537
|
+
|
|
538
|
+
return {
|
|
539
|
+
success: true,
|
|
540
|
+
message: `Claimed teleport ${claimed.id}. Context follows.`,
|
|
541
|
+
teleport: claimed,
|
|
542
|
+
waitedForSeconds: Math.round(elapsedMs / 1000),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Wait before next poll
|
|
548
|
+
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
549
|
+
elapsedMs = Date.now() - startTime;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return {
|
|
553
|
+
success: false,
|
|
554
|
+
message: "No teleport requests available. Try poll-task for regular tasks.",
|
|
555
|
+
waitedForSeconds: Math.round(POLL_TIMEOUT_MS / 1000),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### 3. start-teleport Tool (`src/tools/start-teleport.ts`)
|
|
563
|
+
|
|
564
|
+
**File**: `src/tools/start-teleport.ts` (NEW)
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
import { z } from "zod/v4";
|
|
568
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
569
|
+
import { createToolRegistrar, type RequestInfo } from "./utils";
|
|
570
|
+
import {
|
|
571
|
+
startTeleportRequest,
|
|
572
|
+
getTeleportRequestById,
|
|
573
|
+
createTask,
|
|
574
|
+
} from "../be/db";
|
|
575
|
+
|
|
576
|
+
const inputSchema = z.object({
|
|
577
|
+
teleportId: z.string().uuid().describe("The teleport request ID to start working on"),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
const outputSchema = z.object({
|
|
581
|
+
success: z.boolean(),
|
|
582
|
+
message: z.string(),
|
|
583
|
+
taskId: z.string().uuid().optional(),
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
export function registerStartTeleport(server: McpServer): void {
|
|
587
|
+
createToolRegistrar(server)(
|
|
588
|
+
"start-teleport",
|
|
589
|
+
{
|
|
590
|
+
description: "Mark a teleport request as started and optionally create a tracking task",
|
|
591
|
+
inputSchema,
|
|
592
|
+
outputSchema,
|
|
593
|
+
},
|
|
594
|
+
async (args, requestInfo: RequestInfo) => {
|
|
595
|
+
const agentId = requestInfo.agentId;
|
|
596
|
+
if (!agentId) {
|
|
597
|
+
return {
|
|
598
|
+
success: false,
|
|
599
|
+
message: "Agent ID required",
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const teleport = getTeleportRequestById(args.teleportId);
|
|
604
|
+
if (!teleport) {
|
|
605
|
+
return {
|
|
606
|
+
success: false,
|
|
607
|
+
message: `Teleport ${args.teleportId} not found`,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (teleport.claimedBy !== agentId) {
|
|
612
|
+
return {
|
|
613
|
+
success: false,
|
|
614
|
+
message: "You did not claim this teleport",
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (teleport.status !== "claimed") {
|
|
619
|
+
return {
|
|
620
|
+
success: false,
|
|
621
|
+
message: `Teleport is ${teleport.status}, not claimed`,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Create a tracking task with teleport context
|
|
626
|
+
const taskDescription = teleport.currentGoal
|
|
627
|
+
? `[Teleport] ${teleport.currentGoal}`
|
|
628
|
+
: `[Teleport] Continue: ${teleport.summary.slice(0, 100)}...`;
|
|
629
|
+
|
|
630
|
+
const task = createTask(agentId, taskDescription, {
|
|
631
|
+
source: "mcp",
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Mark teleport as started
|
|
635
|
+
const started = startTeleportRequest(args.teleportId, task.id);
|
|
636
|
+
|
|
637
|
+
if (!started) {
|
|
638
|
+
return {
|
|
639
|
+
success: false,
|
|
640
|
+
message: "Failed to start teleport",
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return {
|
|
645
|
+
success: true,
|
|
646
|
+
message: `Teleport started. Task ${task.id} created for tracking.`,
|
|
647
|
+
taskId: task.id,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
#### 4. complete-teleport Tool (`src/tools/complete-teleport.ts`)
|
|
655
|
+
|
|
656
|
+
**File**: `src/tools/complete-teleport.ts` (NEW)
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
import { z } from "zod/v4";
|
|
660
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
661
|
+
import { createToolRegistrar, type RequestInfo } from "./utils";
|
|
662
|
+
import {
|
|
663
|
+
completeTeleportRequest,
|
|
664
|
+
failTeleportRequest,
|
|
665
|
+
getTeleportRequestById,
|
|
666
|
+
updateAgentStatus,
|
|
667
|
+
completeTask,
|
|
668
|
+
failTask,
|
|
669
|
+
} from "../be/db";
|
|
670
|
+
|
|
671
|
+
const inputSchema = z.object({
|
|
672
|
+
teleportId: z.string().uuid().describe("The teleport request ID"),
|
|
673
|
+
status: z.enum(["completed", "failed"]).describe("Final status"),
|
|
674
|
+
output: z.string().optional().describe("Result/output of the work (for completed)"),
|
|
675
|
+
failureReason: z.string().optional().describe("Reason for failure (for failed)"),
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
const outputSchema = z.object({
|
|
679
|
+
success: z.boolean(),
|
|
680
|
+
message: z.string(),
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
export function registerCompleteTeleport(server: McpServer): void {
|
|
684
|
+
createToolRegistrar(server)(
|
|
685
|
+
"complete-teleport",
|
|
686
|
+
{
|
|
687
|
+
description: "Mark a teleport request as completed or failed",
|
|
688
|
+
inputSchema,
|
|
689
|
+
outputSchema,
|
|
690
|
+
},
|
|
691
|
+
async (args, requestInfo: RequestInfo) => {
|
|
692
|
+
const agentId = requestInfo.agentId;
|
|
693
|
+
if (!agentId) {
|
|
694
|
+
return {
|
|
695
|
+
success: false,
|
|
696
|
+
message: "Agent ID required",
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const teleport = getTeleportRequestById(args.teleportId);
|
|
701
|
+
if (!teleport) {
|
|
702
|
+
return {
|
|
703
|
+
success: false,
|
|
704
|
+
message: `Teleport ${args.teleportId} not found`,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (teleport.claimedBy !== agentId) {
|
|
709
|
+
return {
|
|
710
|
+
success: false,
|
|
711
|
+
message: "You did not claim this teleport",
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
let result;
|
|
716
|
+
if (args.status === "completed") {
|
|
717
|
+
result = completeTeleportRequest(args.teleportId, args.output);
|
|
718
|
+
// Also complete the tracking task if it exists
|
|
719
|
+
if (teleport.resultTaskId) {
|
|
720
|
+
completeTask(teleport.resultTaskId, args.output);
|
|
721
|
+
}
|
|
722
|
+
} else {
|
|
723
|
+
result = failTeleportRequest(args.teleportId, args.failureReason || "Unknown error");
|
|
724
|
+
if (teleport.resultTaskId) {
|
|
725
|
+
failTask(teleport.resultTaskId, args.failureReason || "Unknown error");
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (!result) {
|
|
730
|
+
return {
|
|
731
|
+
success: false,
|
|
732
|
+
message: "Failed to update teleport status",
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Set agent back to idle
|
|
737
|
+
updateAgentStatus(agentId, "idle");
|
|
738
|
+
|
|
739
|
+
return {
|
|
740
|
+
success: true,
|
|
741
|
+
message: `Teleport ${args.status}`,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
#### 5. get-teleport-details Tool (`src/tools/get-teleport-details.ts`)
|
|
749
|
+
|
|
750
|
+
**File**: `src/tools/get-teleport-details.ts` (NEW)
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
import { z } from "zod/v4";
|
|
754
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
755
|
+
import { createToolRegistrar, type RequestInfo } from "./utils";
|
|
756
|
+
import { getTeleportRequestById, getAgentById } from "../be/db";
|
|
757
|
+
import { TeleportRequestSchema } from "../types";
|
|
758
|
+
|
|
759
|
+
const inputSchema = z.object({
|
|
760
|
+
teleportId: z.string().uuid().describe("The teleport request ID to get details for"),
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
const outputSchema = z.object({
|
|
764
|
+
success: z.boolean(),
|
|
765
|
+
message: z.string(),
|
|
766
|
+
teleport: TeleportRequestSchema.optional(),
|
|
767
|
+
sourceAgent: z.object({
|
|
768
|
+
id: z.string(),
|
|
769
|
+
name: z.string(),
|
|
770
|
+
}).optional(),
|
|
771
|
+
claimedByAgent: z.object({
|
|
772
|
+
id: z.string(),
|
|
773
|
+
name: z.string(),
|
|
774
|
+
}).optional(),
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
export function registerGetTeleportDetails(server: McpServer): void {
|
|
778
|
+
createToolRegistrar(server)(
|
|
779
|
+
"get-teleport-details",
|
|
780
|
+
{
|
|
781
|
+
description: "Get details of a teleport request",
|
|
782
|
+
inputSchema,
|
|
783
|
+
outputSchema,
|
|
784
|
+
},
|
|
785
|
+
async (args, requestInfo: RequestInfo) => {
|
|
786
|
+
const teleport = getTeleportRequestById(args.teleportId);
|
|
787
|
+
|
|
788
|
+
if (!teleport) {
|
|
789
|
+
return {
|
|
790
|
+
success: false,
|
|
791
|
+
message: `Teleport ${args.teleportId} not found`,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const sourceAgent = teleport.sourceAgentId
|
|
796
|
+
? getAgentById(teleport.sourceAgentId)
|
|
797
|
+
: undefined;
|
|
798
|
+
|
|
799
|
+
const claimedByAgent = teleport.claimedBy
|
|
800
|
+
? getAgentById(teleport.claimedBy)
|
|
801
|
+
: undefined;
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
success: true,
|
|
805
|
+
message: `Teleport ${teleport.id} is ${teleport.status}`,
|
|
806
|
+
teleport,
|
|
807
|
+
sourceAgent: sourceAgent
|
|
808
|
+
? { id: sourceAgent.id, name: sourceAgent.name }
|
|
809
|
+
: undefined,
|
|
810
|
+
claimedByAgent: claimedByAgent
|
|
811
|
+
? { id: claimedByAgent.id, name: claimedByAgent.name }
|
|
812
|
+
: undefined,
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
### Success Criteria:
|
|
820
|
+
|
|
821
|
+
#### Automated Verification:
|
|
822
|
+
- [ ] TypeScript compiles: `bun run tsc:check`
|
|
823
|
+
- [ ] Linting passes: `bun run lint`
|
|
824
|
+
- [ ] Server starts: `bun run dev:http`
|
|
825
|
+
|
|
826
|
+
#### Manual Verification:
|
|
827
|
+
- [ ] Can call `teleport-out` and receive teleport ID
|
|
828
|
+
- [ ] Can call `poll-teleport` and receive pending teleport
|
|
829
|
+
- [ ] Teleport lifecycle flows correctly
|
|
830
|
+
|
|
831
|
+
**Implementation Note**: After completing this phase and all automated verification passes, pause here for manual confirmation before proceeding to the next phase.
|
|
832
|
+
|
|
833
|
+
---
|
|
834
|
+
|
|
835
|
+
## Phase 3: Register Tools in Server
|
|
836
|
+
|
|
837
|
+
### Overview
|
|
838
|
+
Wire up the new teleport tools in the MCP server.
|
|
839
|
+
|
|
840
|
+
### Changes Required:
|
|
841
|
+
|
|
842
|
+
#### 1. Update Server (`src/server.ts`)
|
|
843
|
+
|
|
844
|
+
Add imports and registrations:
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
// Add imports after line 10
|
|
848
|
+
import { registerTeleportOut } from "./tools/teleport-out";
|
|
849
|
+
import { registerPollTeleport } from "./tools/poll-teleport";
|
|
850
|
+
import { registerStartTeleport } from "./tools/start-teleport";
|
|
851
|
+
import { registerCompleteTeleport } from "./tools/complete-teleport";
|
|
852
|
+
import { registerGetTeleportDetails } from "./tools/get-teleport-details";
|
|
853
|
+
|
|
854
|
+
// Add registrations after line 33 (after existing tool registrations)
|
|
855
|
+
registerTeleportOut(server);
|
|
856
|
+
registerPollTeleport(server);
|
|
857
|
+
registerStartTeleport(server);
|
|
858
|
+
registerCompleteTeleport(server);
|
|
859
|
+
registerGetTeleportDetails(server);
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
### Success Criteria:
|
|
863
|
+
|
|
864
|
+
#### Automated Verification:
|
|
865
|
+
- [ ] TypeScript compiles: `bun run tsc:check`
|
|
866
|
+
- [ ] Server starts and lists teleport tools
|
|
867
|
+
|
|
868
|
+
#### Manual Verification:
|
|
869
|
+
- [ ] Tools appear in MCP tool list
|
|
870
|
+
|
|
871
|
+
---
|
|
872
|
+
|
|
873
|
+
## Phase 4: Worker Integration
|
|
874
|
+
|
|
875
|
+
### Overview
|
|
876
|
+
Update worker hooks to check for teleports before regular tasks.
|
|
877
|
+
|
|
878
|
+
### Changes Required:
|
|
879
|
+
|
|
880
|
+
#### 1. Update Hook Messages (`src/hooks/hook.ts`)
|
|
881
|
+
|
|
882
|
+
Modify the SessionStart case for workers (around line 60):
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
} else {
|
|
886
|
+
console.log(
|
|
887
|
+
`${agentInfo.name} is registered as a WORKER agent (status: ${agentInfo.status}) as of ${new Date().toISOString()}.`
|
|
888
|
+
);
|
|
889
|
+
console.log(
|
|
890
|
+
`As a worker agent, FIRST check for teleport requests using the poll-teleport tool.
|
|
891
|
+
Teleport requests contain rich context from sessions that need continuation - summary, goal, relevant files, and notes.
|
|
892
|
+
If no teleport requests are pending, fall back to poll-task for regular task assignments.
|
|
893
|
+
|
|
894
|
+
Priority:
|
|
895
|
+
1. poll-teleport - Check for session continuations with rich context
|
|
896
|
+
2. poll-task - Check for regular task assignments
|
|
897
|
+
|
|
898
|
+
When you receive a teleport:
|
|
899
|
+
- Read the summary and context carefully
|
|
900
|
+
- Call start-teleport to begin tracking
|
|
901
|
+
- Continue the work as described
|
|
902
|
+
- Call complete-teleport when done`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### Success Criteria:
|
|
908
|
+
|
|
909
|
+
#### Automated Verification:
|
|
910
|
+
- [ ] TypeScript compiles: `bun run tsc:check`
|
|
911
|
+
- [ ] Hook message includes teleport guidance
|
|
912
|
+
|
|
913
|
+
#### Manual Verification:
|
|
914
|
+
- [ ] Worker receives teleport-first guidance on session start
|
|
915
|
+
|
|
916
|
+
---
|
|
917
|
+
|
|
918
|
+
## Phase 5: HTTP API Endpoints
|
|
919
|
+
|
|
920
|
+
### Overview
|
|
921
|
+
Add REST endpoints for dashboard visibility.
|
|
922
|
+
|
|
923
|
+
### Changes Required:
|
|
924
|
+
|
|
925
|
+
#### 1. Update HTTP Server (`src/http.ts`)
|
|
926
|
+
|
|
927
|
+
Add teleport endpoints after existing API routes:
|
|
928
|
+
|
|
929
|
+
```typescript
|
|
930
|
+
// GET /api/teleports - List teleport requests
|
|
931
|
+
if (req.method === "GET" && url.pathname === "/api/teleports") {
|
|
932
|
+
const statusFilter = url.searchParams.get("status") as TeleportRequestStatus | null;
|
|
933
|
+
const teleports = getAllTeleportRequests(
|
|
934
|
+
statusFilter ? { status: statusFilter } : undefined
|
|
935
|
+
);
|
|
936
|
+
return Response.json({ teleports });
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// GET /api/teleports/:id - Get teleport details
|
|
940
|
+
if (req.method === "GET" && url.pathname.match(/^\/api\/teleports\/[a-f0-9-]+$/)) {
|
|
941
|
+
const teleportId = url.pathname.split("/").pop()!;
|
|
942
|
+
const teleport = getTeleportRequestById(teleportId);
|
|
943
|
+
if (!teleport) {
|
|
944
|
+
return Response.json({ error: "Teleport not found" }, { status: 404 });
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const sourceAgent = teleport.sourceAgentId
|
|
948
|
+
? getAgentById(teleport.sourceAgentId)
|
|
949
|
+
: undefined;
|
|
950
|
+
const claimedByAgent = teleport.claimedBy
|
|
951
|
+
? getAgentById(teleport.claimedBy)
|
|
952
|
+
: undefined;
|
|
953
|
+
|
|
954
|
+
return Response.json({
|
|
955
|
+
teleport,
|
|
956
|
+
sourceAgent,
|
|
957
|
+
claimedByAgent,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
Update `/api/stats` to include teleport counts:
|
|
963
|
+
|
|
964
|
+
```typescript
|
|
965
|
+
// Add to stats response
|
|
966
|
+
const teleportCounts = {
|
|
967
|
+
pending: getAllTeleportRequests({ status: "pending" }).length,
|
|
968
|
+
claimed: getAllTeleportRequests({ status: "claimed" }).length,
|
|
969
|
+
started: getAllTeleportRequests({ status: "started" }).length,
|
|
970
|
+
completed: getAllTeleportRequests({ status: "completed" }).length,
|
|
971
|
+
failed: getAllTeleportRequests({ status: "failed" }).length,
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
// Include in response
|
|
975
|
+
return Response.json({
|
|
976
|
+
agents: { ... },
|
|
977
|
+
tasks: { ... },
|
|
978
|
+
teleports: teleportCounts, // Add this
|
|
979
|
+
});
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Success Criteria:
|
|
983
|
+
|
|
984
|
+
#### Automated Verification:
|
|
985
|
+
- [ ] TypeScript compiles: `bun run tsc:check`
|
|
986
|
+
- [ ] Server starts without errors
|
|
987
|
+
|
|
988
|
+
#### Manual Verification:
|
|
989
|
+
- [ ] `/api/teleports` returns list
|
|
990
|
+
- [ ] `/api/teleports/:id` returns details
|
|
991
|
+
- [ ] `/api/stats` includes teleport counts
|
|
992
|
+
|
|
993
|
+
---
|
|
994
|
+
|
|
995
|
+
## User Experience Flow
|
|
996
|
+
|
|
997
|
+
### Sending (Local Session)
|
|
998
|
+
|
|
999
|
+
```
|
|
1000
|
+
User: "Send this to a worker to continue"
|
|
1001
|
+
|
|
1002
|
+
Claude: I'll package the current context and teleport it to an available worker.
|
|
1003
|
+
|
|
1004
|
+
[Calls teleport-out with:
|
|
1005
|
+
summary: "Implementing user authentication. Created login form, connected to API.
|
|
1006
|
+
Currently debugging token refresh - getting 401 intermittently."
|
|
1007
|
+
currentGoal: "Fix token refresh to prevent session expiration"
|
|
1008
|
+
relevantFiles: [
|
|
1009
|
+
{ path: "src/components/LoginForm.tsx", summary: "Login UI" },
|
|
1010
|
+
{ path: "src/api/auth.ts", summary: "Auth client with refresh logic" }
|
|
1011
|
+
]
|
|
1012
|
+
contextNotes: "The refresh endpoint seems to fail after ~15 minutes of inactivity"
|
|
1013
|
+
]
|
|
1014
|
+
|
|
1015
|
+
Claude: Session teleported! Teleport ID: abc-12345
|
|
1016
|
+
A worker will pick this up and continue the debugging.
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
### Receiving (Worker)
|
|
1020
|
+
|
|
1021
|
+
```
|
|
1022
|
+
[Worker calls poll-teleport, receives:]
|
|
1023
|
+
|
|
1024
|
+
{
|
|
1025
|
+
"success": true,
|
|
1026
|
+
"teleport": {
|
|
1027
|
+
"id": "abc-12345",
|
|
1028
|
+
"summary": "Implementing user authentication...",
|
|
1029
|
+
"currentGoal": "Fix token refresh to prevent session expiration",
|
|
1030
|
+
"relevantFiles": "[{...}]",
|
|
1031
|
+
"contextNotes": "The refresh endpoint seems to fail after ~15 minutes..."
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
Claude: Received teleport abc-12345!
|
|
1036
|
+
|
|
1037
|
+
I'm continuing work from another session. Here's the context:
|
|
1038
|
+
- **Summary**: Implementing user authentication. Login form created, API connected.
|
|
1039
|
+
- **Current Goal**: Fix token refresh to prevent session expiration
|
|
1040
|
+
- **Key Files**: src/components/LoginForm.tsx, src/api/auth.ts
|
|
1041
|
+
- **Notes**: Refresh fails after ~15 minutes of inactivity
|
|
1042
|
+
|
|
1043
|
+
Let me start by reading the auth.ts file to understand the refresh logic...
|
|
1044
|
+
|
|
1045
|
+
[Calls start-teleport to begin tracking]
|
|
1046
|
+
[Does the work]
|
|
1047
|
+
[Calls complete-teleport when done]
|
|
1048
|
+
```
|
|
1049
|
+
|
|
1050
|
+
---
|
|
1051
|
+
|
|
1052
|
+
## Testing Strategy
|
|
1053
|
+
|
|
1054
|
+
### Unit Tests
|
|
1055
|
+
|
|
1056
|
+
Create `src/tools/teleport-out.test.ts`:
|
|
1057
|
+
|
|
1058
|
+
```typescript
|
|
1059
|
+
import { test, expect, describe, beforeEach, mock } from "bun:test";
|
|
1060
|
+
|
|
1061
|
+
// Mock database functions
|
|
1062
|
+
mock.module("../be/db", () => ({
|
|
1063
|
+
createTeleportRequest: mock(() => ({
|
|
1064
|
+
id: "test-uuid",
|
|
1065
|
+
status: "pending",
|
|
1066
|
+
summary: "Test summary",
|
|
1067
|
+
createdAt: new Date().toISOString(),
|
|
1068
|
+
})),
|
|
1069
|
+
getAgentById: mock((id: string) => {
|
|
1070
|
+
if (id === "worker-1") return { id: "worker-1", name: "Worker", isLead: false, status: "idle" };
|
|
1071
|
+
if (id === "lead-1") return { id: "lead-1", name: "Lead", isLead: true, status: "idle" };
|
|
1072
|
+
return null;
|
|
1073
|
+
}),
|
|
1074
|
+
getAllAgents: mock(() => [
|
|
1075
|
+
{ id: "worker-1", name: "Worker", isLead: false, status: "idle" },
|
|
1076
|
+
]),
|
|
1077
|
+
}));
|
|
1078
|
+
|
|
1079
|
+
describe("teleport-out", () => {
|
|
1080
|
+
test("creates teleport request with summary", async () => {
|
|
1081
|
+
// Test implementation
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
test("rejects teleport to lead agent", async () => {
|
|
1085
|
+
// Test implementation
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
test("rejects when no workers available", async () => {
|
|
1089
|
+
// Test implementation
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
### Integration Tests
|
|
1095
|
+
|
|
1096
|
+
Manual testing checklist:
|
|
1097
|
+
|
|
1098
|
+
1. **Teleport Creation**
|
|
1099
|
+
- Call `teleport-out` from local session
|
|
1100
|
+
- Verify teleport appears in `/api/teleports`
|
|
1101
|
+
- Verify status is "pending"
|
|
1102
|
+
|
|
1103
|
+
2. **Teleport Claiming**
|
|
1104
|
+
- Start a worker
|
|
1105
|
+
- Worker calls `poll-teleport`
|
|
1106
|
+
- Verify teleport status changes to "claimed"
|
|
1107
|
+
|
|
1108
|
+
3. **Teleport Completion**
|
|
1109
|
+
- Worker calls `start-teleport`
|
|
1110
|
+
- Worker calls `complete-teleport`
|
|
1111
|
+
- Verify status is "completed"
|
|
1112
|
+
|
|
1113
|
+
4. **Error Cases**
|
|
1114
|
+
- Teleport to offline agent - verify rejection
|
|
1115
|
+
- Teleport to lead agent - verify rejection
|
|
1116
|
+
- Double-claim - verify only one succeeds
|
|
1117
|
+
|
|
1118
|
+
---
|
|
1119
|
+
|
|
1120
|
+
## Files Summary
|
|
1121
|
+
|
|
1122
|
+
| File | Action | Purpose |
|
|
1123
|
+
|------|--------|---------|
|
|
1124
|
+
| `src/types.ts` | Edit | Add TeleportRequest types |
|
|
1125
|
+
| `src/be/db.ts` | Edit | Add teleport_requests table + CRUD |
|
|
1126
|
+
| `src/tools/teleport-out.ts` | Create | Send context to swarm |
|
|
1127
|
+
| `src/tools/poll-teleport.ts` | Create | Worker claims teleport |
|
|
1128
|
+
| `src/tools/start-teleport.ts` | Create | Worker starts work |
|
|
1129
|
+
| `src/tools/complete-teleport.ts` | Create | Worker completes |
|
|
1130
|
+
| `src/tools/get-teleport-details.ts` | Create | View status |
|
|
1131
|
+
| `src/server.ts` | Edit | Register new tools |
|
|
1132
|
+
| `src/hooks/hook.ts` | Edit | Worker teleport awareness |
|
|
1133
|
+
| `src/http.ts` | Edit | REST API endpoints |
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
## References
|
|
1138
|
+
|
|
1139
|
+
- Claude Code Web teleport: `claude --teleport <session_id>`
|
|
1140
|
+
- Session storage: `~/.claude/projects/`
|
|
1141
|
+
- Existing task flow: `src/tools/send-task.ts`, `src/tools/poll-task.ts`
|
|
1142
|
+
- Hook system: `src/hooks/hook.ts`
|