teleportation-cli 1.1.5 → 1.2.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/hooks/permission_request.mjs +326 -59
- package/.claude/hooks/post_tool_use.mjs +90 -0
- package/.claude/hooks/pre_tool_use.mjs +212 -293
- package/.claude/hooks/session-register.mjs +89 -104
- package/.claude/hooks/session_end.mjs +41 -42
- package/.claude/hooks/session_start.mjs +45 -60
- package/.claude/hooks/stop.mjs +752 -99
- package/.claude/hooks/user_prompt_submit.mjs +26 -3
- package/lib/cli/daemon-commands.js +1 -1
- package/lib/cli/teleport-commands.js +469 -0
- package/lib/daemon/daemon-v2.js +104 -0
- package/lib/daemon/lifecycle.js +56 -171
- package/lib/daemon/services/index.js +3 -0
- package/lib/daemon/services/polling-service.js +173 -0
- package/lib/daemon/services/queue-service.js +318 -0
- package/lib/daemon/services/session-service.js +115 -0
- package/lib/daemon/state.js +35 -0
- package/lib/daemon/task-executor-v2.js +413 -0
- package/lib/daemon/task-executor.js +270 -96
- package/lib/daemon/teleportation-daemon.js +709 -126
- package/lib/daemon/timeline-analyzer.js +215 -0
- package/lib/daemon/transcript-ingestion.js +696 -0
- package/lib/daemon/utils.js +91 -0
- package/lib/install/installer.js +184 -20
- package/lib/install/uhr-installer.js +136 -0
- package/lib/remote/providers/base-provider.js +46 -0
- package/lib/remote/providers/daytona-provider.js +58 -0
- package/lib/remote/providers/provider-factory.js +90 -19
- package/lib/remote/providers/sprites-provider.js +711 -0
- package/lib/teleport/exporters/claude-exporter.js +302 -0
- package/lib/teleport/exporters/gemini-exporter.js +307 -0
- package/lib/teleport/exporters/index.js +93 -0
- package/lib/teleport/exporters/interface.js +153 -0
- package/lib/teleport/fork-tracker.js +415 -0
- package/lib/teleport/git-committer.js +337 -0
- package/lib/teleport/index.js +48 -0
- package/lib/teleport/manager.js +620 -0
- package/lib/teleport/session-capture.js +282 -0
- package/package.json +9 -5
- package/teleportation-cli.cjs +488 -453
- package/.claude/hooks/heartbeat.mjs +0 -396
- package/lib/daemon/pid-manager.js +0 -183
package/lib/daemon/lifecycle.js
CHANGED
|
@@ -1,132 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Daemon Lifecycle Management
|
|
3
|
+
*
|
|
4
|
+
* Thin adapter over @derivativelabs/agent-process.
|
|
5
|
+
* Provides startDaemon/stopDaemon/restartDaemon/getDaemonStatus
|
|
6
|
+
* for the CLI and daemon-commands module.
|
|
7
|
+
*
|
|
8
|
+
* PRD-0025: Replaced custom pid-manager.js with agent-process platform-native
|
|
9
|
+
* service management (launchd/systemd/pm2).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { agentStart, agentStop, agentStatus } from '@derivativelabs/agent-process';
|
|
2
13
|
import { fileURLToPath } from 'url';
|
|
3
14
|
import { dirname, join } from 'path';
|
|
4
|
-
import {
|
|
5
|
-
checkDaemonStatus,
|
|
6
|
-
acquirePidLock,
|
|
7
|
-
releasePidLock,
|
|
8
|
-
isProcessRunning
|
|
9
|
-
} from './pid-manager.js';
|
|
10
15
|
|
|
11
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
12
17
|
const __dirname = dirname(__filename);
|
|
13
18
|
|
|
14
|
-
|
|
19
|
+
const AGENT_NAME = 'teleportation-daemon';
|
|
15
20
|
const DAEMON_SCRIPT = join(__dirname, 'teleportation-daemon.js');
|
|
16
21
|
|
|
17
22
|
/**
|
|
18
|
-
* Start the daemon process
|
|
19
|
-
* @param {Object} options - Start options
|
|
20
|
-
* @param {boolean} options.detached -
|
|
21
|
-
* @param {boolean} options.silent -
|
|
23
|
+
* Start the daemon process via agent-process
|
|
24
|
+
* @param {Object} options - Start options (kept for backward compat)
|
|
25
|
+
* @param {boolean} options.detached - Ignored (agent-process handles this)
|
|
26
|
+
* @param {boolean} options.silent - Ignored (agent-process handles this)
|
|
22
27
|
* @returns {Promise<{pid: number, success: boolean}>}
|
|
23
28
|
*/
|
|
24
29
|
export async function startDaemon(options = {}) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (status.running) {
|
|
30
|
-
throw new Error(`Daemon already running with PID ${status.pid}`);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Clean up stale PID file if exists
|
|
34
|
-
if (status.stale) {
|
|
35
|
-
await releasePidLock(status.pid);
|
|
30
|
+
// Check if already running
|
|
31
|
+
const current = await getDaemonStatus();
|
|
32
|
+
if (current.running) {
|
|
33
|
+
throw new Error(`Daemon already running with PID ${current.pid}`);
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
env: {
|
|
46
|
-
...process.env,
|
|
47
|
-
TELEPORTATION_DAEMON: 'true'
|
|
48
|
-
}
|
|
36
|
+
const result = await agentStart({
|
|
37
|
+
name: AGENT_NAME,
|
|
38
|
+
script: DAEMON_SCRIPT,
|
|
39
|
+
env: {
|
|
40
|
+
...process.env,
|
|
41
|
+
TELEPORTATION_DAEMON: 'true',
|
|
42
|
+
DAEMON_IDLE_TIMEOUT_MS: process.env.DAEMON_IDLE_TIMEOUT_MS || '0'
|
|
49
43
|
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
// Detach from parent if requested
|
|
53
|
-
if (detached) {
|
|
54
|
-
child.unref();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Wait a moment to ensure process started (increase wait time for CI)
|
|
58
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
59
|
-
|
|
60
|
-
// Verify daemon is running (check multiple times for slow CI)
|
|
61
|
-
let newStatus = await checkDaemonStatus();
|
|
62
|
-
if (!newStatus.running) {
|
|
63
|
-
// Wait a bit more and check again
|
|
64
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
65
|
-
newStatus = await checkDaemonStatus();
|
|
66
|
-
if (!newStatus.running) {
|
|
67
|
-
throw new Error('Daemon failed to start');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
44
|
+
});
|
|
70
45
|
|
|
71
46
|
return {
|
|
72
|
-
pid:
|
|
47
|
+
pid: result.pid || null,
|
|
73
48
|
success: true
|
|
74
49
|
};
|
|
75
50
|
}
|
|
76
51
|
|
|
77
52
|
/**
|
|
78
|
-
* Stop the daemon process
|
|
53
|
+
* Stop the daemon process via agent-process
|
|
79
54
|
* @param {Object} options - Stop options
|
|
80
55
|
* @param {number} options.timeout - Timeout in ms for graceful shutdown (default: 5000)
|
|
81
56
|
* @param {boolean} options.force - Force kill if graceful shutdown fails (default: true)
|
|
82
57
|
* @returns {Promise<{success: boolean, forced: boolean}>}
|
|
83
58
|
*/
|
|
84
59
|
export async function stopDaemon(options = {}) {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
// Check daemon status
|
|
88
|
-
const status = await checkDaemonStatus();
|
|
89
|
-
if (!status.running) {
|
|
60
|
+
const current = await getDaemonStatus();
|
|
61
|
+
if (!current.running) {
|
|
90
62
|
return { success: true, forced: false };
|
|
91
63
|
}
|
|
92
64
|
|
|
93
|
-
const { pid } = status;
|
|
94
|
-
|
|
95
65
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Wait for process to exit
|
|
100
|
-
const startTime = Date.now();
|
|
101
|
-
while (Date.now() - startTime < timeout) {
|
|
102
|
-
if (!isProcessRunning(pid)) {
|
|
103
|
-
await releasePidLock(pid);
|
|
104
|
-
return { success: true, forced: false };
|
|
105
|
-
}
|
|
106
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Timeout reached, force kill if requested
|
|
110
|
-
if (force) {
|
|
111
|
-
process.kill(pid, 'SIGKILL');
|
|
112
|
-
|
|
113
|
-
// Wait a moment for kill to take effect
|
|
114
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
115
|
-
|
|
116
|
-
if (!isProcessRunning(pid)) {
|
|
117
|
-
await releasePidLock(pid);
|
|
118
|
-
return { success: true, forced: true };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
throw new Error('Failed to kill daemon process');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return { success: false, forced: false };
|
|
66
|
+
await agentStop(AGENT_NAME);
|
|
67
|
+
return { success: true, forced: false };
|
|
125
68
|
} catch (err) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return { success: true, forced: false };
|
|
69
|
+
if (options.force !== false) {
|
|
70
|
+
// agent-process handles force kill internally
|
|
71
|
+
return { success: true, forced: true };
|
|
130
72
|
}
|
|
131
73
|
throw err;
|
|
132
74
|
}
|
|
@@ -142,16 +84,13 @@ export async function stopDaemon(options = {}) {
|
|
|
142
84
|
export async function restartDaemon(options = {}) {
|
|
143
85
|
const { stopTimeout = 5000, force = true } = options;
|
|
144
86
|
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
const wasRunning = status.running;
|
|
87
|
+
const current = await getDaemonStatus();
|
|
88
|
+
const wasRunning = current.running;
|
|
148
89
|
|
|
149
|
-
// Stop daemon if running
|
|
150
90
|
if (wasRunning) {
|
|
151
91
|
await stopDaemon({ timeout: stopTimeout, force });
|
|
152
92
|
}
|
|
153
93
|
|
|
154
|
-
// Start daemon
|
|
155
94
|
const result = await startDaemon();
|
|
156
95
|
|
|
157
96
|
return {
|
|
@@ -192,7 +131,7 @@ async function updateSessionDaemonState(sessionId, updates) {
|
|
|
192
131
|
}
|
|
193
132
|
|
|
194
133
|
export async function startDaemonIfNeeded(sessionId, reason = 'manual') {
|
|
195
|
-
const status = await
|
|
134
|
+
const status = await getDaemonStatus();
|
|
196
135
|
|
|
197
136
|
if (!status.running) {
|
|
198
137
|
await startDaemon();
|
|
@@ -207,12 +146,11 @@ export async function startDaemonIfNeeded(sessionId, reason = 'manual') {
|
|
|
207
146
|
}
|
|
208
147
|
|
|
209
148
|
export async function stopDaemonIfNeeded(sessionId, reason = 'manual_stop') {
|
|
210
|
-
const status = await
|
|
149
|
+
const status = await getDaemonStatus();
|
|
211
150
|
if (!status.running) {
|
|
212
151
|
return { stopped: false, reason: 'not_running' };
|
|
213
152
|
}
|
|
214
153
|
|
|
215
|
-
// If relay is configured, check if other sessions still have daemon running
|
|
216
154
|
const { url, key } = getRelayConfig();
|
|
217
155
|
if (url && key) {
|
|
218
156
|
try {
|
|
@@ -232,20 +170,17 @@ export async function stopDaemonIfNeeded(sessionId, reason = 'manual_stop') {
|
|
|
232
170
|
}
|
|
233
171
|
}
|
|
234
172
|
} catch (err) {
|
|
235
|
-
// Fail open: if we can't query sessions, we still attempt to stop daemon
|
|
236
173
|
if (process.env.DEBUG) {
|
|
237
174
|
console.error('[lifecycle] Failed to query sessions before stop:', err.message);
|
|
238
175
|
}
|
|
239
176
|
}
|
|
240
177
|
}
|
|
241
178
|
|
|
242
|
-
// Update daemon_state before stopping
|
|
243
179
|
if (sessionId) {
|
|
244
180
|
await updateSessionDaemonState(sessionId, {
|
|
245
181
|
status: 'stopped',
|
|
246
182
|
started_reason: null,
|
|
247
183
|
is_away: false
|
|
248
|
-
// stopped_reason could be added later if DaemonState schema is extended
|
|
249
184
|
});
|
|
250
185
|
}
|
|
251
186
|
|
|
@@ -254,83 +189,33 @@ export async function stopDaemonIfNeeded(sessionId, reason = 'manual_stop') {
|
|
|
254
189
|
}
|
|
255
190
|
|
|
256
191
|
/**
|
|
257
|
-
* Get daemon status
|
|
192
|
+
* Get daemon status via agent-process
|
|
258
193
|
* @returns {Promise<{running: boolean, pid: number|null, uptime: number|null}>}
|
|
259
194
|
*/
|
|
260
195
|
export async function getDaemonStatus() {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
196
|
+
try {
|
|
197
|
+
const status = await agentStatus(AGENT_NAME);
|
|
198
|
+
return {
|
|
199
|
+
running: status.state === 'online' || status.running || false,
|
|
200
|
+
pid: status.pid || null,
|
|
201
|
+
uptime: status.uptime || null
|
|
202
|
+
};
|
|
203
|
+
} catch (err) {
|
|
204
|
+
return { running: false, pid: null, uptime: null };
|
|
205
|
+
}
|
|
270
206
|
}
|
|
271
207
|
|
|
272
208
|
/**
|
|
273
|
-
*
|
|
274
|
-
* @param {Function} cleanupCallback - Async function to call before exit
|
|
209
|
+
* Alias for getDaemonStatus (backward compat with pid-manager API)
|
|
275
210
|
*/
|
|
276
|
-
export
|
|
277
|
-
const handleSignal = async (signal) => {
|
|
278
|
-
console.log(`Received ${signal}, shutting down gracefully...`);
|
|
279
|
-
|
|
280
|
-
try {
|
|
281
|
-
// Run cleanup callback
|
|
282
|
-
if (cleanupCallback) {
|
|
283
|
-
await cleanupCallback();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Release PID lock
|
|
287
|
-
await releasePidLock(process.pid);
|
|
288
|
-
|
|
289
|
-
process.exit(0);
|
|
290
|
-
} catch (err) {
|
|
291
|
-
console.error('Error during cleanup:', err);
|
|
292
|
-
process.exit(1);
|
|
293
|
-
}
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
// Handle termination signals
|
|
297
|
-
process.on('SIGTERM', () => handleSignal('SIGTERM'));
|
|
298
|
-
process.on('SIGINT', () => handleSignal('SIGINT'));
|
|
299
|
-
|
|
300
|
-
// Handle uncaught errors
|
|
301
|
-
process.on('uncaughtException', async (err) => {
|
|
302
|
-
console.error('Uncaught exception:', err);
|
|
303
|
-
try {
|
|
304
|
-
if (cleanupCallback) {
|
|
305
|
-
await cleanupCallback();
|
|
306
|
-
}
|
|
307
|
-
await releasePidLock(process.pid);
|
|
308
|
-
} catch (cleanupErr) {
|
|
309
|
-
console.error('Error during cleanup:', cleanupErr);
|
|
310
|
-
}
|
|
311
|
-
process.exit(1);
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
process.on('unhandledRejection', async (reason) => {
|
|
315
|
-
console.error('Unhandled rejection:', reason);
|
|
316
|
-
try {
|
|
317
|
-
if (cleanupCallback) {
|
|
318
|
-
await cleanupCallback();
|
|
319
|
-
}
|
|
320
|
-
await releasePidLock(process.pid);
|
|
321
|
-
} catch (cleanupErr) {
|
|
322
|
-
console.error('Error during cleanup:', cleanupErr);
|
|
323
|
-
}
|
|
324
|
-
process.exit(1);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
211
|
+
export const checkDaemonStatus = getDaemonStatus;
|
|
327
212
|
|
|
328
213
|
export default {
|
|
329
214
|
startDaemon,
|
|
330
215
|
stopDaemon,
|
|
331
216
|
restartDaemon,
|
|
332
217
|
getDaemonStatus,
|
|
333
|
-
|
|
218
|
+
checkDaemonStatus,
|
|
334
219
|
startDaemonIfNeeded,
|
|
335
220
|
stopDaemonIfNeeded
|
|
336
221
|
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polling Service
|
|
3
|
+
*
|
|
4
|
+
* Periodically polls the relay API for updates.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { executeTaskTurn } from '../task-executor-v2.js';
|
|
8
|
+
import { ingestTranscriptToTimeline } from '../transcript-ingestion.js';
|
|
9
|
+
import { debugLog } from '../utils.js';
|
|
10
|
+
|
|
11
|
+
export class PollingService {
|
|
12
|
+
constructor(state, config) {
|
|
13
|
+
this.name = 'polling';
|
|
14
|
+
this.state = state;
|
|
15
|
+
this.config = config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async start(ctx) {
|
|
19
|
+
this.ctx = ctx;
|
|
20
|
+
console.log('[PollingService] Started');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async stop() {
|
|
24
|
+
console.log('[PollingService] Stopped');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getStats() {
|
|
28
|
+
return {
|
|
29
|
+
ingestionsInProgress: this.state.ingestionInProgress.size
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Main polling loop iteration
|
|
35
|
+
*/
|
|
36
|
+
async poll() {
|
|
37
|
+
if (this.state.isShuttingDown) return;
|
|
38
|
+
|
|
39
|
+
for (const [session_id, sessionData] of this.state.sessions) {
|
|
40
|
+
this.state.sessionActivity.set(session_id, Date.now());
|
|
41
|
+
|
|
42
|
+
// 1) Poll Approvals
|
|
43
|
+
await this.pollApprovals(session_id);
|
|
44
|
+
|
|
45
|
+
// 2) Poll Inbox Messages
|
|
46
|
+
await this.pollInbox(session_id);
|
|
47
|
+
|
|
48
|
+
// 3) Poll Tasks
|
|
49
|
+
await this.pollTasks(session_id);
|
|
50
|
+
|
|
51
|
+
// 4) Transcript Ingestion
|
|
52
|
+
await this.runTranscriptIngestion(session_id, sessionData);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async pollApprovals(session_id) {
|
|
57
|
+
if (!this.config.relayApiUrl || !this.config.relayApiKey) return;
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(
|
|
60
|
+
`${this.config.relayApiUrl}/api/approvals?status=allowed&session_id=${session_id}`,
|
|
61
|
+
{ headers: { 'Authorization': `Bearer ${this.config.relayApiKey}` } }
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (response.ok) {
|
|
65
|
+
const approvals = await response.json();
|
|
66
|
+
for (const approval of approvals) {
|
|
67
|
+
if (this.state.approvalQueue.find(a => a.approval_id === approval.id)) continue;
|
|
68
|
+
if (this.state.executions.has(approval.id)) continue;
|
|
69
|
+
if (approval.acknowledgedAt || approval.decision_location) continue;
|
|
70
|
+
|
|
71
|
+
this.state.approvalQueue.push({
|
|
72
|
+
approval_id: approval.id,
|
|
73
|
+
session_id: approval.session_id,
|
|
74
|
+
tool_name: approval.tool_name,
|
|
75
|
+
tool_input: approval.tool_input,
|
|
76
|
+
queued_at: Date.now(),
|
|
77
|
+
tool_use_id: approval.tool_use_id || null,
|
|
78
|
+
conversation_context: approval.conversation_context || null
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
console.log(`[PollingService] Approval discovered: ${approval.id} (${approval.tool_name})`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error(`[PollingService] Approval polling error: ${err.message}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async pollInbox(session_id) {
|
|
90
|
+
if (!this.config.relayApiUrl || !this.config.relayApiKey) return;
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(
|
|
93
|
+
`${this.config.relayApiUrl}/api/messages/pending?session_id=${encodeURIComponent(session_id)}&agent_id=daemon`,
|
|
94
|
+
{ headers: { 'Authorization': `Bearer ${this.config.relayApiKey}` } }
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (response.ok) {
|
|
98
|
+
const inboxMessage = await response.json();
|
|
99
|
+
if (inboxMessage && inboxMessage.id && inboxMessage.text) {
|
|
100
|
+
// Note: handleInboxMessage logic should be here or in another service
|
|
101
|
+
// For now, we'll just log it. In a full refactor, we'd move the handler too.
|
|
102
|
+
console.log(`[PollingService] Inbox message discovered for ${session_id}: ${inboxMessage.id}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
// 404 is expected if no pending messages
|
|
107
|
+
if (err.status !== 404) {
|
|
108
|
+
console.error(`[PollingService] Inbox polling error: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async pollTasks(session_id) {
|
|
114
|
+
if (!this.config.relayApiUrl || !this.config.relayApiKey) return;
|
|
115
|
+
try {
|
|
116
|
+
const response = await fetch(
|
|
117
|
+
`${this.config.relayApiUrl}/api/sessions/${encodeURIComponent(session_id)}/tasks`,
|
|
118
|
+
{ headers: { 'Authorization': `Bearer ${this.config.relayApiKey}` } }
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (response.ok) {
|
|
122
|
+
const tasks = await response.json();
|
|
123
|
+
for (const task of tasks) {
|
|
124
|
+
if (task.status === 'stopped' || task.status === 'completed') continue;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const result = await executeTaskTurn({
|
|
128
|
+
task_id: task.id,
|
|
129
|
+
session_id: task.session_id,
|
|
130
|
+
config: {
|
|
131
|
+
relayApiUrl: this.config.relayApiUrl,
|
|
132
|
+
apiKey: this.config.relayApiKey
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (result.executed) {
|
|
137
|
+
console.log(`[PollingService] Task ${task.id.slice(0, 8)}... executed turn`);
|
|
138
|
+
}
|
|
139
|
+
} catch (err) {
|
|
140
|
+
console.error(`[PollingService] Task execution error: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
console.error(`[PollingService] Task polling error: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async runTranscriptIngestion(session_id, sessionData) {
|
|
150
|
+
if (this.state.ingestionInProgress.has(session_id)) return;
|
|
151
|
+
|
|
152
|
+
const claude_session_id = sessionData.claude_session_id || session_id;
|
|
153
|
+
const cwd = sessionData.cwd || process.cwd();
|
|
154
|
+
|
|
155
|
+
const promise = ingestTranscriptToTimeline({
|
|
156
|
+
claude_session_id,
|
|
157
|
+
parent_session_id: session_id,
|
|
158
|
+
task_id: null,
|
|
159
|
+
cwd,
|
|
160
|
+
config: {
|
|
161
|
+
relayApiUrl: this.config.relayApiUrl,
|
|
162
|
+
apiKey: this.config.relayApiKey
|
|
163
|
+
},
|
|
164
|
+
maxEvents: 100
|
|
165
|
+
}).catch(err => {
|
|
166
|
+
console.error(`[PollingService] Ingestion error: ${err.message}`);
|
|
167
|
+
}).finally(() => {
|
|
168
|
+
this.state.ingestionInProgress.delete(session_id);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
this.state.ingestionInProgress.set(session_id, promise);
|
|
172
|
+
}
|
|
173
|
+
}
|