claude-tempo 0.22.0-beta.2 → 0.22.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/dist/activities/outbox.d.ts +6 -6
- package/dist/activities/outbox.js +10 -9
- package/dist/cli/commands.js +39 -9
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/copilot-bridge.d.ts +1 -0
- package/dist/copilot-bridge.js +63 -9
- package/dist/spawn.d.ts +2 -0
- package/dist/spawn.js +1 -0
- package/dist/tui/client.js +5 -1
- package/dist/types.d.ts +2 -2
- package/dist/workflows/session.js +5 -4
- package/dist/workflows/signals.d.ts +1 -1
- package/package.json +1 -1
- package/workflow-bundle.js +6 -5
|
@@ -48,8 +48,8 @@ export interface SpawnProcessInput {
|
|
|
48
48
|
nativeResolvable?: boolean;
|
|
49
49
|
/** When true, use --resume instead of -n (reconnect to existing session). */
|
|
50
50
|
resume?: boolean;
|
|
51
|
-
/**
|
|
52
|
-
|
|
51
|
+
/** Session UUID — used for Copilot SDK sessionId and Claude Code --resume/--session-id. */
|
|
52
|
+
sessionId?: string;
|
|
53
53
|
/** Tool restrictions from the agent definition frontmatter. */
|
|
54
54
|
allowedTools?: string[];
|
|
55
55
|
/** Custom claude binary path (from config.claudeBin). */
|
|
@@ -70,8 +70,8 @@ export interface EncoreResult {
|
|
|
70
70
|
agentDefinitionPath?: string;
|
|
71
71
|
nativeResolvable?: boolean;
|
|
72
72
|
allowedTools?: string[];
|
|
73
|
-
/**
|
|
74
|
-
|
|
73
|
+
/** Session UUID — used for Copilot SDK sessionId and Claude Code --resume/--session-id. */
|
|
74
|
+
sessionId?: string;
|
|
75
75
|
temporalAddress: string;
|
|
76
76
|
temporalNamespace: string;
|
|
77
77
|
/** Custom claude binary path (from config.claudeBin). */
|
|
@@ -82,8 +82,8 @@ export interface OutboxActivityResult {
|
|
|
82
82
|
error?: string;
|
|
83
83
|
}
|
|
84
84
|
export interface RecruitResult extends OutboxActivityResult {
|
|
85
|
-
/**
|
|
86
|
-
|
|
85
|
+
/** Session UUID assigned at recruit time. */
|
|
86
|
+
sessionId?: string;
|
|
87
87
|
}
|
|
88
88
|
export interface OutboxActivities {
|
|
89
89
|
deliverCue(input: DeliverCueInput): Promise<OutboxActivityResult>;
|
|
@@ -107,8 +107,8 @@ function createOutboxActivities(client, config) {
|
|
|
107
107
|
? (0, config_1.conductorWorkflowId)(ensemble)
|
|
108
108
|
: (0, config_1.sessionWorkflowId)(ensemble, targetName);
|
|
109
109
|
const { gitRoot, gitBranch } = (0, git_info_1.getGitInfo)(workDir);
|
|
110
|
-
// Generate a UUID for the
|
|
111
|
-
const
|
|
110
|
+
// Generate a UUID for the session — used for deterministic --resume on encore
|
|
111
|
+
const sessionId = crypto.randomUUID();
|
|
112
112
|
const sessionInput = {
|
|
113
113
|
metadata: {
|
|
114
114
|
playerId: targetName,
|
|
@@ -120,7 +120,7 @@ function createOutboxActivities(client, config) {
|
|
|
120
120
|
isConductor,
|
|
121
121
|
agentType: agent,
|
|
122
122
|
status: 'pending',
|
|
123
|
-
|
|
123
|
+
sessionId,
|
|
124
124
|
...(agentDefinition ? { playerType: agentDefinition } : {}),
|
|
125
125
|
...(agentDefinitionDescription ? { playerTypeDescription: agentDefinitionDescription } : {}),
|
|
126
126
|
recruitedBy: fromPlayerId,
|
|
@@ -149,15 +149,15 @@ function createOutboxActivities(client, config) {
|
|
|
149
149
|
ClaudeTempoPlayerId: [targetName],
|
|
150
150
|
},
|
|
151
151
|
});
|
|
152
|
-
log(`Pre-created workflow ${workflowId} for recruit "${targetName}" (sessionId=${
|
|
153
|
-
return { success: true,
|
|
152
|
+
log(`Pre-created workflow ${workflowId} for recruit "${targetName}" (sessionId=${sessionId})`);
|
|
153
|
+
return { success: true, sessionId };
|
|
154
154
|
}
|
|
155
155
|
catch (err) {
|
|
156
156
|
throw activity_1.ApplicationFailure.nonRetryable(`Failed to start recruited session "${targetName}": ${err instanceof Error ? err.message : String(err)}`);
|
|
157
157
|
}
|
|
158
158
|
},
|
|
159
159
|
async spawnProcess(input) {
|
|
160
|
-
const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, agentDefinition, agentDefinitionPath, nativeResolvable, resume,
|
|
160
|
+
const { targetName, workDir, isConductor, agent, systemPrompt, ensemble, temporalAddress, temporalNamespace, agentDefinition, agentDefinitionPath, nativeResolvable, resume, sessionId, allowedTools, claudeBin } = input;
|
|
161
161
|
// Read secrets from the worker's config closure — never from workflow state
|
|
162
162
|
const { temporalApiKey, temporalTlsCertPath, temporalTlsKeyPath } = config;
|
|
163
163
|
try {
|
|
@@ -175,6 +175,7 @@ function createOutboxActivities(client, config) {
|
|
|
175
175
|
temporalTlsKeyPath,
|
|
176
176
|
isConductor,
|
|
177
177
|
workDir,
|
|
178
|
+
sessionId,
|
|
178
179
|
});
|
|
179
180
|
log(`Spawned copilot-bridge (pid ${pid}) in ${workDir} as "${targetName}"`);
|
|
180
181
|
}
|
|
@@ -194,8 +195,8 @@ function createOutboxActivities(client, config) {
|
|
|
194
195
|
// For encore: use UUID for deterministic --resume (no interactive picker).
|
|
195
196
|
// For new sessions: use --session-id to track the UUID for future encores.
|
|
196
197
|
const nameArgs = resume
|
|
197
|
-
? ['--resume',
|
|
198
|
-
: ['-n', targetName, ...(
|
|
198
|
+
? ['--resume', sessionId || targetName]
|
|
199
|
+
: ['-n', targetName, ...(sessionId ? ['--session-id', sessionId] : [])];
|
|
199
200
|
// Build --allowedTools flag from agent definition frontmatter
|
|
200
201
|
const allowedToolsFlags = allowedTools && allowedTools.length > 0
|
|
201
202
|
? ['--allowedTools', ...allowedTools]
|
|
@@ -296,7 +297,7 @@ function createOutboxActivities(client, config) {
|
|
|
296
297
|
agentDefinitionPath,
|
|
297
298
|
nativeResolvable,
|
|
298
299
|
allowedTools,
|
|
299
|
-
|
|
300
|
+
sessionId: metadata.sessionId || undefined,
|
|
300
301
|
temporalAddress: config.temporalAddress,
|
|
301
302
|
temporalNamespace: config.temporalNamespace,
|
|
302
303
|
claudeBin: config.claudeBin,
|
package/dist/cli/commands.js
CHANGED
|
@@ -438,10 +438,25 @@ const SEARCH_ATTRIBUTES = [
|
|
|
438
438
|
{ name: 'ClaudeTempoStatus', type: 'Keyword' },
|
|
439
439
|
{ name: 'ClaudeTempoPlayerType', type: 'Keyword' },
|
|
440
440
|
];
|
|
441
|
-
function isTemporalReachable(config) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
441
|
+
async function isTemporalReachable(config) {
|
|
442
|
+
try {
|
|
443
|
+
const conn = await (0, connection_1.createTemporalConnection)(config);
|
|
444
|
+
try {
|
|
445
|
+
// Verify namespace is ready — a gRPC connection alone doesn't guarantee the server can serve requests
|
|
446
|
+
const client = new client_1.Client({ connection: conn, namespace: config.temporalNamespace || 'default' });
|
|
447
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
448
|
+
for await (const _ of client.workflow.list({ query: 'WorkflowId = "__readiness_probe__"' })) {
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
await conn.close();
|
|
454
|
+
}
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
445
460
|
}
|
|
446
461
|
function temporalCliExists() {
|
|
447
462
|
const cmd = process.platform === 'win32' ? 'where' : 'which';
|
|
@@ -777,7 +792,7 @@ async function up(opts) {
|
|
|
777
792
|
isConductor: true,
|
|
778
793
|
agentType: conductorAgent,
|
|
779
794
|
status: 'pending',
|
|
780
|
-
|
|
795
|
+
sessionId: conductorSessionId,
|
|
781
796
|
...(resolvedConductorType ? { playerType: resolvedConductorType.name, playerTypeDescription: resolvedConductorType.description || '' } : {}),
|
|
782
797
|
},
|
|
783
798
|
autoSummary: `Conductor session`,
|
|
@@ -858,7 +873,7 @@ async function up(opts) {
|
|
|
858
873
|
console.log();
|
|
859
874
|
out.log(`Recruiting ${lineup.players.length} player${lineup.players.length !== 1 ? 's' : ''} from lineup...`);
|
|
860
875
|
for (const player of lineup.players) {
|
|
861
|
-
const playerAgent = player.agent === 'copilot' ? 'copilot' : 'claude';
|
|
876
|
+
const playerAgent = player.agent === 'copilot' ? 'copilot' : (player.agent === 'claude' ? 'claude' : opts.agent);
|
|
862
877
|
const playerWorkDir = player.workDir || process.cwd();
|
|
863
878
|
const playerTypeName = player.type;
|
|
864
879
|
const resolvedPlayerType = playerTypeName ? (0, agent_types_1.resolveAgentType)(playerTypeName) : null;
|
|
@@ -877,7 +892,7 @@ async function up(opts) {
|
|
|
877
892
|
isConductor: false,
|
|
878
893
|
agentType: playerAgent,
|
|
879
894
|
status: 'pending',
|
|
880
|
-
|
|
895
|
+
sessionId: playerSessionId,
|
|
881
896
|
recruitedBy: sessionName,
|
|
882
897
|
...(resolvedPlayerType ? { playerType: resolvedPlayerType.name, playerTypeDescription: resolvedPlayerType.description || '' } : {}),
|
|
883
898
|
},
|
|
@@ -965,8 +980,23 @@ async function up(opts) {
|
|
|
965
980
|
try {
|
|
966
981
|
const entry = lineupScheduleToEntry(sched);
|
|
967
982
|
const schedulerWfId = (0, config_1.schedulerWorkflowId)(opts.ensemble);
|
|
968
|
-
|
|
969
|
-
|
|
983
|
+
// Try to signal existing scheduler; if not running, start it with this schedule as seed
|
|
984
|
+
try {
|
|
985
|
+
const handle = client.workflow.getHandle(schedulerWfId);
|
|
986
|
+
await handle.describe();
|
|
987
|
+
await handle.signal(scheduler_signals_1.addScheduleSignal, entry);
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
await client.workflow.start('claudeSchedulerWorkflow', {
|
|
991
|
+
workflowId: schedulerWfId,
|
|
992
|
+
taskQueue: config.taskQueue,
|
|
993
|
+
args: [{ ensemble: opts.ensemble, entries: [entry] }],
|
|
994
|
+
workflowIdConflictPolicy: client_1.WorkflowIdConflictPolicy.USE_EXISTING,
|
|
995
|
+
searchAttributes: {
|
|
996
|
+
ClaudeTempoEnsemble: [opts.ensemble],
|
|
997
|
+
},
|
|
998
|
+
});
|
|
999
|
+
}
|
|
970
1000
|
out.check(sched.name, true, `→ ${sched.target}`);
|
|
971
1001
|
}
|
|
972
1002
|
catch (err) {
|
package/dist/config.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export declare const ENV: {
|
|
|
8
8
|
readonly BRIDGE_NAME: "COPILOT_BRIDGE_NAME";
|
|
9
9
|
readonly BRIDGE_MODE: "CLAUDE_TEMPO_BRIDGE_MODE";
|
|
10
10
|
readonly BRIDGE_MODEL: "COPILOT_BRIDGE_MODEL";
|
|
11
|
+
readonly BRIDGE_SESSION_ID: "COPILOT_BRIDGE_SESSION_ID";
|
|
11
12
|
readonly TEMPORAL_ADDRESS: "TEMPORAL_ADDRESS";
|
|
12
13
|
readonly TEMPORAL_NAMESPACE: "TEMPORAL_NAMESPACE";
|
|
13
14
|
readonly TEMPORAL_API_KEY: "TEMPORAL_API_KEY";
|
package/dist/config.js
CHANGED
|
@@ -29,6 +29,7 @@ exports.ENV = {
|
|
|
29
29
|
BRIDGE_NAME: 'COPILOT_BRIDGE_NAME',
|
|
30
30
|
BRIDGE_MODE: 'CLAUDE_TEMPO_BRIDGE_MODE',
|
|
31
31
|
BRIDGE_MODEL: 'COPILOT_BRIDGE_MODEL',
|
|
32
|
+
BRIDGE_SESSION_ID: 'COPILOT_BRIDGE_SESSION_ID',
|
|
32
33
|
TEMPORAL_ADDRESS: 'TEMPORAL_ADDRESS',
|
|
33
34
|
TEMPORAL_NAMESPACE: 'TEMPORAL_NAMESPACE',
|
|
34
35
|
TEMPORAL_API_KEY: 'TEMPORAL_API_KEY',
|
package/dist/copilot-bridge.d.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* CLAUDE_TEMPO_PLAYER_NAME — player ID for workflow registration (set by spawner for deterministic workflow IDs)
|
|
17
17
|
* COPILOT_BRIDGE_NAME — player name for set_name (optional)
|
|
18
18
|
* COPILOT_BRIDGE_MODEL — model to use (optional)
|
|
19
|
+
* COPILOT_BRIDGE_SESSION_ID — deterministic session ID for resumable sessions (optional)
|
|
19
20
|
* GITHUB_TOKEN — GitHub auth token (optional, uses logged-in user by default)
|
|
20
21
|
*/
|
|
21
22
|
export {};
|
package/dist/copilot-bridge.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* CLAUDE_TEMPO_PLAYER_NAME — player ID for workflow registration (set by spawner for deterministic workflow IDs)
|
|
18
18
|
* COPILOT_BRIDGE_NAME — player name for set_name (optional)
|
|
19
19
|
* COPILOT_BRIDGE_MODEL — model to use (optional)
|
|
20
|
+
* COPILOT_BRIDGE_SESSION_ID — deterministic session ID for resumable sessions (optional)
|
|
20
21
|
* GITHUB_TOKEN — GitHub auth token (optional, uses logged-in user by default)
|
|
21
22
|
*/
|
|
22
23
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
@@ -86,6 +87,8 @@ const MAX_CONSECUTIVE_FAILURES = 3;
|
|
|
86
87
|
const MAX_SESSION_RECREATIONS = 2;
|
|
87
88
|
/** Check workflow status every N polls (~30s at 2s interval). */
|
|
88
89
|
const WORKFLOW_STATUS_CHECK_INTERVAL = 15;
|
|
90
|
+
/** Proactively recreate the Copilot session after this idle period (ms). Default 60 min. */
|
|
91
|
+
const SESSION_MAX_IDLE_MS = 60 * 60 * 1000;
|
|
89
92
|
/** Wrap createSession with a timeout so auth/network hangs don't block forever. */
|
|
90
93
|
async function createSessionWithTimeout(copilotClient, sessionConfig, timeoutMs = CREATE_SESSION_TIMEOUT_MS) {
|
|
91
94
|
let timer;
|
|
@@ -106,6 +109,7 @@ async function main() {
|
|
|
106
109
|
const config = (0, config_1.getConfig)();
|
|
107
110
|
const playerName = process.env[config_1.ENV.BRIDGE_NAME];
|
|
108
111
|
const model = process.env[config_1.ENV.BRIDGE_MODEL];
|
|
112
|
+
const copilotSessionId = process.env[config_1.ENV.BRIDGE_SESSION_ID] || `tempo-${config.ensemble}-${playerName || 'unknown'}-${Date.now()}-${process.pid}`;
|
|
109
113
|
const workDir = process.cwd();
|
|
110
114
|
log(`Starting Copilot bridge in ${workDir} (ensemble: ${config.ensemble})`);
|
|
111
115
|
// Connect Temporal client (for polling only — the MCP server child process runs its own worker)
|
|
@@ -156,6 +160,7 @@ async function main() {
|
|
|
156
160
|
},
|
|
157
161
|
});
|
|
158
162
|
const sessionConfig = {
|
|
163
|
+
sessionId: copilotSessionId,
|
|
159
164
|
// approveAll is intentional: Copilot bridge sessions run headless with no
|
|
160
165
|
// interactive terminal, so there is no way to prompt for permission approval.
|
|
161
166
|
// All tool calls are auto-approved by design — the bridge operator accepts
|
|
@@ -290,6 +295,11 @@ async function main() {
|
|
|
290
295
|
process.exit(1);
|
|
291
296
|
}
|
|
292
297
|
log(`Workflow ready: ${expectedWorkflowId}`);
|
|
298
|
+
// Store sessionId in workflow metadata for future encore/resume
|
|
299
|
+
try {
|
|
300
|
+
await handle.signal('updateMetadata', { sessionId: copilotSessionId });
|
|
301
|
+
}
|
|
302
|
+
catch { /* workflow may not be ready yet */ }
|
|
293
303
|
// If a name was requested, send the set_name instruction
|
|
294
304
|
if (playerName) {
|
|
295
305
|
log(`Sending set_name instruction for "${playerName}"...`);
|
|
@@ -314,6 +324,8 @@ async function main() {
|
|
|
314
324
|
let pollCount = 0;
|
|
315
325
|
let consecutiveFailures = 0;
|
|
316
326
|
let sessionRecreations = 0;
|
|
327
|
+
let proactiveRecreations = 0;
|
|
328
|
+
let lastActivityTime = Date.now();
|
|
317
329
|
// interval declared here, assigned after poll is defined
|
|
318
330
|
let interval;
|
|
319
331
|
// Shared cleanup — disconnects session, removes PID file, stops client.
|
|
@@ -351,18 +363,33 @@ async function main() {
|
|
|
351
363
|
log(`ERROR: Exceeded max session recreations (${MAX_SESSION_RECREATIONS}). Giving up.`);
|
|
352
364
|
return false;
|
|
353
365
|
}
|
|
354
|
-
log(`Attempting session
|
|
366
|
+
log(`Attempting session recovery (${sessionRecreations}/${MAX_SESSION_RECREATIONS})...`);
|
|
355
367
|
try {
|
|
356
368
|
await session.disconnect().catch(() => { });
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
369
|
+
// Try resumeSession first to preserve conversation history
|
|
370
|
+
try {
|
|
371
|
+
const { sessionId: _discard, ...resumeConfig } = sessionConfig;
|
|
372
|
+
session = await copilotClient.resumeSession(copilotSessionId, resumeConfig);
|
|
373
|
+
attachEventLogger(session);
|
|
374
|
+
sessionAlive = true;
|
|
375
|
+
consecutiveFailures = 0;
|
|
376
|
+
lastActivityTime = Date.now();
|
|
377
|
+
log(`Session resumed successfully: ${session.sessionId}`);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
catch (resumeErr) {
|
|
381
|
+
log(`resumeSession failed (${resumeErr?.message}), falling back to createSession`);
|
|
382
|
+
session = await createSessionWithTimeout(copilotClient, sessionConfig);
|
|
383
|
+
attachEventLogger(session);
|
|
384
|
+
sessionAlive = true;
|
|
385
|
+
consecutiveFailures = 0;
|
|
386
|
+
lastActivityTime = Date.now();
|
|
387
|
+
log(`Session recreated (fresh) successfully: ${session.sessionId}`);
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
363
390
|
}
|
|
364
391
|
catch (err) {
|
|
365
|
-
log(`Session
|
|
392
|
+
log(`Session recovery failed: ${err?.message}`);
|
|
366
393
|
return false;
|
|
367
394
|
}
|
|
368
395
|
}
|
|
@@ -393,13 +420,37 @@ async function main() {
|
|
|
393
420
|
process.exit(0);
|
|
394
421
|
}
|
|
395
422
|
}
|
|
423
|
+
// Proactive stale-session detection — recreate before the SDK server GCs the session
|
|
424
|
+
const idleMs = Date.now() - lastActivityTime;
|
|
425
|
+
if (idleMs > SESSION_MAX_IDLE_MS && !processing) {
|
|
426
|
+
try {
|
|
427
|
+
processing = true; // guard against overlapping polls during async recreation
|
|
428
|
+
log(`Session idle for ${(idleMs / 1000 / 60).toFixed(0)}min — proactively recreating`);
|
|
429
|
+
proactiveRecreations++;
|
|
430
|
+
const recovered = await recreateSession();
|
|
431
|
+
if (recovered) {
|
|
432
|
+
// Proactive recreation is lifecycle management, not failure recovery — restore failure budget
|
|
433
|
+
// but don't reset to 0: use proactiveRecreations to cap total lifecycle recreations
|
|
434
|
+
sessionRecreations = Math.max(0, sessionRecreations - 1);
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
// Session is almost certainly dead server-side — force immediate recovery on next message
|
|
438
|
+
// Use MAX - 1 so the next poll error increments to the threshold and triggers recovery
|
|
439
|
+
consecutiveFailures = MAX_CONSECUTIVE_FAILURES - 1;
|
|
440
|
+
sessionAlive = false;
|
|
441
|
+
log('ERROR: Proactive session recreation failed — will force recovery on next message');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
processing = false;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
396
448
|
try {
|
|
397
449
|
const messages = await handle.query('pendingMessages');
|
|
398
450
|
if (messages.length === 0)
|
|
399
451
|
return;
|
|
400
452
|
processing = true;
|
|
401
453
|
const ids = messages.map((m) => m.id);
|
|
402
|
-
await handle.signal('markDelivered', ids);
|
|
403
454
|
// Format messages into a single prompt, appending ack instruction for Maestro messages
|
|
404
455
|
const prompt = messages
|
|
405
456
|
.map((m) => {
|
|
@@ -417,8 +468,11 @@ async function main() {
|
|
|
417
468
|
const elapsed = Date.now() - t0;
|
|
418
469
|
log(`sendAndWait completed in ${elapsed}ms`);
|
|
419
470
|
log(`Response: ${JSON.stringify(result)?.substring(0, 500)}`);
|
|
471
|
+
// Mark delivered only after successful send — failed messages stay in pending queue for retry
|
|
472
|
+
await handle.signal('markDelivered', ids);
|
|
420
473
|
// Success — reset failure tracking
|
|
421
474
|
consecutiveFailures = 0;
|
|
475
|
+
lastActivityTime = Date.now();
|
|
422
476
|
sessionAlive = true;
|
|
423
477
|
processing = false;
|
|
424
478
|
}
|
package/dist/spawn.d.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface CopilotBridgeOpts {
|
|
|
64
64
|
workDir: string;
|
|
65
65
|
/** Directory for log and PID files. Defaults to `logs/` inside workDir. */
|
|
66
66
|
logDir?: string;
|
|
67
|
+
/** Copilot SDK session ID for resumable sessions. */
|
|
68
|
+
sessionId?: string;
|
|
67
69
|
}
|
|
68
70
|
export interface CopilotBridgeResult {
|
|
69
71
|
pid: number | undefined;
|
package/dist/spawn.js
CHANGED
|
@@ -427,6 +427,7 @@ function spawnCopilotBridge(opts) {
|
|
|
427
427
|
...(opts.temporalApiKey ? { [config_1.ENV.TEMPORAL_API_KEY]: opts.temporalApiKey } : {}),
|
|
428
428
|
...(opts.temporalTlsCertPath ? { [config_1.ENV.TEMPORAL_TLS_CERT_PATH]: opts.temporalTlsCertPath } : {}),
|
|
429
429
|
...(opts.temporalTlsKeyPath ? { [config_1.ENV.TEMPORAL_TLS_KEY_PATH]: opts.temporalTlsKeyPath } : {}),
|
|
430
|
+
...(opts.sessionId ? { [config_1.ENV.BRIDGE_SESSION_ID]: opts.sessionId } : {}),
|
|
430
431
|
},
|
|
431
432
|
});
|
|
432
433
|
child.unref();
|
package/dist/tui/client.js
CHANGED
|
@@ -29,7 +29,7 @@ function createTempoClient(client) {
|
|
|
29
29
|
try {
|
|
30
30
|
const h = handle(globalMaestroId);
|
|
31
31
|
const byEnsemble = await h.query('maestroPlayersByEnsemble');
|
|
32
|
-
|
|
32
|
+
const results = Object.entries(byEnsemble).map(([name, players]) => {
|
|
33
33
|
const conductor = players.find(p => p.isConductor);
|
|
34
34
|
return {
|
|
35
35
|
name,
|
|
@@ -38,6 +38,10 @@ function createTempoClient(client) {
|
|
|
38
38
|
conductorStatus: conductor?.status,
|
|
39
39
|
};
|
|
40
40
|
});
|
|
41
|
+
// Only trust Maestro if it has discovered ensembles; fall through to
|
|
42
|
+
// Strategy 2 when empty — the Maestro may not have refreshed yet.
|
|
43
|
+
if (results.length > 0)
|
|
44
|
+
return results;
|
|
41
45
|
}
|
|
42
46
|
catch {
|
|
43
47
|
// Global Maestro not available — fall through
|
package/dist/types.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export interface SessionMetadata {
|
|
|
18
18
|
recruitedBy?: string;
|
|
19
19
|
/** Worktree path if this session was spawned in an isolated worktree. */
|
|
20
20
|
worktreePath?: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/** Session UUID — used for Copilot SDK sessionId and Claude Code --resume/--session-id. */
|
|
22
|
+
sessionId?: string;
|
|
23
23
|
}
|
|
24
24
|
export interface AgentTypeInfo {
|
|
25
25
|
name: string;
|
|
@@ -132,8 +132,9 @@ async function claudeSessionWorkflow(input) {
|
|
|
132
132
|
input.metadata.playerTypeDescription = update.playerTypeDescription;
|
|
133
133
|
if (update.worktreePath != null)
|
|
134
134
|
input.metadata.worktreePath = update.worktreePath;
|
|
135
|
-
if (update.claudeSessionId != null)
|
|
136
|
-
input.metadata.
|
|
135
|
+
if (update.sessionId != null || update.claudeSessionId != null) {
|
|
136
|
+
input.metadata.sessionId = update.sessionId ?? update.claudeSessionId;
|
|
137
|
+
}
|
|
137
138
|
if (update.status != null) {
|
|
138
139
|
input.metadata.status = update.status;
|
|
139
140
|
// Re-enable stale detection only when explicitly requested (server.ts sets this)
|
|
@@ -468,7 +469,7 @@ async function claudeSessionWorkflow(input) {
|
|
|
468
469
|
agentDefinition: entry.agentDefinition,
|
|
469
470
|
agentDefinitionPath: entry.agentDefinitionPath,
|
|
470
471
|
nativeResolvable: entry.nativeResolvable,
|
|
471
|
-
|
|
472
|
+
sessionId: recruitResult.sessionId,
|
|
472
473
|
allowedTools: entry.allowedTools,
|
|
473
474
|
claudeBin: entry.claudeBin,
|
|
474
475
|
});
|
|
@@ -496,7 +497,7 @@ async function claudeSessionWorkflow(input) {
|
|
|
496
497
|
agentDefinitionPath: encoreResult.agentDefinitionPath,
|
|
497
498
|
nativeResolvable: encoreResult.nativeResolvable,
|
|
498
499
|
allowedTools: encoreResult.allowedTools,
|
|
499
|
-
|
|
500
|
+
sessionId: encoreResult.sessionId,
|
|
500
501
|
resume: true,
|
|
501
502
|
claudeBin: entry.claudeBin || encoreResult.claudeBin,
|
|
502
503
|
});
|
|
@@ -23,7 +23,7 @@ export declare const updateMetadataSignal: import("@temporalio/workflow").Signal
|
|
|
23
23
|
playerType?: string;
|
|
24
24
|
playerTypeDescription?: string;
|
|
25
25
|
worktreePath?: string;
|
|
26
|
-
|
|
26
|
+
sessionId?: string;
|
|
27
27
|
}], string>;
|
|
28
28
|
export declare const getPartQuery: import("@temporalio/workflow").QueryDefinition<string, [], string>;
|
|
29
29
|
export declare const getMetadataQuery: import("@temporalio/workflow").QueryDefinition<SessionMetadata, [], string>;
|