claude-tempo 0.8.0 → 0.10.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.md +27 -4
- package/README.md +81 -18
- package/assets/icon-32.png +0 -0
- package/assets/icon-64.png +0 -0
- package/assets/icon-dark-32.png +0 -0
- package/assets/icon-dark-64.png +0 -0
- package/assets/icon-dark.svg +9 -0
- package/assets/icon.svg +9 -0
- package/assets/logo-dark.svg +11 -0
- package/assets/logo-light.svg +11 -0
- package/dist/activities/outbox.d.ts +65 -0
- package/dist/activities/outbox.js +219 -0
- package/dist/cli/commands.d.ts +6 -1
- package/dist/cli/commands.js +202 -40
- package/dist/cli.js +9 -3
- package/dist/config.d.ts +3 -0
- package/dist/config.js +6 -0
- package/dist/ensemble/agent-types.d.ts +23 -0
- package/dist/ensemble/agent-types.js +125 -0
- package/dist/ensemble/loader.d.ts +3 -3
- package/dist/ensemble/loader.js +20 -14
- package/dist/ensemble/saver.d.ts +6 -6
- package/dist/ensemble/saver.js +14 -12
- package/dist/ensemble/schema.d.ts +7 -1
- package/dist/ensemble/schema.js +1 -1
- package/dist/server.js +60 -19
- package/dist/spawn.d.ts +10 -0
- package/dist/spawn.js +120 -2
- package/dist/tools/agent-types.d.ts +2 -0
- package/dist/tools/agent-types.js +18 -0
- package/dist/tools/cue.d.ts +2 -2
- package/dist/tools/cue.js +11 -18
- package/dist/tools/ensemble.js +5 -1
- package/dist/tools/load-ensemble.js +22 -4
- package/dist/tools/load-lineup.d.ts +5 -0
- package/dist/tools/load-lineup.js +303 -0
- package/dist/tools/recruit.d.ts +2 -2
- package/dist/tools/recruit.js +46 -138
- package/dist/tools/report.d.ts +2 -3
- package/dist/tools/report.js +11 -19
- package/dist/tools/save-lineup.d.ts +4 -0
- package/dist/tools/save-lineup.js +43 -0
- package/dist/tools/stop.d.ts +2 -2
- package/dist/tools/stop.js +10 -17
- package/dist/tools/who-am-i.d.ts +3 -0
- package/dist/tools/who-am-i.js +24 -0
- package/dist/types.d.ts +70 -0
- package/dist/worker.d.ts +13 -0
- package/dist/worker.js +36 -3
- package/dist/workflows/session.js +123 -1
- package/dist/workflows/signals.d.ts +6 -2
- package/dist/workflows/signals.js +4 -1
- package/examples/agents/tempo-composer.md +44 -0
- package/examples/agents/tempo-conductor.md +52 -0
- package/examples/agents/tempo-critic.md +45 -0
- package/examples/agents/tempo-improv.md +47 -0
- package/examples/agents/tempo-liner.md +48 -0
- package/examples/agents/tempo-roadie.md +48 -0
- package/examples/agents/tempo-soloist.md +45 -0
- package/examples/agents/tempo-tuner.md +49 -0
- package/examples/ensembles/tempo-big-band.yaml +146 -0
- package/examples/ensembles/tempo-dev-team.yaml +58 -0
- package/examples/ensembles/tempo-jam-session.yaml +41 -0
- package/examples/ensembles/tempo-review-squad.yaml +32 -0
- package/package.json +3 -1
- package/workflow-bundle.js +128 -3
package/CLAUDE.md
CHANGED
|
@@ -21,16 +21,31 @@ src/
|
|
|
21
21
|
├── workflows/
|
|
22
22
|
│ ├── session.ts # claude-session workflow
|
|
23
23
|
│ └── signals.ts # Signal/query type definitions
|
|
24
|
+
├── activities/
|
|
25
|
+
│ ├── outbox.ts # Outbox delivery activities (cue, report, stop, recruit)
|
|
26
|
+
│ └── schedule-fire.ts # Schedule fire activity
|
|
27
|
+
├── ensemble/
|
|
28
|
+
│ ├── schema.ts # Lineup type definitions
|
|
29
|
+
│ ├── loader.ts # Load and validate YAML lineups
|
|
30
|
+
│ ├── saver.ts # Save live ensemble state to YAML
|
|
31
|
+
│ └── agent-types.ts # Agent type discovery, resolution, and lineup resolution
|
|
24
32
|
├── tools/
|
|
25
33
|
│ ├── ensemble.ts # Discover active sessions
|
|
26
|
-
│ ├── cue.ts # Send message to peer
|
|
34
|
+
│ ├── cue.ts # Send message to peer (via outbox)
|
|
27
35
|
│ ├── set-name.ts # Set session name
|
|
28
36
|
│ ├── set-part.ts # Update own summary
|
|
37
|
+
│ ├── who-am-i.ts # Query own identity, role, and session details
|
|
38
|
+
│ ├── agent-types.ts # Discover available player types (agent definitions)
|
|
29
39
|
│ ├── resolve.ts # Search-attribute session lookup
|
|
30
40
|
│ ├── listen.ts # Manual message check
|
|
31
|
-
│ ├── recruit.ts # Spawn new session
|
|
32
|
-
│ ├── report.ts # Report to conductor
|
|
33
|
-
│ ├── stop.ts # Stop a session
|
|
41
|
+
│ ├── recruit.ts # Spawn new session (via outbox), supports `type` param
|
|
42
|
+
│ ├── report.ts # Report to conductor (via outbox)
|
|
43
|
+
│ ├── stop.ts # Stop a session (via outbox)
|
|
44
|
+
│ ├── load-lineup.ts # Load an ensemble lineup, recruit players
|
|
45
|
+
│ ├── save-lineup.ts # Save current ensemble state as a lineup
|
|
46
|
+
│ ├── schedule.ts # Create one-shot or recurring schedules
|
|
47
|
+
│ ├── unschedule.ts # Cancel a named schedule
|
|
48
|
+
│ ├── schedules.ts # List active schedules
|
|
34
49
|
│ └── helpers.ts # Zod/MCP tool registration wrapper
|
|
35
50
|
├── types.ts # Shared type definitions
|
|
36
51
|
├── channel.ts # Claude channel notification helper
|
|
@@ -60,6 +75,10 @@ npm test
|
|
|
60
75
|
> **Important**: Always run `npm run build` after changing workflow code (`src/workflows/`).
|
|
61
76
|
> The build pre-bundles workflows into `workflow-bundle.js` so all workers use identical code.
|
|
62
77
|
|
|
78
|
+
> **Dual workers**: Each session runs two Temporal workers — a shared `claude-tempo` queue
|
|
79
|
+
> (workflows + delivery activities) and a per-host `claude-tempo-{hostname}` queue (spawn activities only).
|
|
80
|
+
> Both are created via `createWorkers()` in `src/worker.ts`.
|
|
81
|
+
|
|
63
82
|
## Key Concepts
|
|
64
83
|
|
|
65
84
|
- **Player**: A Claude Code session registered as a Temporal workflow
|
|
@@ -70,6 +89,10 @@ npm test
|
|
|
70
89
|
- **Recruit**: Spawning a new Claude Code session as a player. The workflow is pre-created with the initial message before the process spawns, ensuring reliable delivery.
|
|
71
90
|
- **set_name**: Players start with a random hex ID; `set_name` updates the `ClaudeTempoPlayerId` search attribute to a human-readable name
|
|
72
91
|
- **Session status**: Each session has a status (`pending` → `active` → `stale`) tracked via `ClaudeTempoStatus` search attribute. Pre-created workflows start as `pending`, transition to `active` when the process connects, and become `stale` if messages go undelivered for 3+ minutes.
|
|
92
|
+
- **Outbox**: Outbound requests (cue, report, stop, recruit) go through the session's own workflow outbox instead of directly signaling other workflows. The workflow's dispatch loop processes entries via activities, decoupling tools from cross-workflow signaling.
|
|
93
|
+
- **Per-host task queues**: Each host runs a `claude-tempo-{hostname}` activity worker for local-only operations (e.g., `spawnProcess`). This enables cross-machine recruiting — the `recruit` tool accepts an optional `host` parameter to route the spawn to a remote machine's task queue.
|
|
94
|
+
- **Player types**: Reusable agent definitions in Claude Code's standard subagent format (`.md` files with YAML frontmatter). Ensemble lineups can reference types by name via a `type` field on players. Three-tier lookup: project `.claude/agents/` → user `~/.claude/agents/` → shipped `examples/agents/`. Players know their type via workflow metadata and the `who_am_i` tool.
|
|
95
|
+
- **Agent type discovery**: The `agent_types` MCP tool and `claude-tempo agent-types` CLI command let conductors discover available player types. Shipped examples (tempo-conductor, tempo-composer, tempo-soloist, tempo-tuner, tempo-critic, tempo-roadie, tempo-improv, tempo-liner) work out of the box. Ensemble lineups: tempo-big-band (full lifecycle), tempo-dev-team (feature work), tempo-review-squad (parallel review), tempo-jam-session (exploration).
|
|
73
96
|
|
|
74
97
|
## Dashboard
|
|
75
98
|
|
package/README.md
CHANGED
|
@@ -98,7 +98,7 @@ claude-tempo <command> [options]
|
|
|
98
98
|
|
|
99
99
|
| Command | Description |
|
|
100
100
|
|---------|-------------|
|
|
101
|
-
| `up [ensemble]` | First-time setup: start Temporal, configure MCP, launch conductor. Use `--
|
|
101
|
+
| `up [ensemble]` | First-time setup: start Temporal, configure MCP, launch conductor. Use `--lineup` to load a lineup. |
|
|
102
102
|
| `down` | Stop Temporal, terminate sessions, remove MCP config |
|
|
103
103
|
| `server` | Start the Temporal dev server and register search attributes |
|
|
104
104
|
| `conduct [ensemble]` | Start a conductor session (one per ensemble). Use `--resume` or `--replace` if one exists. |
|
|
@@ -108,7 +108,8 @@ claude-tempo <command> [options]
|
|
|
108
108
|
| `stop [ensemble]` | Stop sessions (`-n <name>` for one, `--all` for everything) |
|
|
109
109
|
| `init` | Register claude-tempo MCP server globally (`--project` for per-directory) |
|
|
110
110
|
| `preflight` | Run environment checks |
|
|
111
|
-
| `ensemble <sub>` | Manage saved
|
|
111
|
+
| `ensemble <sub>` | Manage saved lineups (`save`, `list`, `show`) |
|
|
112
|
+
| `agent-types <sub>` | Manage player types (`list`, `show <name>`, `init`) |
|
|
112
113
|
| `help` | Show usage info |
|
|
113
114
|
|
|
114
115
|
### Global options
|
|
@@ -123,7 +124,7 @@ claude-tempo <command> [options]
|
|
|
123
124
|
--skip-preflight Skip preflight checks (start/conduct)
|
|
124
125
|
-d, --dir <path> Target directory (default: cwd)
|
|
125
126
|
--background Run Temporal in background (server only)
|
|
126
|
-
--
|
|
127
|
+
--lineup <name|file> Load an ensemble lineup by name or file path (up only)
|
|
127
128
|
--resume Resume an existing conductor session (conduct only)
|
|
128
129
|
--replace Stop existing conductor and start fresh (conduct only)
|
|
129
130
|
```
|
|
@@ -221,8 +222,10 @@ These tools are available inside Claude Code sessions connected to claude-tempo:
|
|
|
221
222
|
| `schedule` | Create a one-shot or recurring schedule to cue a player. |
|
|
222
223
|
| `unschedule` | Cancel a named schedule. |
|
|
223
224
|
| `schedules` | List all active schedules. |
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
225
|
+
| `who_am_i` | Get your identity, role, player type, and session details. |
|
|
226
|
+
| `agent_types` | List available player types with name, description, and source. |
|
|
227
|
+
| `save_lineup` | Save the current ensemble as a YAML lineup (conductor only). |
|
|
228
|
+
| `load_lineup` | Load a lineup to recruit players and create schedules. |
|
|
226
229
|
|
|
227
230
|
## Scheduling
|
|
228
231
|
|
|
@@ -255,11 +258,11 @@ Schedules support one-shot delays, fixed times, and recurring intervals with opt
|
|
|
255
258
|
- `claude-tempo status` shows active schedules alongside sessions
|
|
256
259
|
- A single durable scheduler workflow per ensemble manages all schedules using Temporal timers
|
|
257
260
|
|
|
258
|
-
## Ensemble
|
|
261
|
+
## Ensemble Lineups
|
|
259
262
|
|
|
260
|
-
Define reusable ensemble configurations as YAML files. A
|
|
263
|
+
Define reusable ensemble configurations as YAML files. A lineup specifies which players to recruit, what instructions to give them, what schedules to create, and optionally which custom agent files to use.
|
|
261
264
|
|
|
262
|
-
### Example
|
|
265
|
+
### Example lineup
|
|
263
266
|
|
|
264
267
|
```yaml
|
|
265
268
|
name: my-project
|
|
@@ -287,29 +290,29 @@ schedules:
|
|
|
287
290
|
delay: 10m
|
|
288
291
|
```
|
|
289
292
|
|
|
290
|
-
### Three ways to use
|
|
293
|
+
### Three ways to use lineups
|
|
291
294
|
|
|
292
|
-
1. **From the CLI** — load a
|
|
295
|
+
1. **From the CLI** — load a lineup when starting an ensemble:
|
|
293
296
|
|
|
294
297
|
```bash
|
|
295
|
-
claude-tempo up --
|
|
298
|
+
claude-tempo up --lineup my-lineup.yaml
|
|
296
299
|
```
|
|
297
300
|
|
|
298
|
-
2. **From inside a session** — use the `
|
|
301
|
+
2. **From inside a session** — use the `load_lineup` tool:
|
|
299
302
|
|
|
300
|
-
*"Load the
|
|
303
|
+
*"Load the lineup from ~/.claude-tempo/ensembles/my-project.yaml"*
|
|
301
304
|
|
|
302
|
-
3. **Save the current state** — snapshot a running ensemble as a
|
|
305
|
+
3. **Save the current state** — snapshot a running ensemble as a lineup (conductor only):
|
|
303
306
|
|
|
304
|
-
*"Save this ensemble as a
|
|
307
|
+
*"Save this ensemble as a lineup called my-project"*
|
|
305
308
|
|
|
306
309
|
### Natural language examples
|
|
307
310
|
|
|
308
311
|
Tell your session things like:
|
|
309
312
|
|
|
310
|
-
- *"Load the my-project
|
|
311
|
-
- *"Save this ensemble as a
|
|
312
|
-
- *"Load the
|
|
313
|
+
- *"Load the my-project lineup"*
|
|
314
|
+
- *"Save this ensemble as a lineup"*
|
|
315
|
+
- *"Load the lineup from /repos/configs/team.yaml"*
|
|
313
316
|
|
|
314
317
|
### Fan-out schedules
|
|
315
318
|
|
|
@@ -329,6 +332,66 @@ players:
|
|
|
329
332
|
instructions: "Review the latest PR for security issues"
|
|
330
333
|
```
|
|
331
334
|
|
|
335
|
+
## Player Types
|
|
336
|
+
|
|
337
|
+
Player types are reusable agent definitions in Claude Code's standard subagent format — `.md` files with YAML frontmatter specifying name, description, and optional model. They let you define specialized roles once and reuse them across lineups.
|
|
338
|
+
|
|
339
|
+
### How player types work
|
|
340
|
+
|
|
341
|
+
Reference a type by name in a lineup's `type` field:
|
|
342
|
+
|
|
343
|
+
```yaml
|
|
344
|
+
players:
|
|
345
|
+
- name: arch
|
|
346
|
+
type: tempo-composer
|
|
347
|
+
- name: eng
|
|
348
|
+
type: tempo-soloist
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
When a player is recruited with a type, the agent definition is resolved and passed to the session. Players know their type via the `who_am_i` tool.
|
|
352
|
+
|
|
353
|
+
### Three-tier lookup
|
|
354
|
+
|
|
355
|
+
Player types are resolved in order (first match wins):
|
|
356
|
+
|
|
357
|
+
1. **Project** — `.claude/agents/` in the project directory
|
|
358
|
+
2. **User** — `~/.claude/agents/` in the user's home directory
|
|
359
|
+
3. **Shipped** — `examples/agents/` bundled with claude-tempo
|
|
360
|
+
|
|
361
|
+
Project and user types are resolved natively by Claude Code via `--agent <name>`. Shipped types fall back to `--system-prompt <path>`.
|
|
362
|
+
|
|
363
|
+
### Shipped player types
|
|
364
|
+
|
|
365
|
+
| Type | Description |
|
|
366
|
+
|------|-------------|
|
|
367
|
+
| `tempo-conductor` | Orchestrates the ensemble — breaks down tasks, delegates to players, tracks progress |
|
|
368
|
+
| `tempo-composer` | Software architect — designs system structure, defines interfaces, makes technology decisions |
|
|
369
|
+
| `tempo-soloist` | Senior engineer — implements features, fixes bugs, writes tests, delivers working code |
|
|
370
|
+
| `tempo-tuner` | QA engineer — designs test strategies, finds bugs, validates edge cases |
|
|
371
|
+
| `tempo-critic` | Code reviewer — evaluates changes for correctness, security, performance, maintainability |
|
|
372
|
+
| `tempo-roadie` | DevOps engineer — manages CI/CD, deployments, infrastructure, environment configuration |
|
|
373
|
+
| `tempo-improv` | Researcher and explorer — investigates unknowns, runs spikes, evaluates options |
|
|
374
|
+
| `tempo-liner` | Documentation specialist — owns README, CHANGELOG, CLAUDE.md, and PR descriptions |
|
|
375
|
+
|
|
376
|
+
### Shipped lineups
|
|
377
|
+
|
|
378
|
+
| Lineup | Description |
|
|
379
|
+
|--------|-------------|
|
|
380
|
+
| `tempo-big-band` | Full-lifecycle ensemble with all 8 player types — design, implement, test, review, and ship |
|
|
381
|
+
| `tempo-dev-team` | Feature development — conductor, composer, two soloists, and a tuner |
|
|
382
|
+
| `tempo-review-squad` | Three critics with different focus areas for thorough parallel code review |
|
|
383
|
+
| `tempo-jam-session` | Exploratory ensemble for spikes, research, and problems where the path is unclear |
|
|
384
|
+
|
|
385
|
+
### Discovery
|
|
386
|
+
|
|
387
|
+
Use the `agent_types` MCP tool inside a session or the CLI:
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
claude-tempo agent-types list # show available types
|
|
391
|
+
claude-tempo agent-types show <name> # print full definition
|
|
392
|
+
claude-tempo agent-types init # copy shipped examples to ~/.claude/agents/
|
|
393
|
+
```
|
|
394
|
+
|
|
332
395
|
## Conductors
|
|
333
396
|
|
|
334
397
|
A **conductor** is an optional special player that acts as an orchestration hub. Use one when you want:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
2
|
+
<!-- Ultra-minimal metronome icon: triangle + pendulum (dark mode) -->
|
|
3
|
+
<!-- Metronome body — single-stroke triangle -->
|
|
4
|
+
<path d="M32 8 L14 54 L50 54 Z" stroke="#FAF3EE" stroke-width="3" fill="none" stroke-linejoin="round"/>
|
|
5
|
+
<!-- Pendulum arm (angled right) -->
|
|
6
|
+
<line x1="32" y1="46" x2="44" y2="14" stroke="#E07A5F" stroke-width="3" stroke-linecap="round"/>
|
|
7
|
+
<!-- Pivot dot -->
|
|
8
|
+
<circle cx="32" cy="46" r="3" fill="#E07A5F"/>
|
|
9
|
+
</svg>
|
package/assets/icon.svg
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
|
2
|
+
<!-- Ultra-minimal metronome icon: triangle + pendulum -->
|
|
3
|
+
<!-- Metronome body — single-stroke triangle -->
|
|
4
|
+
<path d="M32 8 L14 54 L50 54 Z" stroke="#1B2838" stroke-width="3" fill="none" stroke-linejoin="round"/>
|
|
5
|
+
<!-- Pendulum arm (angled right) -->
|
|
6
|
+
<line x1="32" y1="46" x2="44" y2="14" stroke="#E07A5F" stroke-width="3" stroke-linecap="round"/>
|
|
7
|
+
<!-- Pivot dot -->
|
|
8
|
+
<circle cx="32" cy="46" r="3" fill="#E07A5F"/>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 140" fill="none">
|
|
2
|
+
<!-- Ultra-minimal metronome: triangle outline + pendulum line (dark mode) -->
|
|
3
|
+
<!-- Metronome body — single-stroke triangle -->
|
|
4
|
+
<path d="M160 18 L122 100 L198 100 Z" stroke="#FAF3EE" stroke-width="3" fill="none" stroke-linejoin="round"/>
|
|
5
|
+
<!-- Pendulum arm (angled right ~18deg) -->
|
|
6
|
+
<line x1="160" y1="88" x2="182" y2="24" stroke="#E07A5F" stroke-width="3" stroke-linecap="round"/>
|
|
7
|
+
<!-- Pivot dot -->
|
|
8
|
+
<circle cx="160" cy="88" r="3.5" fill="#E07A5F"/>
|
|
9
|
+
<!-- Text -->
|
|
10
|
+
<text x="160" y="132" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Consolas',monospace" font-size="18" font-weight="600" fill="#FAF3EE" letter-spacing="-0.5">claude-tempo</text>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 140" fill="none">
|
|
2
|
+
<!-- Ultra-minimal metronome: triangle outline + pendulum line -->
|
|
3
|
+
<!-- Metronome body — single-stroke triangle -->
|
|
4
|
+
<path d="M160 18 L122 100 L198 100 Z" stroke="#1B2838" stroke-width="3" fill="none" stroke-linejoin="round"/>
|
|
5
|
+
<!-- Pendulum arm (angled right ~18deg) -->
|
|
6
|
+
<line x1="160" y1="88" x2="182" y2="24" stroke="#E07A5F" stroke-width="3" stroke-linecap="round"/>
|
|
7
|
+
<!-- Pivot dot -->
|
|
8
|
+
<circle cx="160" cy="88" r="3.5" fill="#E07A5F"/>
|
|
9
|
+
<!-- Text -->
|
|
10
|
+
<text x="160" y="132" text-anchor="middle" font-family="'JetBrains Mono','SF Mono','Consolas',monospace" font-size="18" font-weight="600" fill="#1B2838" letter-spacing="-0.5">claude-tempo</text>
|
|
11
|
+
</svg>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Client } from '@temporalio/client';
|
|
2
|
+
import { Config } from '../config';
|
|
3
|
+
import { AgentType } from '../types';
|
|
4
|
+
export interface DeliverCueInput {
|
|
5
|
+
ensemble: string;
|
|
6
|
+
fromPlayerId: string;
|
|
7
|
+
targetPlayerId: string;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DeliverReportInput {
|
|
11
|
+
ensemble: string;
|
|
12
|
+
fromPlayerId: string;
|
|
13
|
+
text: string;
|
|
14
|
+
reportType: 'result' | 'blocker' | 'question';
|
|
15
|
+
}
|
|
16
|
+
export interface TerminateSessionInput {
|
|
17
|
+
ensemble: string;
|
|
18
|
+
targetPlayerId: string;
|
|
19
|
+
terminatedBy: string;
|
|
20
|
+
}
|
|
21
|
+
export interface StartRecruitedSessionInput {
|
|
22
|
+
ensemble: string;
|
|
23
|
+
targetName: string;
|
|
24
|
+
workDir: string;
|
|
25
|
+
isConductor: boolean;
|
|
26
|
+
initialMessage?: string;
|
|
27
|
+
fromPlayerId: string;
|
|
28
|
+
agent: AgentType;
|
|
29
|
+
systemPrompt?: string;
|
|
30
|
+
taskQueue: string;
|
|
31
|
+
agentDefinition?: string;
|
|
32
|
+
agentDefinitionDescription?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface SpawnProcessInput {
|
|
35
|
+
targetName: string;
|
|
36
|
+
workDir: string;
|
|
37
|
+
isConductor: boolean;
|
|
38
|
+
agent: AgentType;
|
|
39
|
+
systemPrompt?: string;
|
|
40
|
+
ensemble: string;
|
|
41
|
+
temporalAddress: string;
|
|
42
|
+
temporalNamespace: string;
|
|
43
|
+
temporalApiKey?: string;
|
|
44
|
+
temporalTlsCertPath?: string;
|
|
45
|
+
temporalTlsKeyPath?: string;
|
|
46
|
+
agentDefinition?: string;
|
|
47
|
+
agentDefinitionPath?: string;
|
|
48
|
+
nativeResolvable?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface OutboxActivityResult {
|
|
51
|
+
success: boolean;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface OutboxActivities {
|
|
55
|
+
deliverCue(input: DeliverCueInput): Promise<OutboxActivityResult>;
|
|
56
|
+
deliverReport(input: DeliverReportInput): Promise<OutboxActivityResult>;
|
|
57
|
+
terminateSession(input: TerminateSessionInput): Promise<OutboxActivityResult>;
|
|
58
|
+
startRecruitedSession(input: StartRecruitedSessionInput): Promise<OutboxActivityResult>;
|
|
59
|
+
spawnProcess(input: SpawnProcessInput): Promise<OutboxActivityResult>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create outbox delivery activities bound to a Temporal client and config.
|
|
63
|
+
* The returned object is registered with the worker as activities.
|
|
64
|
+
*/
|
|
65
|
+
export declare function createOutboxActivities(client: Client, config: Config): OutboxActivities;
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.createOutboxActivities = createOutboxActivities;
|
|
37
|
+
const client_1 = require("@temporalio/client");
|
|
38
|
+
const activity_1 = require("@temporalio/activity");
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const config_1 = require("../config");
|
|
41
|
+
const git_info_1 = require("../git-info");
|
|
42
|
+
const spawn_1 = require("../spawn");
|
|
43
|
+
const config_2 = require("../config");
|
|
44
|
+
const log = (...args) => console.error('[claude-tempo:outbox]', ...args);
|
|
45
|
+
// ── Helper: resolve session by player name ──
|
|
46
|
+
async function resolveSession(client, ensemble, playerName) {
|
|
47
|
+
const query = `WorkflowType = "claudeSessionWorkflow" AND ExecutionStatus = "Running"`;
|
|
48
|
+
for await (const wf of client.workflow.list({ query })) {
|
|
49
|
+
try {
|
|
50
|
+
const handle = client.workflow.getHandle(wf.workflowId);
|
|
51
|
+
const metadata = await handle.query('getMetadata');
|
|
52
|
+
if (metadata.ensemble === ensemble && metadata.playerId === playerName) {
|
|
53
|
+
return handle;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Workflow may have just completed — skip
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create outbox delivery activities bound to a Temporal client and config.
|
|
64
|
+
* The returned object is registered with the worker as activities.
|
|
65
|
+
*/
|
|
66
|
+
function createOutboxActivities(client, config) {
|
|
67
|
+
return {
|
|
68
|
+
async deliverCue(input) {
|
|
69
|
+
const { ensemble, fromPlayerId, targetPlayerId, message } = input;
|
|
70
|
+
const handle = await resolveSession(client, ensemble, targetPlayerId);
|
|
71
|
+
if (!handle) {
|
|
72
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
|
|
73
|
+
}
|
|
74
|
+
await handle.signal('receiveMessage', { from: fromPlayerId, text: message });
|
|
75
|
+
return { success: true };
|
|
76
|
+
},
|
|
77
|
+
async deliverReport(input) {
|
|
78
|
+
const { ensemble, fromPlayerId, text, reportType } = input;
|
|
79
|
+
const conductorId = (0, config_1.conductorWorkflowId)(ensemble);
|
|
80
|
+
const handle = client.workflow.getHandle(conductorId);
|
|
81
|
+
await handle.signal('playerReport', { playerId: fromPlayerId, text, type: reportType });
|
|
82
|
+
return { success: true };
|
|
83
|
+
},
|
|
84
|
+
async terminateSession(input) {
|
|
85
|
+
const { ensemble, targetPlayerId, terminatedBy } = input;
|
|
86
|
+
const handle = await resolveSession(client, ensemble, targetPlayerId);
|
|
87
|
+
if (!handle) {
|
|
88
|
+
throw activity_1.ApplicationFailure.nonRetryable(`No active session found for "${targetPlayerId}"`);
|
|
89
|
+
}
|
|
90
|
+
// Signal target to mark as terminated
|
|
91
|
+
await handle.signal('updateMetadata', { status: 'terminated', terminatedBy });
|
|
92
|
+
// Notify conductor about the termination (best effort)
|
|
93
|
+
try {
|
|
94
|
+
const conductorId = (0, config_1.conductorWorkflowId)(ensemble);
|
|
95
|
+
const conductorHandle = client.workflow.getHandle(conductorId);
|
|
96
|
+
await conductorHandle.signal('receiveMessage', {
|
|
97
|
+
from: 'system',
|
|
98
|
+
text: `Session "${targetPlayerId}" was terminated by ${terminatedBy}.`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Conductor may not exist — that's fine
|
|
103
|
+
}
|
|
104
|
+
return { success: true };
|
|
105
|
+
},
|
|
106
|
+
async startRecruitedSession(input) {
|
|
107
|
+
const { ensemble, targetName, workDir, isConductor, initialMessage, fromPlayerId, agent, systemPrompt, taskQueue, agentDefinition, agentDefinitionDescription } = input;
|
|
108
|
+
try {
|
|
109
|
+
const workflowId = isConductor
|
|
110
|
+
? (0, config_1.conductorWorkflowId)(ensemble)
|
|
111
|
+
: (0, config_1.sessionWorkflowId)(ensemble, targetName);
|
|
112
|
+
const { gitRoot, gitBranch } = (0, git_info_1.getGitInfo)(workDir);
|
|
113
|
+
const sessionInput = {
|
|
114
|
+
metadata: {
|
|
115
|
+
playerId: targetName,
|
|
116
|
+
ensemble,
|
|
117
|
+
hostname: os.hostname(),
|
|
118
|
+
workDir,
|
|
119
|
+
gitRoot,
|
|
120
|
+
gitBranch,
|
|
121
|
+
isConductor,
|
|
122
|
+
agentType: agent,
|
|
123
|
+
status: 'pending',
|
|
124
|
+
...(agentDefinition ? { playerType: agentDefinition } : {}),
|
|
125
|
+
...(agentDefinitionDescription ? { playerTypeDescription: agentDefinitionDescription } : {}),
|
|
126
|
+
recruitedBy: fromPlayerId,
|
|
127
|
+
},
|
|
128
|
+
autoSummary: `Session in ${require('path').basename(workDir)}`,
|
|
129
|
+
disableStaleDetection: true,
|
|
130
|
+
...(initialMessage ? {
|
|
131
|
+
messages: [{
|
|
132
|
+
id: require('crypto').randomUUID(),
|
|
133
|
+
from: fromPlayerId,
|
|
134
|
+
text: initialMessage,
|
|
135
|
+
timestamp: new Date().toISOString(),
|
|
136
|
+
delivered: false,
|
|
137
|
+
}],
|
|
138
|
+
} : {}),
|
|
139
|
+
};
|
|
140
|
+
await client.workflow.start('claudeSessionWorkflow', {
|
|
141
|
+
workflowId,
|
|
142
|
+
taskQueue,
|
|
143
|
+
args: [sessionInput],
|
|
144
|
+
workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
|
|
145
|
+
searchAttributes: {
|
|
146
|
+
...(gitRoot ? { ClaudeTempoGitRoot: [gitRoot] } : {}),
|
|
147
|
+
ClaudeTempoHostname: [os.hostname()],
|
|
148
|
+
ClaudeTempoEnsemble: [ensemble],
|
|
149
|
+
ClaudeTempoPlayerId: [targetName],
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
log(`Pre-created workflow ${workflowId} for recruit "${targetName}"`);
|
|
153
|
+
return { success: true };
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw activity_1.ApplicationFailure.nonRetryable(`Failed to start recruited session "${targetName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
async spawnProcess(input) {
|
|
160
|
+
const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, temporalApiKey, temporalTlsCertPath, temporalTlsKeyPath, agentDefinition, agentDefinitionPath, nativeResolvable } = input;
|
|
161
|
+
try {
|
|
162
|
+
if (agent === 'copilot') {
|
|
163
|
+
const { pid } = (0, spawn_1.spawnCopilotBridge)({
|
|
164
|
+
name: targetName,
|
|
165
|
+
ensemble,
|
|
166
|
+
temporalAddress,
|
|
167
|
+
temporalNamespace,
|
|
168
|
+
temporalApiKey,
|
|
169
|
+
temporalTlsCertPath,
|
|
170
|
+
temporalTlsKeyPath,
|
|
171
|
+
isConductor,
|
|
172
|
+
workDir,
|
|
173
|
+
});
|
|
174
|
+
log(`Spawned copilot-bridge (pid ${pid}) in ${workDir} as "${targetName}"`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
// Resolve agent flags: --agent (native) > --system-prompt (shipped/legacy)
|
|
178
|
+
let agentFlags = [];
|
|
179
|
+
if (agentDefinition && nativeResolvable) {
|
|
180
|
+
agentFlags = ['--agent', agentDefinition];
|
|
181
|
+
}
|
|
182
|
+
else if (agentDefinitionPath) {
|
|
183
|
+
agentFlags = ['--system-prompt', agentDefinitionPath];
|
|
184
|
+
}
|
|
185
|
+
else if (systemPrompt) {
|
|
186
|
+
agentFlags = ['--system-prompt', systemPrompt];
|
|
187
|
+
}
|
|
188
|
+
const spawnArgs = [
|
|
189
|
+
'--dangerously-skip-permissions',
|
|
190
|
+
'--dangerously-load-development-channels', 'server:claude-tempo',
|
|
191
|
+
'-n', targetName,
|
|
192
|
+
...agentFlags,
|
|
193
|
+
];
|
|
194
|
+
const envVars = {
|
|
195
|
+
[config_2.ENV.ENSEMBLE]: ensemble,
|
|
196
|
+
[config_2.ENV.CONDUCTOR]: isConductor ? 'true' : '',
|
|
197
|
+
[config_2.ENV.PLAYER_NAME]: targetName,
|
|
198
|
+
[config_2.ENV.TEMPORAL_ADDRESS]: temporalAddress,
|
|
199
|
+
[config_2.ENV.TEMPORAL_NAMESPACE]: temporalNamespace,
|
|
200
|
+
};
|
|
201
|
+
if (agentDefinition)
|
|
202
|
+
envVars[config_2.ENV.PLAYER_TYPE] = agentDefinition;
|
|
203
|
+
if (temporalApiKey)
|
|
204
|
+
envVars[config_2.ENV.TEMPORAL_API_KEY] = temporalApiKey;
|
|
205
|
+
if (temporalTlsCertPath)
|
|
206
|
+
envVars[config_2.ENV.TEMPORAL_TLS_CERT_PATH] = temporalTlsCertPath;
|
|
207
|
+
if (temporalTlsKeyPath)
|
|
208
|
+
envVars[config_2.ENV.TEMPORAL_TLS_KEY_PATH] = temporalTlsKeyPath;
|
|
209
|
+
const { pid } = (0, spawn_1.spawnInTerminal)(spawnArgs, workDir, envVars);
|
|
210
|
+
log(`Spawned claude process (pid ${pid}) in ${workDir} as "${targetName}"`);
|
|
211
|
+
}
|
|
212
|
+
return { success: true };
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
throw activity_1.ApplicationFailure.nonRetryable(`Failed to spawn process for "${targetName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export declare function server(opts: ServerOpts): Promise<void>;
|
|
|
27
27
|
interface UpOpts extends CliOverrides {
|
|
28
28
|
ensemble: string;
|
|
29
29
|
name?: string;
|
|
30
|
-
|
|
30
|
+
lineup?: string;
|
|
31
31
|
agent: AgentType;
|
|
32
32
|
}
|
|
33
33
|
export declare function up(opts: UpOpts): Promise<void>;
|
|
@@ -45,6 +45,11 @@ interface StopOpts extends CliOverrides {
|
|
|
45
45
|
all?: boolean;
|
|
46
46
|
}
|
|
47
47
|
export declare function stop(opts: StopOpts): Promise<void>;
|
|
48
|
+
interface AgentTypesCommandOpts {
|
|
49
|
+
subcommand?: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
}
|
|
52
|
+
export declare function agentTypesCommand(opts: AgentTypesCommandOpts): Promise<void>;
|
|
48
53
|
interface EnsembleCommandOpts extends CliOverrides {
|
|
49
54
|
subcommand?: string;
|
|
50
55
|
name?: string;
|