claude-tempo 0.3.0-beta.1 → 0.3.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -25
- package/dist/cli/commands.d.ts +1 -0
- package/dist/cli/commands.js +4 -2
- package/dist/cli/config-command.js +16 -12
- package/dist/cli.js +2 -0
- package/dist/config.d.ts +10 -0
- package/dist/config.js +45 -0
- package/dist/server.js +3 -2
- package/dist/tools/ensemble.js +2 -0
- package/dist/tools/recruit.d.ts +2 -1
- package/dist/tools/recruit.js +6 -5
- package/dist/types.d.ts +1 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -4,31 +4,7 @@ Multi-session [Claude Code](https://claude.ai/code) coordination via [Temporal](
|
|
|
4
4
|
|
|
5
5
|
Multiple Claude Code sessions discover each other, exchange messages in real time, and coordinate work — across machines, not just localhost.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
You (CLI / Discord / Telegram / Claude Code)
|
|
9
|
-
│
|
|
10
|
-
▼
|
|
11
|
-
┌─────────────────────────────────────────────┐
|
|
12
|
-
│ Temporal Server │
|
|
13
|
-
│ │
|
|
14
|
-
│ ┌───────────┐ │
|
|
15
|
-
│ │ Conductor │──cue──┬──────┬──────┐ │
|
|
16
|
-
│ └───────────┘ │ │ │ │
|
|
17
|
-
│ ▼ ▼ ▼ │
|
|
18
|
-
│ ┌────┐ ┌────┐ ┌────┐ │
|
|
19
|
-
│ │ A │ │ B │ │ C │ │
|
|
20
|
-
│ └──┬─┘ └──┬─┘ └──┬─┘ │
|
|
21
|
-
│ │ │ │ │
|
|
22
|
-
└──────────────────────┼──────┼──────┼────────┘
|
|
23
|
-
│ │ │
|
|
24
|
-
┌────────┘ │ └────────┐
|
|
25
|
-
▼ ▼ ▼
|
|
26
|
-
┌─────────┐ ┌─────────┐ ┌─────────┐
|
|
27
|
-
│ Host 1 │ │ Host 1 │ │ Host 2 │
|
|
28
|
-
│ Claude │ │ Claude │ │ Claude │
|
|
29
|
-
│ Session │ │ Session │ │ Session │
|
|
30
|
-
└─────────┘ └─────────┘ └─────────┘
|
|
31
|
-
```
|
|
7
|
+
Each Claude Code session registers as a **player** in Temporal. Players discover each other with `ensemble`, exchange messages with `cue`, and coordinate work — across machines, not just localhost. An optional **conductor** orchestrates the group and connects to external interfaces like Discord, Telegram, or a dashboard.
|
|
32
8
|
|
|
33
9
|
## Installation
|
|
34
10
|
|
package/dist/cli/commands.d.ts
CHANGED
package/dist/cli/commands.js
CHANGED
|
@@ -54,7 +54,7 @@ const out = __importStar(require("./output"));
|
|
|
54
54
|
const PACKAGE_ROOT = (0, path_1.resolve)(__dirname, '..', '..');
|
|
55
55
|
async function start(opts) {
|
|
56
56
|
const config = (0, config_1.getConfig)(opts);
|
|
57
|
-
const workDir = process.cwd();
|
|
57
|
+
const workDir = opts.dir || process.cwd();
|
|
58
58
|
if (!opts.skipPreflight) {
|
|
59
59
|
const result = await (0, preflight_1.runPreflight)({
|
|
60
60
|
dir: workDir,
|
|
@@ -177,6 +177,7 @@ async function status(opts) {
|
|
|
177
177
|
branch: meta.gitBranch || '',
|
|
178
178
|
host: meta.hostname || '',
|
|
179
179
|
conductor: meta.isConductor || false,
|
|
180
|
+
agentType: meta.agentType || 'claude',
|
|
180
181
|
});
|
|
181
182
|
}
|
|
182
183
|
catch {
|
|
@@ -209,8 +210,9 @@ async function status(opts) {
|
|
|
209
210
|
});
|
|
210
211
|
for (const s of members) {
|
|
211
212
|
const role = s.conductor ? out.yellow(' (conductor)') : '';
|
|
213
|
+
const agent = s.agentType === 'copilot' ? out.dim(' [copilot]') : '';
|
|
212
214
|
const name = out.bold(s.name);
|
|
213
|
-
out.log(` ${name}${role}`);
|
|
215
|
+
out.log(` ${name}${role}${agent}`);
|
|
214
216
|
if (s.part)
|
|
215
217
|
out.log(` ${out.dim(s.part)}`);
|
|
216
218
|
const details = [s.workDir, s.branch, s.host].filter(Boolean).join(' ');
|
|
@@ -42,6 +42,7 @@ const config_1 = require("../config");
|
|
|
42
42
|
const connection_1 = require("../connection");
|
|
43
43
|
const config_2 = require("../config");
|
|
44
44
|
const out = __importStar(require("./output"));
|
|
45
|
+
const SECRET_KEYS = new Set(['temporalApiKey']);
|
|
45
46
|
/** Read a line from stdin with a prompt and optional default value. */
|
|
46
47
|
function ask(prompt, defaultVal, mask = false) {
|
|
47
48
|
return new Promise((resolve) => {
|
|
@@ -178,21 +179,24 @@ function configSet(key, value) {
|
|
|
178
179
|
/** Show current config: `claude-tempo config show` */
|
|
179
180
|
function configShow() {
|
|
180
181
|
out.heading('claude-tempo config');
|
|
181
|
-
const config = (0, config_1.
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
['temporalTlsKeyPath', resolved.temporalTlsKeyPath || '(not set)', config.temporalTlsKeyPath || ''],
|
|
182
|
+
const { config, sources } = (0, config_1.getConfigWithSources)();
|
|
183
|
+
const keys = [
|
|
184
|
+
{ key: 'temporalAddress', configKey: 'temporalAddress' },
|
|
185
|
+
{ key: 'temporalNamespace', configKey: 'temporalNamespace' },
|
|
186
|
+
{ key: 'temporalApiKey', configKey: 'temporalApiKey' },
|
|
187
|
+
{ key: 'temporalTlsCertPath', configKey: 'temporalTlsCertPath' },
|
|
188
|
+
{ key: 'temporalTlsKeyPath', configKey: 'temporalTlsKeyPath' },
|
|
189
189
|
];
|
|
190
190
|
out.log(` Config file: ${out.dim(config_1.CONFIG_FILE_PATH)}`);
|
|
191
191
|
console.log();
|
|
192
|
-
out.log(` ${'Key'.padEnd(22)} ${'
|
|
193
|
-
out.log(` ${'─'.repeat(22)} ${'─'.repeat(
|
|
194
|
-
for (const
|
|
195
|
-
|
|
192
|
+
out.log(` ${'Key'.padEnd(22)} ${'Value'.padEnd(30)} ${out.dim('Source')}`);
|
|
193
|
+
out.log(` ${'─'.repeat(22)} ${'─'.repeat(30)} ${'─'.repeat(15)}`);
|
|
194
|
+
for (const { key, configKey } of keys) {
|
|
195
|
+
const value = config[configKey];
|
|
196
|
+
const source = sources[configKey];
|
|
197
|
+
const isSecret = SECRET_KEYS.has(key);
|
|
198
|
+
const display = !value ? '(not set)' : isSecret ? '****' : value;
|
|
199
|
+
out.log(` ${key.padEnd(22)} ${display.padEnd(30)} ${out.dim(source)}`);
|
|
196
200
|
}
|
|
197
201
|
console.log();
|
|
198
202
|
}
|
package/dist/cli.js
CHANGED
|
@@ -132,6 +132,7 @@ async function main() {
|
|
|
132
132
|
name: args.name,
|
|
133
133
|
skipPreflight: args.skipPreflight,
|
|
134
134
|
agent: args.agent ?? 'claude',
|
|
135
|
+
dir: args.dir,
|
|
135
136
|
...overrides,
|
|
136
137
|
});
|
|
137
138
|
break;
|
|
@@ -142,6 +143,7 @@ async function main() {
|
|
|
142
143
|
name: args.name,
|
|
143
144
|
skipPreflight: args.skipPreflight,
|
|
144
145
|
agent: args.agent ?? 'claude',
|
|
146
|
+
dir: args.dir,
|
|
145
147
|
...overrides,
|
|
146
148
|
});
|
|
147
149
|
break;
|
package/dist/config.d.ts
CHANGED
|
@@ -70,6 +70,16 @@ export interface CliOverrides {
|
|
|
70
70
|
* CLI flag > env var > claude-tempo config file > temporal CLI config > defaults
|
|
71
71
|
*/
|
|
72
72
|
export declare function getConfig(overrides?: CliOverrides): Config;
|
|
73
|
+
export type ConfigSource = 'flag' | 'env' | 'config' | 'temporal-cli' | 'default' | 'none';
|
|
74
|
+
export interface ConfigWithSources {
|
|
75
|
+
config: Config;
|
|
76
|
+
sources: Record<string, ConfigSource>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Like getConfig(), but also returns which source each value came from.
|
|
80
|
+
* Used by `claude-tempo config show` to help users debug.
|
|
81
|
+
*/
|
|
82
|
+
export declare function getConfigWithSources(overrides?: CliOverrides): ConfigWithSources;
|
|
73
83
|
/** Build a workflow ID for a player session: claude-session-{ensemble}-{playerId} */
|
|
74
84
|
export declare function sessionWorkflowId(ensemble: string, playerId: string): string;
|
|
75
85
|
/** Build a workflow ID for a conductor: claude-session-{ensemble}-conductor */
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.saveConfigFile = saveConfigFile;
|
|
|
6
6
|
exports.loadTemporalCliConfig = loadTemporalCliConfig;
|
|
7
7
|
exports.parseTemporalYaml = parseTemporalYaml;
|
|
8
8
|
exports.getConfig = getConfig;
|
|
9
|
+
exports.getConfigWithSources = getConfigWithSources;
|
|
9
10
|
exports.sessionWorkflowId = sessionWorkflowId;
|
|
10
11
|
exports.conductorWorkflowId = conductorWorkflowId;
|
|
11
12
|
const fs_1 = require("fs");
|
|
@@ -182,6 +183,50 @@ function getConfig(overrides = {}) {
|
|
|
182
183
|
ensemble: process.env[exports.ENV.ENSEMBLE] ?? 'default',
|
|
183
184
|
};
|
|
184
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Like getConfig(), but also returns which source each value came from.
|
|
188
|
+
* Used by `claude-tempo config show` to help users debug.
|
|
189
|
+
*/
|
|
190
|
+
function getConfigWithSources(overrides = {}) {
|
|
191
|
+
const temporalCli = loadTemporalCliConfig();
|
|
192
|
+
const configFile = loadConfigFile();
|
|
193
|
+
function resolveWithSource(key, cliVal, envKey, fileVal, temporalCliVal, defaultVal) {
|
|
194
|
+
if (cliVal)
|
|
195
|
+
return { value: cliVal, source: 'flag' };
|
|
196
|
+
if (process.env[envKey])
|
|
197
|
+
return { value: process.env[envKey], source: 'env' };
|
|
198
|
+
if (fileVal)
|
|
199
|
+
return { value: fileVal, source: 'config' };
|
|
200
|
+
if (temporalCliVal)
|
|
201
|
+
return { value: temporalCliVal, source: 'temporal-cli' };
|
|
202
|
+
if (defaultVal)
|
|
203
|
+
return { value: defaultVal, source: 'default' };
|
|
204
|
+
return { value: undefined, source: 'none' };
|
|
205
|
+
}
|
|
206
|
+
const address = resolveWithSource('temporalAddress', overrides.temporalAddress, exports.ENV.TEMPORAL_ADDRESS, configFile.temporalAddress, temporalCli.temporalAddress, 'localhost:7233');
|
|
207
|
+
const namespace = resolveWithSource('temporalNamespace', overrides.temporalNamespace, exports.ENV.TEMPORAL_NAMESPACE, configFile.temporalNamespace, temporalCli.temporalNamespace, 'default');
|
|
208
|
+
const apiKey = resolveWithSource('temporalApiKey', overrides.temporalApiKey, exports.ENV.TEMPORAL_API_KEY, configFile.temporalApiKey, temporalCli.temporalApiKey);
|
|
209
|
+
const tlsCert = resolveWithSource('temporalTlsCertPath', overrides.temporalTlsCertPath, exports.ENV.TEMPORAL_TLS_CERT_PATH, configFile.temporalTlsCertPath, temporalCli.temporalTlsCertPath);
|
|
210
|
+
const tlsKey = resolveWithSource('temporalTlsKeyPath', overrides.temporalTlsKeyPath, exports.ENV.TEMPORAL_TLS_KEY_PATH, configFile.temporalTlsKeyPath, temporalCli.temporalTlsKeyPath);
|
|
211
|
+
return {
|
|
212
|
+
config: {
|
|
213
|
+
temporalAddress: address.value,
|
|
214
|
+
temporalNamespace: namespace.value,
|
|
215
|
+
temporalApiKey: apiKey.value,
|
|
216
|
+
temporalTlsCertPath: tlsCert.value,
|
|
217
|
+
temporalTlsKeyPath: tlsKey.value,
|
|
218
|
+
taskQueue: process.env[exports.ENV.TASK_QUEUE] ?? 'claude-tempo',
|
|
219
|
+
ensemble: process.env[exports.ENV.ENSEMBLE] ?? 'default',
|
|
220
|
+
},
|
|
221
|
+
sources: {
|
|
222
|
+
temporalAddress: address.source,
|
|
223
|
+
temporalNamespace: namespace.source,
|
|
224
|
+
temporalApiKey: apiKey.source,
|
|
225
|
+
temporalTlsCertPath: tlsCert.source,
|
|
226
|
+
temporalTlsKeyPath: tlsKey.source,
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
185
230
|
/** Build a workflow ID for a player session: claude-session-{ensemble}-{playerId} */
|
|
186
231
|
function sessionWorkflowId(ensemble, playerId) {
|
|
187
232
|
return `claude-session-${ensemble}-${playerId}`;
|
package/dist/server.js
CHANGED
|
@@ -113,6 +113,7 @@ async function main() {
|
|
|
113
113
|
const workflowId = isConductor
|
|
114
114
|
? (0, config_1.conductorWorkflowId)(config.ensemble)
|
|
115
115
|
: `claude-session-${config.ensemble}-${playerId}`;
|
|
116
|
+
const isBridgeMode = process.env[config_1.ENV.BRIDGE_MODE] === '1';
|
|
116
117
|
const sessionInput = {
|
|
117
118
|
metadata: {
|
|
118
119
|
playerId,
|
|
@@ -122,6 +123,7 @@ async function main() {
|
|
|
122
123
|
gitRoot,
|
|
123
124
|
gitBranch,
|
|
124
125
|
isConductor,
|
|
126
|
+
agentType: isBridgeMode ? 'copilot' : 'claude',
|
|
125
127
|
},
|
|
126
128
|
autoSummary: `Session in ${path.basename(workDir)}`,
|
|
127
129
|
};
|
|
@@ -177,14 +179,13 @@ async function main() {
|
|
|
177
179
|
(0, set_part_1.registerSetPartTool)(mcpServer, handle);
|
|
178
180
|
(0, set_name_1.registerSetNameTool)(mcpServer, client, config, handle, getPlayerId, setPlayerId);
|
|
179
181
|
(0, listen_1.registerListenTool)(mcpServer, handle);
|
|
180
|
-
(0, recruit_1.registerRecruitTool)(mcpServer, client, config, getPlayerId);
|
|
182
|
+
(0, recruit_1.registerRecruitTool)(mcpServer, client, config, getPlayerId, isBridgeMode ? 'copilot' : 'claude');
|
|
181
183
|
(0, report_1.registerReportTool)(mcpServer, client, config, getPlayerId);
|
|
182
184
|
(0, terminate_1.registerTerminateTool)(mcpServer, client, config, getPlayerId);
|
|
183
185
|
// Start message poller — push messages into Claude Code via channel notifications.
|
|
184
186
|
// Skip when running under the Copilot bridge: the bridge has its own poller that
|
|
185
187
|
// injects messages via sendAndWait. If both pollers run, this one wins the race and
|
|
186
188
|
// sends messages via notifications/claude/channel — which Copilot doesn't understand.
|
|
187
|
-
const isBridgeMode = process.env[config_1.ENV.BRIDGE_MODE] === '1';
|
|
188
189
|
const stopPoller = isBridgeMode
|
|
189
190
|
? () => { } // no-op — bridge handles message delivery
|
|
190
191
|
: (0, channel_1.startMessagePoller)(handle, async (messages) => {
|
package/dist/tools/ensemble.js
CHANGED
|
@@ -67,6 +67,7 @@ function registerEnsembleTool(server, client, config, getPlayerId, ownWorkflowId
|
|
|
67
67
|
gitRoot: metadata.gitRoot,
|
|
68
68
|
gitBranch: metadata.gitBranch,
|
|
69
69
|
isConductor: metadata.isConductor,
|
|
70
|
+
agentType: metadata.agentType || 'claude',
|
|
70
71
|
isYou: metadata.playerId === getPlayerId(),
|
|
71
72
|
});
|
|
72
73
|
}
|
|
@@ -90,6 +91,7 @@ function registerEnsembleTool(server, client, config, getPlayerId, ownWorkflowId
|
|
|
90
91
|
const tags = [
|
|
91
92
|
p.isYou ? '(you)' : '',
|
|
92
93
|
p.isConductor ? '(conductor)' : '',
|
|
94
|
+
p.agentType === 'copilot' ? '[copilot]' : '',
|
|
93
95
|
].filter(Boolean).join(' ');
|
|
94
96
|
return [
|
|
95
97
|
`**${p.playerId}** ${tags}`.trim(),
|
package/dist/tools/recruit.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { Client } from '@temporalio/client';
|
|
3
3
|
import { Config } from '../config';
|
|
4
|
-
|
|
4
|
+
import { AgentType } from '../types';
|
|
5
|
+
export declare function registerRecruitTool(server: McpServer, client: Client, config: Config, getPlayerId: () => string, ownAgentType?: AgentType): void;
|
package/dist/tools/recruit.js
CHANGED
|
@@ -10,16 +10,17 @@ const log = (...args) => console.error('[claude-tempo:recruit]', ...args);
|
|
|
10
10
|
function sleep(ms) {
|
|
11
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
}
|
|
13
|
-
function registerRecruitTool(server, client, config, getPlayerId) {
|
|
14
|
-
(0, helpers_1.defineTool)(server, 'recruit',
|
|
13
|
+
function registerRecruitTool(server, client, config, getPlayerId, ownAgentType = 'claude') {
|
|
14
|
+
(0, helpers_1.defineTool)(server, 'recruit', `Start a new named session in a directory. Rejects if the name is already active. Supports Claude Code or Copilot CLI agents. Defaults to "${ownAgentType}" (same as this session).`, {
|
|
15
15
|
workDir: zod_1.z.string().describe('The working directory for the new session'),
|
|
16
16
|
name: zod_1.z.string().describe('Name for the new session'),
|
|
17
17
|
initialMessage: zod_1.z.string().optional()
|
|
18
18
|
.describe('Optional task or message for the new session (sent after it sets its name)'),
|
|
19
|
-
agent: zod_1.z.enum(['claude', 'copilot']).
|
|
20
|
-
.describe(
|
|
19
|
+
agent: zod_1.z.enum(['claude', 'copilot']).optional()
|
|
20
|
+
.describe(`Which agent to use (default: "${ownAgentType}", same as this session)`),
|
|
21
21
|
}, async (args) => {
|
|
22
|
-
const { workDir, name, initialMessage
|
|
22
|
+
const { workDir, name, initialMessage } = args;
|
|
23
|
+
const agent = args.agent || ownAgentType;
|
|
23
24
|
// Validate name to prevent search attribute query injection
|
|
24
25
|
if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
|
|
25
26
|
return {
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-tempo",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.3",
|
|
4
4
|
"description": "MCP server for multi-session Claude Code coordination via Temporal",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"types": "./dist/workflows/signals.d.ts",
|
|
27
27
|
"default": "./dist/workflows/signals.js"
|
|
28
28
|
},
|
|
29
|
+
"./connection": {
|
|
30
|
+
"types": "./dist/connection.d.ts",
|
|
31
|
+
"default": "./dist/connection.js"
|
|
32
|
+
},
|
|
29
33
|
"./workflow-bundle": "./workflow-bundle.js",
|
|
30
34
|
"./package.json": "./package.json"
|
|
31
35
|
},
|
|
@@ -60,7 +64,8 @@
|
|
|
60
64
|
"types": ["dist/types.d.ts"],
|
|
61
65
|
"config": ["dist/config.d.ts"],
|
|
62
66
|
"spawn": ["dist/spawn.d.ts"],
|
|
63
|
-
"signals": ["dist/workflows/signals.d.ts"]
|
|
67
|
+
"signals": ["dist/workflows/signals.d.ts"],
|
|
68
|
+
"connection": ["dist/connection.d.ts"]
|
|
64
69
|
}
|
|
65
70
|
},
|
|
66
71
|
"files": [
|