claude-multi-session 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/STRATEGY.md +179 -0
- package/bin/cli.js +693 -0
- package/bin/mcp.js +27 -0
- package/bin/setup.js +469 -0
- package/package.json +42 -0
- package/src/delegate.js +343 -0
- package/src/index.js +35 -0
- package/src/manager.js +510 -0
- package/src/mcp-server.js +808 -0
- package/src/safety-net.js +170 -0
- package/src/store.js +130 -0
- package/src/stream-session.js +463 -0
package/src/delegate.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delegate — The Control Loop for intelligent task delegation.
|
|
3
|
+
*
|
|
4
|
+
* This is what makes the system truly human-like. Instead of just
|
|
5
|
+
* sending messages and getting responses, the Delegate system:
|
|
6
|
+
*
|
|
7
|
+
* 1. Spawns a child session with the right permissions
|
|
8
|
+
* 2. Sends the task
|
|
9
|
+
* 3. Monitors the response for issues (permission denials, errors, going off-track)
|
|
10
|
+
* 4. Auto-handles common problems (permission retries, error recovery)
|
|
11
|
+
* 5. Returns structured output for the parent Claude to evaluate
|
|
12
|
+
* 6. Accepts follow-up instructions and continues the loop
|
|
13
|
+
*
|
|
14
|
+
* The parent Claude acts as the "brain" — it reads the structured output,
|
|
15
|
+
* decides if the work is good, and sends corrections or approvals.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const delegate = new Delegate(manager);
|
|
19
|
+
*
|
|
20
|
+
* // Simple one-shot delegation
|
|
21
|
+
* const result = await delegate.run('fix-auth', {
|
|
22
|
+
* task: 'Fix the authentication bug in auth.service.ts',
|
|
23
|
+
* model: 'sonnet',
|
|
24
|
+
* maxCost: 0.50,
|
|
25
|
+
* });
|
|
26
|
+
* console.log(result.status); // 'completed', 'needs_input', 'failed', 'budget_exceeded'
|
|
27
|
+
* console.log(result.response); // The actual work done
|
|
28
|
+
*
|
|
29
|
+
* // Multi-step delegation with evaluation
|
|
30
|
+
* const r1 = await delegate.run('build-feature', { task: 'Build login page' });
|
|
31
|
+
* // Parent evaluates r1.response...
|
|
32
|
+
* const r2 = await delegate.continue('build-feature', 'Good, now add form validation');
|
|
33
|
+
* // Parent evaluates r2.response...
|
|
34
|
+
* const r3 = await delegate.continue('build-feature', 'Perfect. Now write tests.');
|
|
35
|
+
* delegate.finish('build-feature');
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const SafetyNet = require('./safety-net');
|
|
39
|
+
|
|
40
|
+
// Permission mode recommendations based on task type
|
|
41
|
+
const PERMISSION_PRESETS = {
|
|
42
|
+
// Read-only: can search and read but not modify
|
|
43
|
+
'read-only': {
|
|
44
|
+
permissionMode: 'default',
|
|
45
|
+
allowedTools: ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'Task', 'TaskOutput'],
|
|
46
|
+
},
|
|
47
|
+
// Code review: read + analyze
|
|
48
|
+
'review': {
|
|
49
|
+
permissionMode: 'default',
|
|
50
|
+
allowedTools: ['Read', 'Glob', 'Grep', 'WebSearch', 'WebFetch', 'Task', 'TaskOutput'],
|
|
51
|
+
},
|
|
52
|
+
// Edit files: can read and edit but limited bash
|
|
53
|
+
'edit': {
|
|
54
|
+
permissionMode: 'acceptEdits',
|
|
55
|
+
allowedTools: [], // All tools, but auto-accept edits
|
|
56
|
+
},
|
|
57
|
+
// Full access: can do anything (use with caution)
|
|
58
|
+
'full': {
|
|
59
|
+
permissionMode: 'bypassPermissions',
|
|
60
|
+
allowedTools: [],
|
|
61
|
+
},
|
|
62
|
+
// Plan only: can explore but not execute
|
|
63
|
+
'plan': {
|
|
64
|
+
permissionMode: 'plan',
|
|
65
|
+
allowedTools: [],
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
class Delegate {
|
|
70
|
+
/**
|
|
71
|
+
* @param {SessionManager} manager - The session manager instance
|
|
72
|
+
*/
|
|
73
|
+
constructor(manager) {
|
|
74
|
+
this.manager = manager;
|
|
75
|
+
this.safetyNets = new Map(); // name -> SafetyNet
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run a task in a new delegated session.
|
|
80
|
+
*
|
|
81
|
+
* This is the main entry point. It:
|
|
82
|
+
* 1. Creates a session with appropriate permissions
|
|
83
|
+
* 2. Attaches safety limits
|
|
84
|
+
* 3. Sends the task
|
|
85
|
+
* 4. Detects and auto-handles permission issues
|
|
86
|
+
* 5. Returns structured output
|
|
87
|
+
*
|
|
88
|
+
* @param {string} name - Session name
|
|
89
|
+
* @param {object} options - Task configuration
|
|
90
|
+
* @param {string} options.task - The task description
|
|
91
|
+
* @param {string} [options.model='sonnet'] - Model to use
|
|
92
|
+
* @param {string} [options.preset='edit'] - Permission preset
|
|
93
|
+
* @param {string} [options.workDir] - Working directory
|
|
94
|
+
* @param {number} [options.maxCost=2.00] - Max cost in USD
|
|
95
|
+
* @param {number} [options.maxTurns=50] - Max agent turns
|
|
96
|
+
* @param {string} [options.context] - Extra context to prepend
|
|
97
|
+
* @param {string} [options.systemPrompt] - System prompt append
|
|
98
|
+
* @param {string} [options.agent] - Agent to use
|
|
99
|
+
* @param {boolean} [options.safety=true] - Enable safety net (set false to disable)
|
|
100
|
+
* @returns {Promise<DelegateResult>} Structured result
|
|
101
|
+
*/
|
|
102
|
+
async run(name, options = {}) {
|
|
103
|
+
const task = options.task;
|
|
104
|
+
if (!task) throw new Error('options.task is required');
|
|
105
|
+
|
|
106
|
+
// Get permission preset
|
|
107
|
+
const preset = PERMISSION_PRESETS[options.preset || 'edit'] || PERMISSION_PRESETS.edit;
|
|
108
|
+
|
|
109
|
+
// Create safety net (only if not disabled)
|
|
110
|
+
// options.safety defaults to true — pass safety: false to disable
|
|
111
|
+
const useSafety = options.safety !== false;
|
|
112
|
+
const safety = useSafety
|
|
113
|
+
? new SafetyNet({
|
|
114
|
+
maxCostUsd: options.maxCost ?? 2.00,
|
|
115
|
+
maxTurns: options.maxTurns ?? 50,
|
|
116
|
+
maxDurationMs: options.maxDuration ?? 300000,
|
|
117
|
+
protectedPaths: options.protectedPaths,
|
|
118
|
+
})
|
|
119
|
+
: null;
|
|
120
|
+
|
|
121
|
+
// Build the full prompt with context and instructions
|
|
122
|
+
const fullPrompt = this._buildPrompt(task, options.context);
|
|
123
|
+
|
|
124
|
+
// Spawn the session
|
|
125
|
+
let spawnResult;
|
|
126
|
+
try {
|
|
127
|
+
spawnResult = await this.manager.spawn(name, {
|
|
128
|
+
prompt: fullPrompt,
|
|
129
|
+
model: options.model || 'sonnet',
|
|
130
|
+
workDir: options.workDir || process.cwd(),
|
|
131
|
+
permissionMode: preset.permissionMode,
|
|
132
|
+
allowedTools: preset.allowedTools.length > 0 ? preset.allowedTools : undefined,
|
|
133
|
+
systemPrompt: options.systemPrompt,
|
|
134
|
+
agent: options.agent,
|
|
135
|
+
});
|
|
136
|
+
} catch (err) {
|
|
137
|
+
return this._makeResult('failed', null, null, {
|
|
138
|
+
error: err.message,
|
|
139
|
+
name,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Attach safety net to the live session (only if enabled)
|
|
144
|
+
if (safety) {
|
|
145
|
+
const session = this.manager.sessions.get(name);
|
|
146
|
+
if (session) {
|
|
147
|
+
safety.attach(session);
|
|
148
|
+
this.safetyNets.set(name, safety);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const response = spawnResult.response;
|
|
153
|
+
|
|
154
|
+
// Check if the response indicates a permission issue
|
|
155
|
+
if (response && this._isPermissionDenied(response.text)) {
|
|
156
|
+
// Auto-retry with permission granted
|
|
157
|
+
const retryResult = await this._handlePermissionRetry(name, response.text);
|
|
158
|
+
if (retryResult) {
|
|
159
|
+
return this._makeResult('completed', retryResult, safety, { name });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check safety violations (only if safety is enabled)
|
|
164
|
+
if (safety && safety.violations.length > 0) {
|
|
165
|
+
const lastViolation = safety.violations[safety.violations.length - 1];
|
|
166
|
+
return this._makeResult(lastViolation.type, response, safety, { name });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Return structured result
|
|
170
|
+
return this._makeResult('completed', response, safety, { name });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Continue a delegated task with follow-up instructions.
|
|
175
|
+
*
|
|
176
|
+
* The parent Claude evaluates the previous result and sends corrections,
|
|
177
|
+
* additional instructions, or approval.
|
|
178
|
+
*
|
|
179
|
+
* @param {string} name - Session name
|
|
180
|
+
* @param {string} message - Follow-up instruction
|
|
181
|
+
* @returns {Promise<DelegateResult>} Structured result
|
|
182
|
+
*/
|
|
183
|
+
async continue(name, message) {
|
|
184
|
+
const safety = this.safetyNets.get(name);
|
|
185
|
+
|
|
186
|
+
let response;
|
|
187
|
+
try {
|
|
188
|
+
response = await this.manager.send(name, message);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
// Session might be stopped — try auto-resume
|
|
191
|
+
try {
|
|
192
|
+
response = await this.manager.resume(name, message);
|
|
193
|
+
// Re-attach safety net to new session
|
|
194
|
+
const session = this.manager.sessions.get(name);
|
|
195
|
+
if (session && safety) {
|
|
196
|
+
safety.attach(session);
|
|
197
|
+
}
|
|
198
|
+
} catch (resumeErr) {
|
|
199
|
+
return this._makeResult('failed', null, safety, {
|
|
200
|
+
error: resumeErr.message,
|
|
201
|
+
name,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check for permission issues
|
|
207
|
+
if (response && this._isPermissionDenied(response.text)) {
|
|
208
|
+
const retryResult = await this._handlePermissionRetry(name, response.text);
|
|
209
|
+
if (retryResult) {
|
|
210
|
+
return this._makeResult('completed', retryResult, safety, { name });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Check safety violations
|
|
215
|
+
if (safety && safety.violations.length > 0) {
|
|
216
|
+
const lastViolation = safety.violations[safety.violations.length - 1];
|
|
217
|
+
return this._makeResult(lastViolation.type, response, safety, { name });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return this._makeResult('completed', response, safety, { name });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Finish a delegated task — stop the session.
|
|
225
|
+
*/
|
|
226
|
+
finish(name) {
|
|
227
|
+
try {
|
|
228
|
+
this.manager.stop(name);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
// Session might already be stopped
|
|
231
|
+
}
|
|
232
|
+
this.safetyNets.delete(name);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Kill a delegated task immediately.
|
|
237
|
+
*/
|
|
238
|
+
abort(name) {
|
|
239
|
+
try {
|
|
240
|
+
this.manager.kill(name);
|
|
241
|
+
} catch (e) {}
|
|
242
|
+
this.safetyNets.delete(name);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ===========================================================================
|
|
246
|
+
// Private
|
|
247
|
+
// ===========================================================================
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Build the full prompt with context and structured output instructions.
|
|
251
|
+
*/
|
|
252
|
+
_buildPrompt(task, context) {
|
|
253
|
+
let prompt = '';
|
|
254
|
+
|
|
255
|
+
if (context) {
|
|
256
|
+
prompt += `CONTEXT:\n${context}\n\n`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
prompt += `TASK:\n${task}\n\n`;
|
|
260
|
+
prompt += `INSTRUCTIONS:\n`;
|
|
261
|
+
prompt += `- Complete the task thoroughly\n`;
|
|
262
|
+
prompt += `- If you encounter errors, try to fix them\n`;
|
|
263
|
+
prompt += `- If you need clarification, say what you need and stop\n`;
|
|
264
|
+
prompt += `- At the end, provide a brief summary of what you did\n`;
|
|
265
|
+
|
|
266
|
+
return prompt;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Detect if a response text indicates a permission denial.
|
|
271
|
+
*/
|
|
272
|
+
_isPermissionDenied(text) {
|
|
273
|
+
if (!text) return false;
|
|
274
|
+
const patterns = [
|
|
275
|
+
'permission to write',
|
|
276
|
+
'permission to edit',
|
|
277
|
+
'permission to create',
|
|
278
|
+
'permission to run',
|
|
279
|
+
'permission to execute',
|
|
280
|
+
'haven\'t granted',
|
|
281
|
+
'need your permission',
|
|
282
|
+
'would you like to allow',
|
|
283
|
+
'please approve',
|
|
284
|
+
'permission denied',
|
|
285
|
+
];
|
|
286
|
+
const lower = text.toLowerCase();
|
|
287
|
+
return patterns.some(p => lower.includes(p));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Handle a permission denial by sending approval.
|
|
292
|
+
*/
|
|
293
|
+
async _handlePermissionRetry(name, deniedText) {
|
|
294
|
+
try {
|
|
295
|
+
const response = await this.manager.send(
|
|
296
|
+
name,
|
|
297
|
+
'Yes, you have permission. Go ahead and proceed with all file operations. Do not ask for permission again — you are fully authorized.'
|
|
298
|
+
);
|
|
299
|
+
return response;
|
|
300
|
+
} catch (err) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Build a structured DelegateResult.
|
|
307
|
+
*
|
|
308
|
+
* @typedef {object} DelegateResult
|
|
309
|
+
* @property {string} status - completed, needs_input, failed, cost_exceeded, turns_exceeded
|
|
310
|
+
* @property {string} response - The text response from the session
|
|
311
|
+
* @property {number} cost - Cost in USD
|
|
312
|
+
* @property {number} turns - Number of agent turns
|
|
313
|
+
* @property {number} duration - Duration in ms
|
|
314
|
+
* @property {string} sessionId - Claude session ID
|
|
315
|
+
* @property {string} name - Session name
|
|
316
|
+
* @property {boolean} canContinue - Whether the session can accept more messages
|
|
317
|
+
* @property {string[]} toolsUsed - Tools that were invoked
|
|
318
|
+
* @property {object} safety - Safety net summary
|
|
319
|
+
* @property {string|null} error - Error message if failed
|
|
320
|
+
*/
|
|
321
|
+
_makeResult(status, response, safety, extra = {}) {
|
|
322
|
+
const result = {
|
|
323
|
+
status: status,
|
|
324
|
+
response: response?.text || '',
|
|
325
|
+
cost: response?.cost || 0,
|
|
326
|
+
turns: response?.turns || 0,
|
|
327
|
+
duration: response?.duration || 0,
|
|
328
|
+
sessionId: response?.sessionId || null,
|
|
329
|
+
name: extra.name || null,
|
|
330
|
+
canContinue: !['failed', 'cost_exceeded', 'turns_exceeded'].includes(status),
|
|
331
|
+
toolsUsed: (response?.toolCalls || []).map(t => t.name),
|
|
332
|
+
safety: safety ? safety.getSummary() : null,
|
|
333
|
+
error: extra.error || null,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Export presets too so users can see/customize them
|
|
341
|
+
Delegate.PERMISSION_PRESETS = PERMISSION_PRESETS;
|
|
342
|
+
|
|
343
|
+
module.exports = Delegate;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-multi-session — Main exports
|
|
3
|
+
*
|
|
4
|
+
* Programmatic API for managing multiple Claude Code sessions.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* const { SessionManager, Delegate, StreamSession, SafetyNet } = require('claude-multi-session');
|
|
8
|
+
*
|
|
9
|
+
* // High-level: delegate tasks with safety and control
|
|
10
|
+
* const mgr = new SessionManager();
|
|
11
|
+
* const delegate = new Delegate(mgr);
|
|
12
|
+
* const result = await delegate.run('fix-bug', { task: 'Fix the auth bug', maxCost: 0.50 });
|
|
13
|
+
*
|
|
14
|
+
* // Mid-level: manage sessions directly
|
|
15
|
+
* const { response } = await mgr.spawn('my-task', { prompt: 'Fix the auth bug' });
|
|
16
|
+
*
|
|
17
|
+
* // Low-level: single streaming session
|
|
18
|
+
* const session = new StreamSession({ name: 'direct', model: 'haiku' });
|
|
19
|
+
* session.start();
|
|
20
|
+
* const r = await session.send('Hello');
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const SessionManager = require('./manager');
|
|
24
|
+
const StreamSession = require('./stream-session');
|
|
25
|
+
const Store = require('./store');
|
|
26
|
+
const Delegate = require('./delegate');
|
|
27
|
+
const SafetyNet = require('./safety-net');
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
SessionManager,
|
|
31
|
+
StreamSession,
|
|
32
|
+
Store,
|
|
33
|
+
Delegate,
|
|
34
|
+
SafetyNet,
|
|
35
|
+
};
|