replicas-engine 0.1.8 → 0.1.10
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/services/codex-manager.js +73 -14
- package/dist/utils/jsonl-reader.js +34 -9
- package/package.json +1 -1
|
@@ -24,28 +24,30 @@ export class CodexManager {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
async sendMessage(message) {
|
|
27
|
+
console.log(`[CodexManager] sendMessage called with message length: ${message.length}`);
|
|
27
28
|
if (!this.currentThread) {
|
|
28
29
|
if (this.currentThreadId) {
|
|
29
|
-
console.log(`Resuming thread ${this.currentThreadId}`);
|
|
30
|
+
console.log(`[CodexManager] Resuming existing thread: ${this.currentThreadId}`);
|
|
30
31
|
this.currentThread = this.codex.resumeThread(this.currentThreadId, {
|
|
31
32
|
workingDirectory: this.workingDirectory,
|
|
32
33
|
skipGitRepoCheck: true,
|
|
33
34
|
sandboxMode: 'danger-full-access',
|
|
34
35
|
});
|
|
36
|
+
console.log(`[CodexManager] Thread resumed successfully`);
|
|
35
37
|
}
|
|
36
38
|
else {
|
|
37
|
-
console.log('
|
|
38
|
-
|
|
39
|
+
console.log('[CodexManager] No existing thread, starting new thread');
|
|
40
|
+
console.log(`[CodexManager] Working directory: ${this.workingDirectory}`);
|
|
41
|
+
const thread = this.codex.startThread({
|
|
39
42
|
workingDirectory: this.workingDirectory,
|
|
40
43
|
skipGitRepoCheck: true,
|
|
41
44
|
sandboxMode: 'danger-full-access',
|
|
42
45
|
});
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log(`Thread started: ${this.currentThreadId}`);
|
|
46
|
-
}
|
|
46
|
+
this.currentThread = thread;
|
|
47
|
+
console.log(`[CodexManager] Thread object created, ID at creation: ${thread.id || 'null'}`);
|
|
47
48
|
// prime the thread with system instructions if thread is new
|
|
48
49
|
const workspaceName = process.env.WORKSPACE_NAME || 'workspace';
|
|
50
|
+
console.log(`[CodexManager] Workspace name from env: ${workspaceName}`);
|
|
49
51
|
const workspaceBranchSegment = workspaceName
|
|
50
52
|
.trim()
|
|
51
53
|
.toLowerCase()
|
|
@@ -53,62 +55,115 @@ export class CodexManager {
|
|
|
53
55
|
.replace(/^-+|-+$/g, '') || 'workspace';
|
|
54
56
|
const uuid = randomUUID().slice(0, 8);
|
|
55
57
|
const branchName = `replicas/${workspaceBranchSegment}-${uuid}`;
|
|
58
|
+
console.log(`[CodexManager] Generated branch name: ${branchName}`);
|
|
56
59
|
const systemMessage = `<replicas_important_instructions>When completing solutions, push your changes to branch ${branchName} and to origin. Greet the user.</replicas_important_instructions>`;
|
|
57
|
-
console.log('
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
console.log('[CodexManager] Starting thread priming with system instructions');
|
|
61
|
+
const primingStartTime = Date.now();
|
|
62
|
+
// Use runStreamed to capture thread.started event with thread_id
|
|
63
|
+
const streamedTurn = await this.currentThread.runStreamed(systemMessage);
|
|
64
|
+
console.log('[CodexManager] Processing priming stream events...');
|
|
65
|
+
for await (const event of streamedTurn.events) {
|
|
66
|
+
console.log(`[CodexManager] Event: ${event.type}`);
|
|
67
|
+
if (event.type === 'thread.started') {
|
|
68
|
+
this.currentThreadId = event.thread_id;
|
|
69
|
+
console.log(`[CodexManager] ✓ Thread ID captured from thread.started event: ${this.currentThreadId}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const primingDuration = Date.now() - primingStartTime;
|
|
73
|
+
console.log(`[CodexManager] Thread priming completed in ${primingDuration}ms`);
|
|
74
|
+
// Double-check thread ID is available
|
|
75
|
+
if (!this.currentThreadId && this.currentThread.id) {
|
|
76
|
+
this.currentThreadId = this.currentThread.id;
|
|
77
|
+
console.log(`[CodexManager] Thread ID fallback from thread.id property: ${this.currentThreadId}`);
|
|
78
|
+
}
|
|
79
|
+
if (!this.currentThreadId) {
|
|
80
|
+
console.error('[CodexManager] ERROR: Thread ID still not available after priming run');
|
|
81
|
+
console.error(`[CodexManager] thread.id = ${this.currentThread.id}`);
|
|
82
|
+
}
|
|
60
83
|
}
|
|
61
84
|
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(`[CodexManager] Using existing thread object for thread ID: ${this.currentThreadId}`);
|
|
87
|
+
}
|
|
88
|
+
console.log(`[CodexManager] Running user message on thread ${this.currentThreadId}`);
|
|
89
|
+
const messageStartTime = Date.now();
|
|
62
90
|
await this.currentThread.run(message);
|
|
91
|
+
const messageDuration = Date.now() - messageStartTime;
|
|
92
|
+
console.log(`[CodexManager] User message run completed in ${messageDuration}ms`);
|
|
63
93
|
}
|
|
64
94
|
async getHistory() {
|
|
95
|
+
console.log('[CodexManager] getHistory called');
|
|
65
96
|
if (!this.currentThreadId) {
|
|
97
|
+
console.log('[CodexManager] No active thread ID, returning empty history');
|
|
66
98
|
return {
|
|
67
99
|
thread_id: null,
|
|
68
100
|
events: [],
|
|
69
101
|
};
|
|
70
102
|
}
|
|
103
|
+
console.log(`[CodexManager] Looking for session file for thread: ${this.currentThreadId}`);
|
|
71
104
|
const sessionFile = await findSessionFile(this.currentThreadId);
|
|
72
105
|
if (!sessionFile) {
|
|
73
|
-
console.warn(`Session file not found for thread ${this.currentThreadId}`);
|
|
106
|
+
console.warn(`[CodexManager] WARNING: Session file not found for thread ${this.currentThreadId}`);
|
|
74
107
|
return {
|
|
75
108
|
thread_id: this.currentThreadId,
|
|
76
109
|
events: [],
|
|
77
110
|
};
|
|
78
111
|
}
|
|
79
|
-
console.log(`Reading session file: ${sessionFile}`);
|
|
112
|
+
console.log(`[CodexManager] Reading session file: ${sessionFile}`);
|
|
80
113
|
const events = await readJSONL(sessionFile);
|
|
114
|
+
console.log(`[CodexManager] Read ${events.length} events from session file`);
|
|
81
115
|
const filteredEvents = events.filter((event) => {
|
|
82
116
|
const eventStr = JSON.stringify(event);
|
|
83
117
|
return !eventStr.includes('<replicas_important_instructions>');
|
|
84
118
|
});
|
|
119
|
+
const filteredCount = events.length - filteredEvents.length;
|
|
120
|
+
if (filteredCount > 0) {
|
|
121
|
+
console.log(`[CodexManager] Filtered out ${filteredCount} priming events`);
|
|
122
|
+
}
|
|
123
|
+
console.log(`[CodexManager] Returning ${filteredEvents.length} events`);
|
|
85
124
|
return {
|
|
86
125
|
thread_id: this.currentThreadId,
|
|
87
126
|
events: filteredEvents,
|
|
88
127
|
};
|
|
89
128
|
}
|
|
90
129
|
async getStatus() {
|
|
130
|
+
console.log('[CodexManager] getStatus called');
|
|
91
131
|
let sessionFile = null;
|
|
92
132
|
if (this.currentThreadId) {
|
|
133
|
+
console.log(`[CodexManager] Checking for session file for thread: ${this.currentThreadId}`);
|
|
93
134
|
sessionFile = await findSessionFile(this.currentThreadId);
|
|
135
|
+
if (sessionFile) {
|
|
136
|
+
console.log(`[CodexManager] Session file found: ${sessionFile}`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log('[CodexManager] Session file not found');
|
|
140
|
+
}
|
|
94
141
|
}
|
|
95
|
-
|
|
142
|
+
else {
|
|
143
|
+
console.log('[CodexManager] No active thread ID');
|
|
144
|
+
}
|
|
145
|
+
const status = {
|
|
96
146
|
has_active_thread: this.currentThreadId !== null,
|
|
97
147
|
thread_id: this.currentThreadId,
|
|
98
148
|
session_file: sessionFile,
|
|
99
149
|
working_directory: this.workingDirectory,
|
|
100
150
|
};
|
|
151
|
+
console.log(`[CodexManager] Status: ${JSON.stringify(status)}`);
|
|
152
|
+
return status;
|
|
101
153
|
}
|
|
102
154
|
reset() {
|
|
103
|
-
console.log(
|
|
155
|
+
console.log(`[CodexManager] Resetting thread (was: ${this.currentThreadId})`);
|
|
104
156
|
this.currentThread = null;
|
|
105
157
|
this.currentThreadId = null;
|
|
158
|
+
console.log('[CodexManager] Thread reset complete');
|
|
106
159
|
}
|
|
107
160
|
getThreadId() {
|
|
108
161
|
return this.currentThreadId;
|
|
109
162
|
}
|
|
110
163
|
async getUpdates(since) {
|
|
164
|
+
console.log(`[CodexManager] getUpdates called with since: ${since}`);
|
|
111
165
|
if (!this.currentThreadId) {
|
|
166
|
+
console.log('[CodexManager] No active thread, returning empty updates');
|
|
112
167
|
return {
|
|
113
168
|
events: [],
|
|
114
169
|
isComplete: true,
|
|
@@ -116,20 +171,24 @@ export class CodexManager {
|
|
|
116
171
|
}
|
|
117
172
|
const sessionFile = await findSessionFile(this.currentThreadId);
|
|
118
173
|
if (!sessionFile) {
|
|
174
|
+
console.log('[CodexManager] Session file not found, returning empty updates');
|
|
119
175
|
return {
|
|
120
176
|
events: [],
|
|
121
177
|
isComplete: true,
|
|
122
178
|
};
|
|
123
179
|
}
|
|
124
180
|
const allEvents = await readJSONL(sessionFile);
|
|
181
|
+
console.log(`[CodexManager] Read ${allEvents.length} total events`);
|
|
125
182
|
// Filter events that occurred after the 'since' timestamp
|
|
126
183
|
const filteredEvents = allEvents.filter((event) => {
|
|
127
184
|
return event.timestamp > since;
|
|
128
185
|
});
|
|
186
|
+
console.log(`[CodexManager] Found ${filteredEvents.length} events since ${since}`);
|
|
129
187
|
// Check if thread is complete by looking for turn.completed or error events
|
|
130
188
|
const isComplete = this.currentThread === null ||
|
|
131
189
|
allEvents.some((event) => event.type === 'event_msg' &&
|
|
132
190
|
(event.payload?.type === 'turn.completed' || event.payload?.type === 'error'));
|
|
191
|
+
console.log(`[CodexManager] Thread complete: ${isComplete}`);
|
|
133
192
|
return {
|
|
134
193
|
events: filteredEvents,
|
|
135
194
|
isComplete,
|
|
@@ -2,30 +2,36 @@ import { readFile, readdir, stat } from 'fs/promises';
|
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
export async function readJSONL(filePath) {
|
|
5
|
+
console.log(`[jsonl-reader] readJSONL called for: ${filePath}`);
|
|
5
6
|
try {
|
|
6
7
|
const content = await readFile(filePath, 'utf-8');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
console.log(`[jsonl-reader] File read successfully, content length: ${content.length} bytes`);
|
|
9
|
+
const lines = content.split('\n').filter((line) => line.trim());
|
|
10
|
+
console.log(`[jsonl-reader] Found ${lines.length} non-empty lines`);
|
|
11
|
+
const events = lines
|
|
12
|
+
.map((line, index) => {
|
|
11
13
|
try {
|
|
12
14
|
return JSON.parse(line);
|
|
13
15
|
}
|
|
14
16
|
catch (e) {
|
|
15
|
-
console.error(`Failed to parse
|
|
17
|
+
console.error(`[jsonl-reader] Failed to parse line ${index + 1}: ${line.substring(0, 100)}...`, e);
|
|
16
18
|
return null;
|
|
17
19
|
}
|
|
18
20
|
})
|
|
19
21
|
.filter((event) => event !== null);
|
|
22
|
+
console.log(`[jsonl-reader] Successfully parsed ${events.length} events`);
|
|
23
|
+
return events;
|
|
20
24
|
}
|
|
21
25
|
catch (error) {
|
|
22
|
-
console.error(`Failed to read JSONL file ${filePath}:`, error);
|
|
26
|
+
console.error(`[jsonl-reader] ERROR: Failed to read JSONL file ${filePath}:`, error);
|
|
23
27
|
return [];
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
// Sessions are stored in ~/.codex/sessions/YYYY/MM/DD/rollout-*-{threadId}.jsonl
|
|
27
31
|
export async function findSessionFile(threadId) {
|
|
32
|
+
console.log(`[jsonl-reader] findSessionFile called for threadId: ${threadId}`);
|
|
28
33
|
const sessionsDir = join(homedir(), '.codex', 'sessions');
|
|
34
|
+
console.log(`[jsonl-reader] Sessions directory: ${sessionsDir}`);
|
|
29
35
|
try {
|
|
30
36
|
// current date for searching
|
|
31
37
|
const now = new Date();
|
|
@@ -33,9 +39,13 @@ export async function findSessionFile(threadId) {
|
|
|
33
39
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
34
40
|
const day = String(now.getDate()).padStart(2, '0');
|
|
35
41
|
const todayDir = join(sessionsDir, String(year), month, day);
|
|
42
|
+
console.log(`[jsonl-reader] Searching today's directory: ${todayDir}`);
|
|
36
43
|
const file = await findFileInDirectory(todayDir, threadId);
|
|
37
|
-
if (file)
|
|
44
|
+
if (file) {
|
|
45
|
+
console.log(`[jsonl-reader] Found file in today's directory: ${file}`);
|
|
38
46
|
return file;
|
|
47
|
+
}
|
|
48
|
+
console.log(`[jsonl-reader] Not found in today's directory, searching previous 7 days`);
|
|
39
49
|
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
40
50
|
const date = new Date(now);
|
|
41
51
|
date.setDate(date.getDate() - daysAgo);
|
|
@@ -43,32 +53,47 @@ export async function findSessionFile(threadId) {
|
|
|
43
53
|
const searchMonth = String(date.getMonth() + 1).padStart(2, '0');
|
|
44
54
|
const searchDay = String(date.getDate()).padStart(2, '0');
|
|
45
55
|
const searchDir = join(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
56
|
+
console.log(`[jsonl-reader] Searching ${daysAgo} day(s) ago: ${searchDir}`);
|
|
46
57
|
const file = await findFileInDirectory(searchDir, threadId);
|
|
47
|
-
if (file)
|
|
58
|
+
if (file) {
|
|
59
|
+
console.log(`[jsonl-reader] Found file ${daysAgo} day(s) ago: ${file}`);
|
|
48
60
|
return file;
|
|
61
|
+
}
|
|
49
62
|
}
|
|
63
|
+
console.log(`[jsonl-reader] Session file not found after searching 8 days`);
|
|
50
64
|
return null;
|
|
51
65
|
}
|
|
52
66
|
catch (error) {
|
|
53
|
-
console.error('
|
|
67
|
+
console.error('[jsonl-reader] ERROR finding session file:', error);
|
|
54
68
|
return null;
|
|
55
69
|
}
|
|
56
70
|
}
|
|
57
71
|
async function findFileInDirectory(directory, threadId) {
|
|
58
72
|
try {
|
|
73
|
+
console.log(`[jsonl-reader] Reading directory: ${directory}`);
|
|
59
74
|
const files = await readdir(directory);
|
|
75
|
+
console.log(`[jsonl-reader] Found ${files.length} files in directory`);
|
|
76
|
+
const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
77
|
+
console.log(`[jsonl-reader] ${jsonlFiles.length} JSONL files: ${jsonlFiles.join(', ')}`);
|
|
60
78
|
for (const file of files) {
|
|
61
79
|
if (file.endsWith('.jsonl') && file.includes(threadId)) {
|
|
62
80
|
const fullPath = join(directory, file);
|
|
81
|
+
console.log(`[jsonl-reader] Found matching file: ${file} -> ${fullPath}`);
|
|
63
82
|
const stats = await stat(fullPath);
|
|
64
83
|
if (stats.isFile()) {
|
|
84
|
+
console.log(`[jsonl-reader] Confirmed as regular file, returning: ${fullPath}`);
|
|
65
85
|
return fullPath;
|
|
66
86
|
}
|
|
87
|
+
else {
|
|
88
|
+
console.log(`[jsonl-reader] WARNING: Path exists but is not a regular file`);
|
|
89
|
+
}
|
|
67
90
|
}
|
|
68
91
|
}
|
|
92
|
+
console.log(`[jsonl-reader] No matching file found in ${directory}`);
|
|
69
93
|
return null;
|
|
70
94
|
}
|
|
71
95
|
catch (error) {
|
|
96
|
+
console.log(`[jsonl-reader] Directory read failed for ${directory}:`, error instanceof Error ? error.message : error);
|
|
72
97
|
return null;
|
|
73
98
|
}
|
|
74
99
|
}
|