claude-tempo 0.2.0 → 0.2.2
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 +18 -44
- package/dist/cli/commands.d.ts +2 -1
- package/dist/cli/commands.js +13 -37
- package/dist/cli.js +3 -2
- package/dist/config.d.ts +12 -0
- package/dist/config.js +17 -4
- package/dist/copilot-bridge.js +11 -11
- package/dist/server.js +5 -5
- package/dist/spawn.d.ts +19 -0
- package/dist/spawn.js +50 -0
- package/dist/tools/recruit.js +9 -55
- package/dist/types.d.ts +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -156,7 +156,6 @@ The `claude-tempo` CLI handles setup, session management, and diagnostics.
|
|
|
156
156
|
```
|
|
157
157
|
--temporal-address <addr> Temporal server address (default: localhost:7233)
|
|
158
158
|
-n, --name <name> Set the player name for the session (start/conduct/up)
|
|
159
|
-
--agent <claude|copilot> Agent type to spawn (default: claude; start/conduct)
|
|
160
159
|
--skip-preflight Skip preflight checks (start/conduct)
|
|
161
160
|
--background, -d Run Temporal in background (server only)
|
|
162
161
|
--dir <path> Target directory for init (default: cwd)
|
|
@@ -187,7 +186,6 @@ ok You're all set!
|
|
|
187
186
|
|
|
188
187
|
What next?
|
|
189
188
|
claude-tempo start myband Add a player session
|
|
190
|
-
claude-tempo start myband --agent copilot -n copilot-1 Add a Copilot player
|
|
191
189
|
claude-tempo status myband See who's active
|
|
192
190
|
Or ask the conductor to recruit players for you
|
|
193
191
|
```
|
|
@@ -395,8 +393,14 @@ When a Claude Code session crashes or is closed without graceful shutdown, its T
|
|
|
395
393
|
|
|
396
394
|
This means you don't need to manually clean up crashed sessions — just `cue` the dead player and the system handles the rest.
|
|
397
395
|
|
|
396
|
+
## Known limitations
|
|
397
|
+
|
|
398
|
+
- **`recruit` requires manual acknowledgment**: Recruited Claude Code sessions use `--dangerously-load-development-channels` to enable channel-based message delivery. Claude Code shows an interactive confirmation prompt that must be manually acknowledged (press Enter) in the spawned terminal window. This will be resolved once claude-tempo is published as an approved channel plugin. Copilot bridge sessions do not have this limitation.
|
|
399
|
+
|
|
398
400
|
## Copilot CLI integration (experimental)
|
|
399
401
|
|
|
402
|
+
> **Warning:** Copilot bridge support is **experimental** and subject to breaking changes. Copilot bridge sessions are headless — they have no interactive terminal. A Claude conductor (or custom Temporal client) is required to send them work via `cue`. Do not rely on this feature for production workflows.
|
|
403
|
+
|
|
400
404
|
GitHub Copilot CLI sessions can join an ensemble via the **Copilot bridge**. The bridge uses the [Copilot SDK](https://github.com/github/copilot-sdk) to spawn a Copilot session with claude-tempo as an MCP server, and injects incoming messages as prompts.
|
|
401
405
|
|
|
402
406
|
### Setup
|
|
@@ -413,54 +417,26 @@ You also need:
|
|
|
413
417
|
|
|
414
418
|
### Starting a Copilot player
|
|
415
419
|
|
|
416
|
-
|
|
417
|
-
# Via CLI (recommended):
|
|
418
|
-
claude-tempo start --agent copilot -n copilot-dev
|
|
419
|
-
|
|
420
|
-
# Or directly with env vars (Linux/macOS):
|
|
421
|
-
CLAUDE_TEMPO_ENSEMBLE=default COPILOT_BRIDGE_NAME=copilot-dev npx ts-node src/copilot-bridge.ts
|
|
422
|
-
|
|
423
|
-
# Or directly with env vars (Windows PowerShell):
|
|
424
|
-
$env:TEMPORAL_ADDRESS="localhost:7233"; $env:CLAUDE_TEMPO_ENSEMBLE="default"; $env:COPILOT_BRIDGE_NAME="copilot-dev"; npx ts-node src/copilot-bridge.ts
|
|
425
|
-
|
|
426
|
-
# Or from any session in the ensemble, recruit one:
|
|
427
|
-
# "Recruit a copilot session named 'copilot-dev' with agent copilot"
|
|
428
|
-
```
|
|
420
|
+
The easiest way to add a Copilot player is via the `recruit` tool from any active session in the ensemble. The `agent` parameter accepts `"claude"` (default) or `"copilot"`:
|
|
429
421
|
|
|
430
|
-
|
|
422
|
+
> "Recruit a copilot session named 'copilot-dev' in /repos/my-project with agent copilot"
|
|
431
423
|
|
|
432
|
-
|
|
424
|
+
This spawns a headless bridge process, registers it as a Temporal workflow, and sets the player name automatically.
|
|
433
425
|
|
|
434
|
-
|
|
426
|
+
<details>
|
|
427
|
+
<summary>Advanced: running the bridge directly</summary>
|
|
435
428
|
|
|
436
|
-
|
|
429
|
+
You can also start the bridge manually with environment variables:
|
|
437
430
|
|
|
438
431
|
```bash
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
npx ts-node /path/to/claude-tempo/src/copilot-bridge.ts
|
|
442
|
-
}
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
**Windows** — add to your PowerShell `$PROFILE`:
|
|
432
|
+
# Linux/macOS
|
|
433
|
+
CLAUDE_TEMPO_ENSEMBLE=default COPILOT_BRIDGE_NAME=copilot-dev npx ts-node src/copilot-bridge.ts
|
|
446
434
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
$env:TEMPORAL_ADDRESS = "localhost:7233"
|
|
450
|
-
$env:CLAUDE_TEMPO_ENSEMBLE = $ensemble
|
|
451
|
-
$env:COPILOT_BRIDGE_NAME = $name
|
|
452
|
-
npx ts-node C:\path\to\claude-tempo\src\copilot-bridge.ts
|
|
453
|
-
$env:CLAUDE_TEMPO_ENSEMBLE = ""
|
|
454
|
-
$env:COPILOT_BRIDGE_NAME = ""
|
|
455
|
-
}
|
|
435
|
+
# Windows PowerShell
|
|
436
|
+
$env:TEMPORAL_ADDRESS="localhost:7233"; $env:CLAUDE_TEMPO_ENSEMBLE="default"; $env:COPILOT_BRIDGE_NAME="copilot-dev"; npx ts-node src/copilot-bridge.ts
|
|
456
437
|
```
|
|
457
438
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
```bash
|
|
461
|
-
copilot-tempo # join "default" ensemble, auto-generated name
|
|
462
|
-
copilot-tempo my-project copilot-1 # join "my-project" ensemble as "copilot-1"
|
|
463
|
-
```
|
|
439
|
+
</details>
|
|
464
440
|
|
|
465
441
|
### How it works
|
|
466
442
|
|
|
@@ -481,10 +457,8 @@ copilot-tempo my-project copilot-1 # join "my-project" ensemble as "copilot-1"
|
|
|
481
457
|
|
|
482
458
|
### Limitations
|
|
483
459
|
|
|
484
|
-
- **`recruit` requires manual acknowledgment (Claude backend)**: Recruited Claude Code sessions use `--dangerously-load-development-channels` to enable channel-based message delivery. Claude Code shows an interactive confirmation prompt that must be manually acknowledged (press Enter) in the spawned terminal window. This will be resolved once claude-tempo is published as an approved channel plugin. The Copilot backend does not have this limitation.
|
|
485
460
|
- **No interactive access** — Copilot bridge sessions run in the background. Unlike Claude Code sessions where you can chat directly, bridge sessions only respond to cues from other players. To send messages to a bridge session, use `cue` from another player or signal the workflow directly via the Temporal CLI.
|
|
486
|
-
- **
|
|
487
|
-
- **No push-based message delivery** — the bridge polls for messages (2s interval), unlike Claude Code sessions which receive instant channel notifications.
|
|
461
|
+
- **Polling latency** — the bridge polls for messages every 2 seconds, unlike Claude Code sessions which receive instant channel notifications. This adds slight latency to message delivery and orchestration.
|
|
488
462
|
- **Copilot sessions must be spawned via the bridge** to participate (not standalone Copilot CLI).
|
|
489
463
|
- **The `@github/copilot-sdk` adds ~243MB** to node_modules when installed.
|
|
490
464
|
- **Node 20+ required for Copilot features** — the `@github/copilot-sdk` requires Node.js 20 or later. The rest of claude-tempo works on Node 18+.
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { AgentType } from '../types';
|
|
1
2
|
interface StartOpts {
|
|
2
3
|
ensemble: string;
|
|
3
4
|
conductor: boolean;
|
|
4
5
|
temporalAddress: string;
|
|
5
6
|
name?: string;
|
|
6
7
|
skipPreflight?: boolean;
|
|
7
|
-
agent:
|
|
8
|
+
agent: AgentType;
|
|
8
9
|
}
|
|
9
10
|
export declare function start(opts: StartOpts): Promise<void>;
|
|
10
11
|
interface StatusOpts {
|
package/dist/cli/commands.js
CHANGED
|
@@ -87,38 +87,14 @@ async function start(opts) {
|
|
|
87
87
|
}
|
|
88
88
|
out.log(`Starting ${out.bold(role)} in ensemble ${out.cyan(opts.ensemble)}${opts.agent === 'copilot' ? out.dim(' (copilot)') : ''}`);
|
|
89
89
|
if (opts.agent === 'copilot') {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const logName = opts.name || `copilot-${Date.now()}`;
|
|
99
|
-
const logPath = (0, path_1.join)(workDir, 'logs', `${logName}.log`);
|
|
100
|
-
fs.mkdirSync((0, path_1.join)(workDir, 'logs'), { recursive: true });
|
|
101
|
-
const logFd = fs.openSync(logPath, 'a');
|
|
102
|
-
let child;
|
|
103
|
-
try {
|
|
104
|
-
child = (0, child_process_1.spawn)(cmd, cmdArgs, {
|
|
105
|
-
cwd: workDir,
|
|
106
|
-
detached: true,
|
|
107
|
-
stdio: ['ignore', logFd, logFd],
|
|
108
|
-
env: {
|
|
109
|
-
...process.env,
|
|
110
|
-
CLAUDE_TEMPO_ENSEMBLE: opts.ensemble,
|
|
111
|
-
COPILOT_BRIDGE_NAME: opts.name || '',
|
|
112
|
-
TEMPORAL_ADDRESS: opts.temporalAddress,
|
|
113
|
-
...(opts.conductor ? { CLAUDE_TEMPO_CONDUCTOR: 'true' } : {}),
|
|
114
|
-
},
|
|
115
|
-
});
|
|
116
|
-
child.unref();
|
|
117
|
-
}
|
|
118
|
-
finally {
|
|
119
|
-
fs.closeSync(logFd);
|
|
120
|
-
}
|
|
121
|
-
out.success(`Launched copilot bridge${opts.name ? ` "${opts.name}"` : ''} (pid ${child.pid ?? 'unknown'})`);
|
|
90
|
+
const { pid } = (0, spawn_1.spawnCopilotBridge)({
|
|
91
|
+
name: opts.name || `copilot-${Date.now()}`,
|
|
92
|
+
ensemble: opts.ensemble,
|
|
93
|
+
temporalAddress: opts.temporalAddress,
|
|
94
|
+
isConductor: opts.conductor,
|
|
95
|
+
workDir,
|
|
96
|
+
});
|
|
97
|
+
out.success(`Launched copilot bridge${opts.name ? ` "${opts.name}"` : ''} (pid ${pid ?? 'unknown'})`);
|
|
122
98
|
}
|
|
123
99
|
else {
|
|
124
100
|
const claudeArgs = [
|
|
@@ -129,13 +105,13 @@ async function start(opts) {
|
|
|
129
105
|
claudeArgs.push('-n', opts.name);
|
|
130
106
|
}
|
|
131
107
|
const envVars = {
|
|
132
|
-
|
|
108
|
+
[config_1.ENV.ENSEMBLE]: opts.ensemble,
|
|
133
109
|
};
|
|
134
110
|
if (opts.conductor) {
|
|
135
|
-
envVars.
|
|
111
|
+
envVars[config_1.ENV.CONDUCTOR] = 'true';
|
|
136
112
|
}
|
|
137
113
|
if (opts.name) {
|
|
138
|
-
envVars.
|
|
114
|
+
envVars[config_1.ENV.PLAYER_NAME] = opts.name;
|
|
139
115
|
}
|
|
140
116
|
const { pid } = (0, spawn_1.spawnInTerminal)(claudeArgs, workDir, envVars);
|
|
141
117
|
out.success(`Launched ${role} session${opts.name ? ` "${opts.name}"` : ''} (pid ${pid ?? 'unknown'})`);
|
|
@@ -454,8 +430,8 @@ async function up(opts) {
|
|
|
454
430
|
if (opts.name)
|
|
455
431
|
claudeArgs.push('-n', opts.name);
|
|
456
432
|
const { pid } = (0, spawn_1.spawnInTerminal)(claudeArgs, process.cwd(), {
|
|
457
|
-
|
|
458
|
-
|
|
433
|
+
[config_1.ENV.ENSEMBLE]: opts.ensemble,
|
|
434
|
+
[config_1.ENV.CONDUCTOR]: 'true',
|
|
459
435
|
});
|
|
460
436
|
console.log();
|
|
461
437
|
out.success('You\'re all set!');
|
package/dist/cli.js
CHANGED
|
@@ -37,11 +37,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
const commands_1 = require("./cli/commands");
|
|
38
38
|
const preflight_1 = require("./cli/preflight");
|
|
39
39
|
const out = __importStar(require("./cli/output"));
|
|
40
|
+
const config_1 = require("./config");
|
|
40
41
|
function parseArgs(argv) {
|
|
41
42
|
const result = {
|
|
42
43
|
command: 'help',
|
|
43
44
|
positional: [],
|
|
44
|
-
temporalAddress: process.env.TEMPORAL_ADDRESS || 'localhost:7233',
|
|
45
|
+
temporalAddress: process.env[config_1.ENV.TEMPORAL_ADDRESS] || 'localhost:7233',
|
|
45
46
|
dir: process.cwd(),
|
|
46
47
|
skipPreflight: false,
|
|
47
48
|
background: false,
|
|
@@ -99,7 +100,7 @@ function parseArgs(argv) {
|
|
|
99
100
|
}
|
|
100
101
|
async function main() {
|
|
101
102
|
const args = parseArgs(process.argv.slice(2));
|
|
102
|
-
const ensemble = args.positional[1] || process.env.
|
|
103
|
+
const ensemble = args.positional[1] || process.env[config_1.ENV.ENSEMBLE] || 'default';
|
|
103
104
|
switch (args.command) {
|
|
104
105
|
case 'conduct':
|
|
105
106
|
await (0, commands_1.start)({
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/** Environment variable name constants — use these instead of string literals. */
|
|
2
|
+
export declare const ENV: {
|
|
3
|
+
readonly ENSEMBLE: "CLAUDE_TEMPO_ENSEMBLE";
|
|
4
|
+
readonly CONDUCTOR: "CLAUDE_TEMPO_CONDUCTOR";
|
|
5
|
+
readonly PLAYER_NAME: "CLAUDE_TEMPO_PLAYER_NAME";
|
|
6
|
+
readonly TASK_QUEUE: "CLAUDE_TEMPO_TASK_QUEUE";
|
|
7
|
+
readonly BRIDGE_NAME: "COPILOT_BRIDGE_NAME";
|
|
8
|
+
readonly BRIDGE_MODE: "CLAUDE_TEMPO_BRIDGE_MODE";
|
|
9
|
+
readonly BRIDGE_MODEL: "COPILOT_BRIDGE_MODEL";
|
|
10
|
+
readonly TEMPORAL_ADDRESS: "TEMPORAL_ADDRESS";
|
|
11
|
+
readonly TEMPORAL_NAMESPACE: "TEMPORAL_NAMESPACE";
|
|
12
|
+
};
|
|
1
13
|
export interface Config {
|
|
2
14
|
temporalAddress: string;
|
|
3
15
|
temporalNamespace: string;
|
package/dist/config.js
CHANGED
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ENV = void 0;
|
|
3
4
|
exports.getConfig = getConfig;
|
|
4
5
|
exports.sessionWorkflowId = sessionWorkflowId;
|
|
5
6
|
exports.conductorWorkflowId = conductorWorkflowId;
|
|
7
|
+
/** Environment variable name constants — use these instead of string literals. */
|
|
8
|
+
exports.ENV = {
|
|
9
|
+
ENSEMBLE: 'CLAUDE_TEMPO_ENSEMBLE',
|
|
10
|
+
CONDUCTOR: 'CLAUDE_TEMPO_CONDUCTOR',
|
|
11
|
+
PLAYER_NAME: 'CLAUDE_TEMPO_PLAYER_NAME',
|
|
12
|
+
TASK_QUEUE: 'CLAUDE_TEMPO_TASK_QUEUE',
|
|
13
|
+
BRIDGE_NAME: 'COPILOT_BRIDGE_NAME',
|
|
14
|
+
BRIDGE_MODE: 'CLAUDE_TEMPO_BRIDGE_MODE',
|
|
15
|
+
BRIDGE_MODEL: 'COPILOT_BRIDGE_MODEL',
|
|
16
|
+
TEMPORAL_ADDRESS: 'TEMPORAL_ADDRESS',
|
|
17
|
+
TEMPORAL_NAMESPACE: 'TEMPORAL_NAMESPACE',
|
|
18
|
+
};
|
|
6
19
|
function getConfig() {
|
|
7
20
|
return {
|
|
8
|
-
temporalAddress: process.env.TEMPORAL_ADDRESS ?? 'localhost:7233',
|
|
9
|
-
temporalNamespace: process.env.TEMPORAL_NAMESPACE ?? 'default',
|
|
10
|
-
taskQueue: process.env.
|
|
11
|
-
ensemble: process.env.
|
|
21
|
+
temporalAddress: process.env[exports.ENV.TEMPORAL_ADDRESS] ?? 'localhost:7233',
|
|
22
|
+
temporalNamespace: process.env[exports.ENV.TEMPORAL_NAMESPACE] ?? 'default',
|
|
23
|
+
taskQueue: process.env[exports.ENV.TASK_QUEUE] ?? 'claude-tempo',
|
|
24
|
+
ensemble: process.env[exports.ENV.ENSEMBLE] ?? 'default',
|
|
12
25
|
};
|
|
13
26
|
}
|
|
14
27
|
/** Build a workflow ID for a player session: claude-session-{ensemble}-{playerId} */
|
package/dist/copilot-bridge.js
CHANGED
|
@@ -101,8 +101,8 @@ async function createSessionWithTimeout(copilotClient, sessionConfig, timeoutMs
|
|
|
101
101
|
}
|
|
102
102
|
async function main() {
|
|
103
103
|
const config = (0, config_1.getConfig)();
|
|
104
|
-
const playerName = process.env.
|
|
105
|
-
const model = process.env.
|
|
104
|
+
const playerName = process.env[config_1.ENV.BRIDGE_NAME];
|
|
105
|
+
const model = process.env[config_1.ENV.BRIDGE_MODEL];
|
|
106
106
|
const workDir = process.cwd();
|
|
107
107
|
log(`Starting Copilot bridge in ${workDir} (ensemble: ${config.ensemble})`);
|
|
108
108
|
// Connect Temporal client (for polling only — the MCP server child process runs its own worker)
|
|
@@ -117,10 +117,10 @@ async function main() {
|
|
|
117
117
|
// `claude-session-{ensemble}-{playerId}`, where playerId comes from
|
|
118
118
|
// CLAUDE_TEMPO_PLAYER_NAME or a random hex. We pass CLAUDE_TEMPO_PLAYER_NAME
|
|
119
119
|
// to the MCP server env so both sides agree on the ID.
|
|
120
|
-
const isConductor = !!process.env.
|
|
120
|
+
const isConductor = !!process.env[config_1.ENV.CONDUCTOR];
|
|
121
121
|
const playerIdForWorkflow = isConductor
|
|
122
122
|
? 'conductor'
|
|
123
|
-
: (process.env.
|
|
123
|
+
: (process.env[config_1.ENV.PLAYER_NAME] || playerName || `copilot-${Date.now()}`);
|
|
124
124
|
const expectedWorkflowId = `claude-session-${config.ensemble}-${playerIdForWorkflow}`;
|
|
125
125
|
// Build the MCP server command — always use the compiled dist/server.js
|
|
126
126
|
// Run `npm run build` (or `pnpm build`) before using the bridge.
|
|
@@ -134,13 +134,13 @@ async function main() {
|
|
|
134
134
|
const serverArgs = [serverJsPath];
|
|
135
135
|
const mcpEnv = {
|
|
136
136
|
...cleanEnv(),
|
|
137
|
-
|
|
138
|
-
TEMPORAL_ADDRESS: config.temporalAddress,
|
|
139
|
-
TEMPORAL_NAMESPACE: config.temporalNamespace,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
137
|
+
[config_1.ENV.ENSEMBLE]: config.ensemble,
|
|
138
|
+
[config_1.ENV.TEMPORAL_ADDRESS]: config.temporalAddress,
|
|
139
|
+
[config_1.ENV.TEMPORAL_NAMESPACE]: config.temporalNamespace,
|
|
140
|
+
[config_1.ENV.TASK_QUEUE]: config.taskQueue,
|
|
141
|
+
[config_1.ENV.CONDUCTOR]: process.env[config_1.ENV.CONDUCTOR] || '',
|
|
142
|
+
[config_1.ENV.BRIDGE_MODE]: '1', // disable MCP server's message poller — bridge handles delivery
|
|
143
|
+
[config_1.ENV.PLAYER_NAME]: playerIdForWorkflow, // ensures MCP server uses same workflow ID
|
|
144
144
|
};
|
|
145
145
|
// Spawn Copilot SDK client and session
|
|
146
146
|
const copilotClient = new CopilotClient({
|
package/dist/server.js
CHANGED
|
@@ -79,8 +79,8 @@ function getGitInfo(workDir) {
|
|
|
79
79
|
}
|
|
80
80
|
async function main() {
|
|
81
81
|
// Only activate when explicitly opted in via CLAUDE_TEMPO_ENSEMBLE
|
|
82
|
-
if (!process.env.
|
|
83
|
-
log(
|
|
82
|
+
if (!process.env[config_1.ENV.ENSEMBLE]) {
|
|
83
|
+
log(`${config_1.ENV.ENSEMBLE} not set — MCP server idle (no workflow started)`);
|
|
84
84
|
// Keep the process alive so Claude Code doesn't see a crash, but do nothing
|
|
85
85
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
86
86
|
const idleServer = new mcp_js_1.McpServer({ name: 'claude-tempo', version: '0.1.0' });
|
|
@@ -88,8 +88,8 @@ async function main() {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
const config = (0, config_1.getConfig)();
|
|
91
|
-
const isConductor = process.env.
|
|
92
|
-
let playerId = isConductor ? 'conductor' : (process.env.
|
|
91
|
+
const isConductor = process.env[config_1.ENV.CONDUCTOR] === 'true';
|
|
92
|
+
let playerId = isConductor ? 'conductor' : (process.env[config_1.ENV.PLAYER_NAME] || crypto.randomBytes(4).toString('hex'));
|
|
93
93
|
const getPlayerId = () => playerId;
|
|
94
94
|
const setPlayerId = (id) => { playerId = id; };
|
|
95
95
|
const workDir = process.cwd();
|
|
@@ -185,7 +185,7 @@ async function main() {
|
|
|
185
185
|
// Skip when running under the Copilot bridge: the bridge has its own poller that
|
|
186
186
|
// injects messages via sendAndWait. If both pollers run, this one wins the race and
|
|
187
187
|
// sends messages via notifications/claude/channel — which Copilot doesn't understand.
|
|
188
|
-
const isBridgeMode = process.env.
|
|
188
|
+
const isBridgeMode = process.env[config_1.ENV.BRIDGE_MODE] === '1';
|
|
189
189
|
const stopPoller = isBridgeMode
|
|
190
190
|
? () => { } // no-op — bridge handles message delivery
|
|
191
191
|
: (0, channel_1.startMessagePoller)(handle, async (messages) => {
|
package/dist/spawn.d.ts
CHANGED
|
@@ -31,3 +31,22 @@ export declare function buildClaudeCommand(claudeBin: string, claudeArgs: string
|
|
|
31
31
|
export declare function spawnInTerminal(claudeArgs: string[], workDir: string, envVars: Record<string, string>): {
|
|
32
32
|
pid: number | undefined;
|
|
33
33
|
};
|
|
34
|
+
export interface CopilotBridgeOpts {
|
|
35
|
+
name: string;
|
|
36
|
+
ensemble: string;
|
|
37
|
+
temporalAddress: string;
|
|
38
|
+
isConductor?: boolean;
|
|
39
|
+
workDir: string;
|
|
40
|
+
/** Directory for log and PID files. Defaults to `logs/` inside workDir. */
|
|
41
|
+
logDir?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface CopilotBridgeResult {
|
|
44
|
+
pid: number | undefined;
|
|
45
|
+
logPath: string;
|
|
46
|
+
pidPath: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Spawn a copilot bridge as a detached headless subprocess.
|
|
50
|
+
* Sets up log file, PID file, and all required env vars.
|
|
51
|
+
*/
|
|
52
|
+
export declare function spawnCopilotBridge(opts: CopilotBridgeOpts): CopilotBridgeResult;
|
package/dist/spawn.js
CHANGED
|
@@ -6,10 +6,12 @@ exports.detectMacTerminal = detectMacTerminal;
|
|
|
6
6
|
exports.findLinuxTerminal = findLinuxTerminal;
|
|
7
7
|
exports.buildClaudeCommand = buildClaudeCommand;
|
|
8
8
|
exports.spawnInTerminal = spawnInTerminal;
|
|
9
|
+
exports.spawnCopilotBridge = spawnCopilotBridge;
|
|
9
10
|
const child_process_1 = require("child_process");
|
|
10
11
|
const fs_1 = require("fs");
|
|
11
12
|
const path_1 = require("path");
|
|
12
13
|
const os_1 = require("os");
|
|
14
|
+
const config_1 = require("./config");
|
|
13
15
|
const log = (...args) => console.error('[claude-tempo:spawn]', ...args);
|
|
14
16
|
/** POSIX shell-safe single-quoting (works in bash, zsh, and fish) */
|
|
15
17
|
function shellQuote(s) {
|
|
@@ -207,3 +209,51 @@ function spawnInTerminal(claudeArgs, workDir, envVars) {
|
|
|
207
209
|
child.unref();
|
|
208
210
|
return { pid: child.pid };
|
|
209
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Resolve the path to the compiled copilot-bridge.js.
|
|
214
|
+
* In dev (ts-node), returns a ts-node command; in production, returns the dist path.
|
|
215
|
+
*/
|
|
216
|
+
function resolveBridgePath() {
|
|
217
|
+
const isDev = __filename.endsWith('.ts');
|
|
218
|
+
if (isDev) {
|
|
219
|
+
return { cmd: 'npx', args: ['ts-node', (0, path_1.resolve)(__dirname, 'copilot-bridge.ts')] };
|
|
220
|
+
}
|
|
221
|
+
return { cmd: 'node', args: [(0, path_1.resolve)(__dirname, 'copilot-bridge.js')] };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Spawn a copilot bridge as a detached headless subprocess.
|
|
225
|
+
* Sets up log file, PID file, and all required env vars.
|
|
226
|
+
*/
|
|
227
|
+
function spawnCopilotBridge(opts) {
|
|
228
|
+
const { cmd, args } = resolveBridgePath();
|
|
229
|
+
const logDirPath = opts.logDir || (0, path_1.join)(opts.workDir, 'logs');
|
|
230
|
+
const logName = opts.name || `copilot-${Date.now()}`;
|
|
231
|
+
const logPath = (0, path_1.join)(logDirPath, `${logName}.log`);
|
|
232
|
+
const pidPath = (0, path_1.join)(logDirPath, `${logName}.pid`);
|
|
233
|
+
(0, fs_1.mkdirSync)(logDirPath, { recursive: true });
|
|
234
|
+
const logFd = (0, fs_1.openSync)(logPath, 'a');
|
|
235
|
+
let child;
|
|
236
|
+
try {
|
|
237
|
+
child = (0, child_process_1.spawn)(cmd, args, {
|
|
238
|
+
cwd: opts.workDir,
|
|
239
|
+
detached: true,
|
|
240
|
+
stdio: ['ignore', logFd, logFd],
|
|
241
|
+
env: {
|
|
242
|
+
...process.env,
|
|
243
|
+
[config_1.ENV.ENSEMBLE]: opts.ensemble,
|
|
244
|
+
[config_1.ENV.BRIDGE_NAME]: opts.name,
|
|
245
|
+
[config_1.ENV.TEMPORAL_ADDRESS]: opts.temporalAddress,
|
|
246
|
+
...(opts.isConductor ? { [config_1.ENV.CONDUCTOR]: 'true' } : {}),
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
child.unref();
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
(0, fs_1.closeSync)(logFd);
|
|
253
|
+
}
|
|
254
|
+
if (child.pid != null) {
|
|
255
|
+
(0, fs_1.writeFileSync)(pidPath, String(child.pid));
|
|
256
|
+
}
|
|
257
|
+
log(`Spawned copilot-bridge (pid ${child.pid}) in ${opts.workDir} as "${opts.name}"`);
|
|
258
|
+
return { pid: child.pid, logPath, pidPath };
|
|
259
|
+
}
|
package/dist/tools/recruit.js
CHANGED
|
@@ -1,42 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.registerRecruitTool = registerRecruitTool;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
|
-
const child_process_1 = require("child_process");
|
|
39
4
|
const zod_1 = require("zod");
|
|
5
|
+
const config_1 = require("../config");
|
|
40
6
|
const spawn_1 = require("../spawn");
|
|
41
7
|
const resolve_1 = require("./resolve");
|
|
42
8
|
const helpers_1 = require("./helpers");
|
|
@@ -84,25 +50,13 @@ function registerRecruitTool(server, client, config, getPlayerId) {
|
|
|
84
50
|
}
|
|
85
51
|
// Spawn the session using the selected backend
|
|
86
52
|
if (agent === 'copilot') {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
: [path.resolve(__dirname, '..', 'copilot-bridge.js')];
|
|
93
|
-
const child = (0, child_process_1.spawn)(cmd, cmdArgs, {
|
|
94
|
-
cwd: workDir,
|
|
95
|
-
detached: true,
|
|
96
|
-
stdio: 'ignore',
|
|
97
|
-
env: {
|
|
98
|
-
...process.env,
|
|
99
|
-
CLAUDE_TEMPO_ENSEMBLE: config.ensemble,
|
|
100
|
-
COPILOT_BRIDGE_NAME: name,
|
|
101
|
-
TEMPORAL_ADDRESS: config.temporalAddress,
|
|
102
|
-
},
|
|
53
|
+
const { pid } = (0, spawn_1.spawnCopilotBridge)({
|
|
54
|
+
name,
|
|
55
|
+
ensemble: config.ensemble,
|
|
56
|
+
temporalAddress: config.temporalAddress,
|
|
57
|
+
workDir,
|
|
103
58
|
});
|
|
104
|
-
|
|
105
|
-
log(`Spawned copilot-bridge (pid ${child.pid}) in ${workDir} as "${name}"`);
|
|
59
|
+
log(`Spawned copilot-bridge (pid ${pid}) in ${workDir} as "${name}"`);
|
|
106
60
|
}
|
|
107
61
|
else {
|
|
108
62
|
const spawnArgs = [
|
|
@@ -111,8 +65,8 @@ function registerRecruitTool(server, client, config, getPlayerId) {
|
|
|
111
65
|
'-n', name,
|
|
112
66
|
];
|
|
113
67
|
const { pid } = (0, spawn_1.spawnInTerminal)(spawnArgs, workDir, {
|
|
114
|
-
|
|
115
|
-
|
|
68
|
+
[config_1.ENV.ENSEMBLE]: config.ensemble,
|
|
69
|
+
[config_1.ENV.CONDUCTOR]: '',
|
|
116
70
|
});
|
|
117
71
|
log(`Spawned claude process (pid ${pid}) in ${workDir} as "${name}"`);
|
|
118
72
|
}
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-tempo",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "MCP server for multi-session Claude Code coordination via Temporal",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"./signals": {
|
|
26
26
|
"types": "./dist/workflows/signals.d.ts",
|
|
27
27
|
"default": "./dist/workflows/signals.js"
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"./workflow-bundle": "./workflow-bundle.js",
|
|
30
|
+
"./package.json": "./package.json"
|
|
29
31
|
},
|
|
30
32
|
"bin": {
|
|
31
33
|
"claude-tempo": "dist/cli.js",
|