@spencerbeggs/claude-coordinator-server 0.1.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/36.js +263 -0
- package/LICENSE +21 -0
- package/README.md +107 -0
- package/bin/claude-coordinator-server.js +15 -0
- package/index.d.ts +129 -0
- package/index.js +1 -0
- package/package.json +52 -0
package/36.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { AnswerInputSchema, AskInputSchema, DEFAULT_HOST, DEFAULT_PORT, GetContextInputSchema, JoinInputSchema, ListContextInputSchema, LogDecisionInputSchema, ShareContextInputSchema } from "@spencerbeggs/claude-coordinator-core";
|
|
2
|
+
import { initTRPC } from "@trpc/server";
|
|
3
|
+
import { observable } from "@trpc/server/observable";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { EventEmitter } from "node:events";
|
|
6
|
+
import { applyWSSHandler } from "@trpc/server/adapters/ws";
|
|
7
|
+
import { WebSocketServer } from "ws";
|
|
8
|
+
class CoordinatorState extends EventEmitter {
|
|
9
|
+
sessionId;
|
|
10
|
+
agents = new Map();
|
|
11
|
+
context = new Map();
|
|
12
|
+
questions = new Map();
|
|
13
|
+
decisions = [];
|
|
14
|
+
constructor(sessionId){
|
|
15
|
+
super();
|
|
16
|
+
this.sessionId = sessionId;
|
|
17
|
+
}
|
|
18
|
+
getSessionId() {
|
|
19
|
+
return this.sessionId;
|
|
20
|
+
}
|
|
21
|
+
addAgent(agent) {
|
|
22
|
+
this.agents.set(agent.id, agent);
|
|
23
|
+
this.emit("agentChange", this.listAgents());
|
|
24
|
+
}
|
|
25
|
+
removeAgent(agentId) {
|
|
26
|
+
const removed = this.agents.delete(agentId);
|
|
27
|
+
if (removed) this.emit("agentChange", this.listAgents());
|
|
28
|
+
return removed;
|
|
29
|
+
}
|
|
30
|
+
getAgent(agentId) {
|
|
31
|
+
return this.agents.get(agentId);
|
|
32
|
+
}
|
|
33
|
+
listAgents() {
|
|
34
|
+
return Array.from(this.agents.values());
|
|
35
|
+
}
|
|
36
|
+
setContext(entry) {
|
|
37
|
+
this.context.set(entry.key, entry);
|
|
38
|
+
this.emit("contextChange", entry);
|
|
39
|
+
}
|
|
40
|
+
getContext(key) {
|
|
41
|
+
return this.context.get(key);
|
|
42
|
+
}
|
|
43
|
+
listContext(filters) {
|
|
44
|
+
let entries = Array.from(this.context.values());
|
|
45
|
+
const filterTags = filters?.tags;
|
|
46
|
+
if (filterTags && filterTags.length > 0) entries = entries.filter((entry)=>filterTags.every((tag)=>entry.tags.includes(tag)));
|
|
47
|
+
if (filters?.createdBy) entries = entries.filter((entry)=>entry.createdBy === filters.createdBy);
|
|
48
|
+
return entries;
|
|
49
|
+
}
|
|
50
|
+
addQuestion(question) {
|
|
51
|
+
this.questions.set(question.id, question);
|
|
52
|
+
this.emit("question", question);
|
|
53
|
+
}
|
|
54
|
+
answerQuestion(questionId, answer, answeredBy) {
|
|
55
|
+
const question = this.questions.get(questionId);
|
|
56
|
+
if (!question) return;
|
|
57
|
+
const answered = {
|
|
58
|
+
...question,
|
|
59
|
+
answer,
|
|
60
|
+
answeredBy,
|
|
61
|
+
status: "answered",
|
|
62
|
+
answeredAt: new Date()
|
|
63
|
+
};
|
|
64
|
+
this.questions.set(questionId, answered);
|
|
65
|
+
this.emit("answer", answered);
|
|
66
|
+
return answered;
|
|
67
|
+
}
|
|
68
|
+
getQuestion(questionId) {
|
|
69
|
+
return this.questions.get(questionId);
|
|
70
|
+
}
|
|
71
|
+
listPendingQuestions(forAgentId) {
|
|
72
|
+
return Array.from(this.questions.values()).filter((q)=>{
|
|
73
|
+
if ("pending" !== q.status) return false;
|
|
74
|
+
if (forAgentId && q.to && q.to !== forAgentId) return false;
|
|
75
|
+
return true;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
addDecision(decision) {
|
|
79
|
+
this.decisions.push(decision);
|
|
80
|
+
}
|
|
81
|
+
listDecisions() {
|
|
82
|
+
return [
|
|
83
|
+
...this.decisions
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let globalState = null;
|
|
88
|
+
function getOrCreateState(sessionId) {
|
|
89
|
+
if (!globalState) globalState = new CoordinatorState(sessionId ?? crypto.randomUUID());
|
|
90
|
+
return globalState;
|
|
91
|
+
}
|
|
92
|
+
function resetState() {
|
|
93
|
+
globalState = null;
|
|
94
|
+
}
|
|
95
|
+
const t = initTRPC.context().create();
|
|
96
|
+
const publicProcedure = t.procedure;
|
|
97
|
+
const sessionRouter = t.router({
|
|
98
|
+
join: publicProcedure.input(JoinInputSchema).mutation(({ input, ctx })=>{
|
|
99
|
+
const agent = {
|
|
100
|
+
id: crypto.randomUUID(),
|
|
101
|
+
name: input.name,
|
|
102
|
+
role: input.role,
|
|
103
|
+
repoPath: input.repoPath,
|
|
104
|
+
connectedAt: new Date()
|
|
105
|
+
};
|
|
106
|
+
ctx.state.addAgent(agent);
|
|
107
|
+
return {
|
|
108
|
+
agent,
|
|
109
|
+
sessionId: ctx.state.getSessionId()
|
|
110
|
+
};
|
|
111
|
+
}),
|
|
112
|
+
leave: publicProcedure.input(z.object({
|
|
113
|
+
agentId: z.string().uuid()
|
|
114
|
+
})).mutation(({ input, ctx })=>{
|
|
115
|
+
const removed = ctx.state.removeAgent(input.agentId);
|
|
116
|
+
return {
|
|
117
|
+
success: removed
|
|
118
|
+
};
|
|
119
|
+
}),
|
|
120
|
+
list: publicProcedure.query(({ ctx })=>ctx.state.listAgents()),
|
|
121
|
+
onAgentChange: publicProcedure.subscription(({ ctx })=>observable((emit)=>{
|
|
122
|
+
const handler = (agents)=>{
|
|
123
|
+
emit.next(agents);
|
|
124
|
+
};
|
|
125
|
+
ctx.state.on("agentChange", handler);
|
|
126
|
+
emit.next(ctx.state.listAgents());
|
|
127
|
+
return ()=>{
|
|
128
|
+
ctx.state.off("agentChange", handler);
|
|
129
|
+
};
|
|
130
|
+
}))
|
|
131
|
+
});
|
|
132
|
+
const contextRouter = t.router({
|
|
133
|
+
share: publicProcedure.input(ShareContextInputSchema.extend({
|
|
134
|
+
agentId: z.string().uuid()
|
|
135
|
+
})).mutation(({ input, ctx })=>{
|
|
136
|
+
const now = new Date();
|
|
137
|
+
const existing = ctx.state.getContext(input.key);
|
|
138
|
+
const entry = {
|
|
139
|
+
id: existing?.id ?? crypto.randomUUID(),
|
|
140
|
+
key: input.key,
|
|
141
|
+
value: input.value,
|
|
142
|
+
tags: input.tags ?? [],
|
|
143
|
+
createdBy: existing?.createdBy ?? input.agentId,
|
|
144
|
+
createdAt: existing?.createdAt ?? now,
|
|
145
|
+
updatedAt: now
|
|
146
|
+
};
|
|
147
|
+
ctx.state.setContext(entry);
|
|
148
|
+
return entry;
|
|
149
|
+
}),
|
|
150
|
+
get: publicProcedure.input(GetContextInputSchema).query(({ input, ctx })=>ctx.state.getContext(input.key) ?? null),
|
|
151
|
+
list: publicProcedure.input(ListContextInputSchema.optional()).query(({ input, ctx })=>ctx.state.listContext(input)),
|
|
152
|
+
onContextChange: publicProcedure.subscription(({ ctx })=>observable((emit)=>{
|
|
153
|
+
const handler = (entry)=>{
|
|
154
|
+
emit.next(entry);
|
|
155
|
+
};
|
|
156
|
+
ctx.state.on("contextChange", handler);
|
|
157
|
+
return ()=>{
|
|
158
|
+
ctx.state.off("contextChange", handler);
|
|
159
|
+
};
|
|
160
|
+
}))
|
|
161
|
+
});
|
|
162
|
+
const questionsRouter = t.router({
|
|
163
|
+
ask: publicProcedure.input(AskInputSchema.extend({
|
|
164
|
+
agentId: z.string().uuid()
|
|
165
|
+
})).mutation(({ input, ctx })=>{
|
|
166
|
+
const question = {
|
|
167
|
+
id: crypto.randomUUID(),
|
|
168
|
+
question: input.question,
|
|
169
|
+
from: input.agentId,
|
|
170
|
+
to: input.to,
|
|
171
|
+
status: "pending",
|
|
172
|
+
createdAt: new Date()
|
|
173
|
+
};
|
|
174
|
+
ctx.state.addQuestion(question);
|
|
175
|
+
return question;
|
|
176
|
+
}),
|
|
177
|
+
answer: publicProcedure.input(AnswerInputSchema.extend({
|
|
178
|
+
agentId: z.string().uuid()
|
|
179
|
+
})).mutation(({ input, ctx })=>{
|
|
180
|
+
const answered = ctx.state.answerQuestion(input.questionId, input.answer, input.agentId);
|
|
181
|
+
if (!answered) throw new Error(`Question not found: ${input.questionId}`);
|
|
182
|
+
return answered;
|
|
183
|
+
}),
|
|
184
|
+
listPending: publicProcedure.input(z.object({
|
|
185
|
+
agentId: z.string().uuid().optional()
|
|
186
|
+
}).optional()).query(({ input, ctx })=>ctx.state.listPendingQuestions(input?.agentId)),
|
|
187
|
+
onQuestion: publicProcedure.subscription(({ ctx })=>observable((emit)=>{
|
|
188
|
+
const questionHandler = (question)=>{
|
|
189
|
+
emit.next(question);
|
|
190
|
+
};
|
|
191
|
+
const answerHandler = (question)=>{
|
|
192
|
+
emit.next(question);
|
|
193
|
+
};
|
|
194
|
+
ctx.state.on("question", questionHandler);
|
|
195
|
+
ctx.state.on("answer", answerHandler);
|
|
196
|
+
return ()=>{
|
|
197
|
+
ctx.state.off("question", questionHandler);
|
|
198
|
+
ctx.state.off("answer", answerHandler);
|
|
199
|
+
};
|
|
200
|
+
}))
|
|
201
|
+
});
|
|
202
|
+
const decisionsRouter = t.router({
|
|
203
|
+
log: publicProcedure.input(LogDecisionInputSchema.extend({
|
|
204
|
+
agentId: z.string().uuid()
|
|
205
|
+
})).mutation(({ input, ctx })=>{
|
|
206
|
+
const decision = {
|
|
207
|
+
id: crypto.randomUUID(),
|
|
208
|
+
decision: input.decision,
|
|
209
|
+
rationale: input.rationale,
|
|
210
|
+
by: input.agentId,
|
|
211
|
+
createdAt: new Date()
|
|
212
|
+
};
|
|
213
|
+
ctx.state.addDecision(decision);
|
|
214
|
+
return decision;
|
|
215
|
+
}),
|
|
216
|
+
list: publicProcedure.query(({ ctx })=>ctx.state.listDecisions())
|
|
217
|
+
});
|
|
218
|
+
const appRouter = t.router({
|
|
219
|
+
session: sessionRouter,
|
|
220
|
+
context: contextRouter,
|
|
221
|
+
questions: questionsRouter,
|
|
222
|
+
decisions: decisionsRouter
|
|
223
|
+
});
|
|
224
|
+
function createContext() {
|
|
225
|
+
return {
|
|
226
|
+
state: getOrCreateState()
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function createServer(options = {}) {
|
|
230
|
+
const port = options.port ?? 3030;
|
|
231
|
+
const host = options.host ?? "localhost";
|
|
232
|
+
const wss = new WebSocketServer({
|
|
233
|
+
port,
|
|
234
|
+
host
|
|
235
|
+
});
|
|
236
|
+
const handler = applyWSSHandler({
|
|
237
|
+
wss,
|
|
238
|
+
router: appRouter,
|
|
239
|
+
createContext: createContext,
|
|
240
|
+
keepAlive: {
|
|
241
|
+
enabled: true,
|
|
242
|
+
pingMs: 30000,
|
|
243
|
+
pongWaitMs: 5000
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
console.error(`[coordinator] WebSocket server listening on ws://${host}:${port}`);
|
|
247
|
+
wss.on("connection", (ws)=>{
|
|
248
|
+
console.error(`[coordinator] Client connected (${wss.clients.size} total)`);
|
|
249
|
+
ws.on("close", ()=>{
|
|
250
|
+
console.error(`[coordinator] Client disconnected (${wss.clients.size} total)`);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
const close = ()=>{
|
|
254
|
+
console.error("[coordinator] Shutting down server...");
|
|
255
|
+
handler.broadcastReconnectNotification();
|
|
256
|
+
wss.close();
|
|
257
|
+
};
|
|
258
|
+
return {
|
|
259
|
+
wss,
|
|
260
|
+
close
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export { CoordinatorState, DEFAULT_HOST, DEFAULT_PORT, appRouter, createContext, createServer, getOrCreateState, resetState };
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 C. Spencer Beggs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @spencerbeggs/claude-coordinator-server
|
|
2
|
+
|
|
3
|
+
tRPC WebSocket server enabling real-time coordination between Claude Code
|
|
4
|
+
instances through session management, context sharing, Q&A, and decision
|
|
5
|
+
logging.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Real-time communication** - WebSocket-based with tRPC subscriptions
|
|
10
|
+
- **Session management** - Agents join/leave coordinated sessions
|
|
11
|
+
- **Context sharing** - Share and retrieve key-value context entries
|
|
12
|
+
- **Q&A system** - Ask and answer questions between agents
|
|
13
|
+
- **Decision logging** - Track decisions made during coordination
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @spencerbeggs/claude-coordinator-server
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
### Start the Server
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Using npx
|
|
27
|
+
npx @spencerbeggs/claude-coordinator-server
|
|
28
|
+
|
|
29
|
+
# With custom port
|
|
30
|
+
PORT=3031 npx @spencerbeggs/claude-coordinator-server
|
|
31
|
+
|
|
32
|
+
# With custom host
|
|
33
|
+
HOST=0.0.0.0 PORT=3030 npx @spencerbeggs/claude-coordinator-server
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Use Programmatically
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
import { createServer } from "@spencerbeggs/claude-coordinator-server";
|
|
40
|
+
|
|
41
|
+
const server = createServer({
|
|
42
|
+
port: 3030,
|
|
43
|
+
host: "localhost",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
console.log("Server started on ws://localhost:3030");
|
|
47
|
+
|
|
48
|
+
// Graceful shutdown
|
|
49
|
+
process.on("SIGTERM", () => {
|
|
50
|
+
server.close();
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## API Overview
|
|
55
|
+
|
|
56
|
+
The server exposes tRPC procedures organized by domain:
|
|
57
|
+
|
|
58
|
+
### Session
|
|
59
|
+
|
|
60
|
+
| Procedure | Type | Description |
|
|
61
|
+
| --------- | ---- | ----------- |
|
|
62
|
+
| `session.join` | mutation | Join session as an agent |
|
|
63
|
+
| `session.leave` | mutation | Leave current session |
|
|
64
|
+
| `session.list` | query | List connected agents |
|
|
65
|
+
| `session.onAgentChange` | subscription | Real-time agent updates |
|
|
66
|
+
|
|
67
|
+
### Context
|
|
68
|
+
|
|
69
|
+
| Procedure | Type | Description |
|
|
70
|
+
| --------- | ---- | ----------- |
|
|
71
|
+
| `context.share` | mutation | Share a context entry |
|
|
72
|
+
| `context.get` | query | Get context by key |
|
|
73
|
+
| `context.list` | query | List context entries |
|
|
74
|
+
| `context.onContextChange` | subscription | Real-time context updates |
|
|
75
|
+
|
|
76
|
+
### Questions
|
|
77
|
+
|
|
78
|
+
| Procedure | Type | Description |
|
|
79
|
+
| --------- | ---- | ----------- |
|
|
80
|
+
| `questions.ask` | mutation | Ask a question |
|
|
81
|
+
| `questions.answer` | mutation | Answer a question |
|
|
82
|
+
| `questions.listPending` | query | List unanswered questions |
|
|
83
|
+
| `questions.onQuestion` | subscription | Real-time Q&A updates |
|
|
84
|
+
|
|
85
|
+
### Decisions
|
|
86
|
+
|
|
87
|
+
| Procedure | Type | Description |
|
|
88
|
+
| --------- | ---- | ----------- |
|
|
89
|
+
| `decisions.log` | mutation | Log a decision |
|
|
90
|
+
| `decisions.list` | query | List all decisions |
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
| Environment Variable | Default | Description |
|
|
95
|
+
| -------------------- | ------- | ----------- |
|
|
96
|
+
| `PORT` | `3030` | Server port |
|
|
97
|
+
| `HOST` | `localhost` | Server host |
|
|
98
|
+
|
|
99
|
+
## Documentation
|
|
100
|
+
|
|
101
|
+
- [Architecture Design Doc](../../.claude/design/claude-coordinator-server/architecture.md)
|
|
102
|
+
- [Core Schemas Package](../claude-coordinator-core/README.md)
|
|
103
|
+
- [MCP Bridge Package](../claude-coordinator-mcp/README.md)
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { DEFAULT_PORT, createServer, DEFAULT_HOST } from "../36.js";
|
|
3
|
+
const port = Number(process.env.PORT) || DEFAULT_PORT;
|
|
4
|
+
const host = process.env.HOST ?? DEFAULT_HOST;
|
|
5
|
+
const server = createServer({
|
|
6
|
+
port: port,
|
|
7
|
+
host: host
|
|
8
|
+
});
|
|
9
|
+
const shutdown = ()=>{
|
|
10
|
+
server.close();
|
|
11
|
+
process.exit(0);
|
|
12
|
+
};
|
|
13
|
+
process.on("SIGINT", shutdown);
|
|
14
|
+
process.on("SIGTERM", shutdown);
|
|
15
|
+
console.error("[coordinator] Server started. Press Ctrl+C to stop.");
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* \@spencerbeggs/claude-coordinator-server
|
|
3
|
+
*
|
|
4
|
+
* tRPC WebSocket server for the Claude Coordinator system.
|
|
5
|
+
* Provides real-time communication between Claude Code instances.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Agent } from '@spencerbeggs/claude-coordinator-core';
|
|
11
|
+
import type { ContextEntry } from '@spencerbeggs/claude-coordinator-core';
|
|
12
|
+
import type { Decision } from '@spencerbeggs/claude-coordinator-core';
|
|
13
|
+
import { EventEmitter } from 'node:events';
|
|
14
|
+
import type { Question } from '@spencerbeggs/claude-coordinator-core';
|
|
15
|
+
import { TRPCDefaultErrorShape } from '@trpc/server';
|
|
16
|
+
import { TRPCRootObject } from '@trpc/server';
|
|
17
|
+
import { TRPCRuntimeConfigOptions } from '@trpc/server';
|
|
18
|
+
import { WebSocketServer } from 'ws';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Type exports for clients
|
|
22
|
+
*/
|
|
23
|
+
export declare type AppRouter = typeof appRouter;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Main application router
|
|
27
|
+
*/
|
|
28
|
+
export declare const appRouter: typeof t.router extends (...args: any) => infer R ? R : unknown;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Context passed to each tRPC procedure
|
|
32
|
+
*/
|
|
33
|
+
export declare interface Context {
|
|
34
|
+
state: CoordinatorState;
|
|
35
|
+
agentId?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Server instance with cleanup method
|
|
40
|
+
*/
|
|
41
|
+
export declare interface CoordinatorServer {
|
|
42
|
+
wss: WebSocketServer;
|
|
43
|
+
close: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* EventEmitter-based state manager for the coordinator
|
|
48
|
+
*/
|
|
49
|
+
export declare class CoordinatorState extends EventEmitter<CoordinatorStateEvents> {
|
|
50
|
+
private sessionId;
|
|
51
|
+
private agents;
|
|
52
|
+
private context;
|
|
53
|
+
private questions;
|
|
54
|
+
private decisions;
|
|
55
|
+
constructor(sessionId: string);
|
|
56
|
+
getSessionId(): string;
|
|
57
|
+
addAgent(agent: Agent): void;
|
|
58
|
+
removeAgent(agentId: string): boolean;
|
|
59
|
+
getAgent(agentId: string): Agent | undefined;
|
|
60
|
+
listAgents(): Agent[];
|
|
61
|
+
setContext(entry: ContextEntry): void;
|
|
62
|
+
getContext(key: string): ContextEntry | undefined;
|
|
63
|
+
listContext(filters?: {
|
|
64
|
+
tags?: string[];
|
|
65
|
+
createdBy?: string;
|
|
66
|
+
}): ContextEntry[];
|
|
67
|
+
addQuestion(question: Question): void;
|
|
68
|
+
answerQuestion(questionId: string, answer: string, answeredBy: string): Question | undefined;
|
|
69
|
+
getQuestion(questionId: string): Question | undefined;
|
|
70
|
+
listPendingQuestions(forAgentId?: string): Question[];
|
|
71
|
+
addDecision(decision: Decision): void;
|
|
72
|
+
listDecisions(): Decision[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Events emitted by the coordinator state
|
|
77
|
+
*/
|
|
78
|
+
export declare interface CoordinatorStateEvents {
|
|
79
|
+
agentChange: [agents: Agent[]];
|
|
80
|
+
contextChange: [entry: ContextEntry];
|
|
81
|
+
question: [question: Question];
|
|
82
|
+
answer: [question: Question];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Create context for a request
|
|
87
|
+
*/
|
|
88
|
+
export declare function createContext(): Context;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create and start the coordinator WebSocket server
|
|
92
|
+
*/
|
|
93
|
+
export declare function createServer(options?: ServerOptions): CoordinatorServer;
|
|
94
|
+
|
|
95
|
+
export declare function getOrCreateState(sessionId?: string): CoordinatorState;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resets the global coordinator state singleton to null.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* Call this in test teardown (e.g., `afterEach` or `afterAll`) to ensure clean
|
|
102
|
+
* state between test suites. This prevents state from leaking across tests when
|
|
103
|
+
* using the singleton pattern.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* afterEach(() => {
|
|
108
|
+
* resetState();
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export declare function resetState(): void;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Options for creating the coordinator server
|
|
116
|
+
*/
|
|
117
|
+
export declare interface ServerOptions {
|
|
118
|
+
port?: number;
|
|
119
|
+
host?: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
declare const t: TRPCRootObject<Context, object, TRPCRuntimeConfigOptions<Context, object>, {
|
|
123
|
+
ctx: Context;
|
|
124
|
+
meta: object;
|
|
125
|
+
errorShape: TRPCDefaultErrorShape;
|
|
126
|
+
transformer: false;
|
|
127
|
+
}>;
|
|
128
|
+
|
|
129
|
+
export { }
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CoordinatorState, appRouter, createContext, createServer, getOrCreateState, resetState } from "./36.js";
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@spencerbeggs/claude-coordinator-server",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "tRPC WebSocket server for Claude Coordinator",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"claude",
|
|
8
|
+
"coordinator",
|
|
9
|
+
"trpc",
|
|
10
|
+
"websocket",
|
|
11
|
+
"server"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/spencerbeggs/claude-design-coordinator.git",
|
|
16
|
+
"directory": "pkgs/claude-coordinator-server"
|
|
17
|
+
},
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "C. Spencer Beggs",
|
|
21
|
+
"email": "spencer@beggs.codes",
|
|
22
|
+
"url": "https://spencerbeg.gs"
|
|
23
|
+
},
|
|
24
|
+
"type": "module",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./index.d.ts",
|
|
28
|
+
"import": "./index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"bin": {
|
|
32
|
+
"claude-coordinator-server": "./bin/claude-coordinator-server.js"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@spencerbeggs/claude-coordinator-core": "0.1.0",
|
|
36
|
+
"@trpc/server": "^11.8.1",
|
|
37
|
+
"ws": "^8.19.0",
|
|
38
|
+
"zod": "^4.3.5"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"36.js",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"README.md",
|
|
47
|
+
"bin/claude-coordinator-server.js",
|
|
48
|
+
"index.d.ts",
|
|
49
|
+
"index.js",
|
|
50
|
+
"package.json"
|
|
51
|
+
]
|
|
52
|
+
}
|