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
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timeline Analyzer
|
|
3
|
+
*
|
|
4
|
+
* Analyzes timeline events to determine task state without in-memory tracking.
|
|
5
|
+
* This makes the daemon stateless and crash-resistant.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/daemon/timeline-analyzer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Fetch timeline events for a session
|
|
12
|
+
* @param {string} session_id - Teleportation session ID
|
|
13
|
+
* @param {Object} config - Configuration with relayApiUrl and apiKey
|
|
14
|
+
* @returns {Promise<Array>} Timeline events
|
|
15
|
+
*/
|
|
16
|
+
export async function fetchTimeline(session_id, config) {
|
|
17
|
+
const { relayApiUrl, apiKey } = config;
|
|
18
|
+
|
|
19
|
+
const response = await fetch(`${relayApiUrl}/api/timeline/${session_id}`, {
|
|
20
|
+
headers: { 'Authorization': `Bearer ${apiKey}` }
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`Failed to fetch timeline: ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
return data.events || [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Analyze timeline to determine current task state
|
|
33
|
+
* @param {Array} events - Timeline events (sorted chronologically)
|
|
34
|
+
* @param {string} task_id - Task ID to analyze
|
|
35
|
+
* @returns {Object} Current state analysis
|
|
36
|
+
*/
|
|
37
|
+
export function analyzeTaskState(events, task_id) {
|
|
38
|
+
// Filter events for this specific task
|
|
39
|
+
const taskEvents = events.filter(e => e.meta?.task_id === task_id);
|
|
40
|
+
|
|
41
|
+
if (taskEvents.length === 0) {
|
|
42
|
+
return {
|
|
43
|
+
state: 'not_started',
|
|
44
|
+
turn_count: 0,
|
|
45
|
+
ready_for_execution: true,
|
|
46
|
+
prompt: null,
|
|
47
|
+
claude_session_id: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Sort by timestamp to ensure correct order
|
|
52
|
+
taskEvents.sort((a, b) => a.timestamp - b.timestamp);
|
|
53
|
+
|
|
54
|
+
const lastEvent = taskEvents[taskEvents.length - 1];
|
|
55
|
+
|
|
56
|
+
// Count turns (each assistant_response indicates a completed turn)
|
|
57
|
+
const turn_count = taskEvents.filter(e =>
|
|
58
|
+
e.type === 'assistant_response' && e.source === 'autonomous_task'
|
|
59
|
+
).length;
|
|
60
|
+
|
|
61
|
+
// Find the latest claude_session_id from task execution
|
|
62
|
+
let claude_session_id = null;
|
|
63
|
+
for (let i = taskEvents.length - 1; i >= 0; i--) {
|
|
64
|
+
if (taskEvents[i].meta?.claude_session_id) {
|
|
65
|
+
claude_session_id = taskEvents[i].meta.claude_session_id;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check for pending approvals
|
|
71
|
+
const pendingApprovals = taskEvents.filter(e =>
|
|
72
|
+
e.type === 'approval_requested' &&
|
|
73
|
+
!taskEvents.some(later =>
|
|
74
|
+
later.type === 'approval_decided' &&
|
|
75
|
+
later.meta?.approval_id === e.meta?.approval_id
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
if (pendingApprovals.length > 0) {
|
|
80
|
+
return {
|
|
81
|
+
state: 'waiting_approval',
|
|
82
|
+
turn_count,
|
|
83
|
+
ready_for_execution: false,
|
|
84
|
+
reason: `Waiting for ${pendingApprovals.length} approval(s)`,
|
|
85
|
+
claude_session_id,
|
|
86
|
+
pending_approvals: pendingApprovals.length,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Check if task was explicitly paused
|
|
91
|
+
if (lastEvent.type === 'task_paused') {
|
|
92
|
+
return {
|
|
93
|
+
state: 'paused',
|
|
94
|
+
turn_count,
|
|
95
|
+
ready_for_execution: false,
|
|
96
|
+
reason: lastEvent.meta?.reason || 'Task paused',
|
|
97
|
+
claude_session_id,
|
|
98
|
+
waiting_for_user_message: true,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check if task was stopped
|
|
103
|
+
if (lastEvent.type === 'task_stopped') {
|
|
104
|
+
return {
|
|
105
|
+
state: 'stopped',
|
|
106
|
+
turn_count,
|
|
107
|
+
ready_for_execution: false,
|
|
108
|
+
reason: lastEvent.meta?.reason || 'Task stopped',
|
|
109
|
+
claude_session_id,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check if task completed successfully
|
|
114
|
+
if (lastEvent.type === 'task_completed') {
|
|
115
|
+
return {
|
|
116
|
+
state: 'completed',
|
|
117
|
+
turn_count,
|
|
118
|
+
ready_for_execution: false,
|
|
119
|
+
claude_session_id,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check if waiting for budget increase
|
|
124
|
+
if (lastEvent.type === 'task_budget_hit') {
|
|
125
|
+
return {
|
|
126
|
+
state: 'budget_exhausted',
|
|
127
|
+
turn_count,
|
|
128
|
+
ready_for_execution: false,
|
|
129
|
+
reason: 'Budget exhausted',
|
|
130
|
+
claude_session_id,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// If last event is assistant_response, ready for next turn
|
|
135
|
+
// Use stop_reason to determine if Claude is done (works like CLI)
|
|
136
|
+
if (lastEvent.type === 'assistant_response') {
|
|
137
|
+
const stopReason = lastEvent.meta?.stop_reason;
|
|
138
|
+
|
|
139
|
+
// Claude uses "end_turn" when it's done with the current turn and waiting for input
|
|
140
|
+
// This is the natural stopping point, just like in the CLI
|
|
141
|
+
if (stopReason === 'end_turn') {
|
|
142
|
+
return {
|
|
143
|
+
state: 'ready_for_next_turn',
|
|
144
|
+
turn_count,
|
|
145
|
+
ready_for_execution: true,
|
|
146
|
+
claude_session_id,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Other stop reasons (max_tokens, stop_sequence, etc.)
|
|
151
|
+
return {
|
|
152
|
+
state: 'ready_for_next_turn',
|
|
153
|
+
turn_count,
|
|
154
|
+
ready_for_execution: true,
|
|
155
|
+
claude_session_id,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Default: ready to execute if we have task_started
|
|
160
|
+
const hasStarted = taskEvents.some(e => e.type === 'task_started');
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
state: hasStarted ? 'in_progress' : 'not_started',
|
|
164
|
+
turn_count,
|
|
165
|
+
ready_for_execution: true,
|
|
166
|
+
claude_session_id,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Determine the next prompt to use for task execution
|
|
172
|
+
* @param {Object} state - Current state from analyzeTaskState
|
|
173
|
+
* @param {Object} task - Task object from Redis
|
|
174
|
+
* @returns {string|null} Prompt to use, or null if not ready
|
|
175
|
+
*/
|
|
176
|
+
export function getNextPrompt(state, task) {
|
|
177
|
+
if (!state.ready_for_execution) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// First turn: use original task description
|
|
182
|
+
if (state.turn_count === 0) {
|
|
183
|
+
return task.task;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// If paused and resumed with user message, use that message
|
|
187
|
+
if (task.pending_question) {
|
|
188
|
+
return task.pending_question;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Default continuation prompt
|
|
192
|
+
return 'Continue working on the task.';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if task should stop based on timeline analysis
|
|
197
|
+
* @param {Object} state - Current state from analyzeTaskState
|
|
198
|
+
* @param {number} maxTurns - Maximum allowed turns
|
|
199
|
+
* @returns {Object|null} Stop reason if should stop, null otherwise
|
|
200
|
+
*/
|
|
201
|
+
export function shouldStopTask(state, maxTurns = 100) {
|
|
202
|
+
if (state.state === 'stopped' || state.state === 'completed') {
|
|
203
|
+
return { should_stop: true, reason: state.state };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (state.turn_count >= maxTurns) {
|
|
207
|
+
return { should_stop: true, reason: `Max turns limit reached (${maxTurns})` };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (state.state === 'budget_exhausted') {
|
|
211
|
+
return { should_stop: true, reason: 'Budget exhausted' };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return null;
|
|
215
|
+
}
|