remcodex 0.1.0-beta.1
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/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/server/src/app.js +186 -0
- package/dist/server/src/cli.js +270 -0
- package/dist/server/src/controllers/codex-options.controller.js +199 -0
- package/dist/server/src/controllers/message.controller.js +21 -0
- package/dist/server/src/controllers/project.controller.js +44 -0
- package/dist/server/src/controllers/session.controller.js +175 -0
- package/dist/server/src/db/client.js +10 -0
- package/dist/server/src/db/migrations.js +32 -0
- package/dist/server/src/gateways/ws.gateway.js +60 -0
- package/dist/server/src/services/codex-app-server-runner.js +363 -0
- package/dist/server/src/services/codex-exec-runner.js +147 -0
- package/dist/server/src/services/codex-rollout-sync.js +977 -0
- package/dist/server/src/services/codex-runner.js +11 -0
- package/dist/server/src/services/codex-stream-events.js +478 -0
- package/dist/server/src/services/event-store.js +328 -0
- package/dist/server/src/services/project-manager.js +130 -0
- package/dist/server/src/services/pty-runner.js +72 -0
- package/dist/server/src/services/session-manager.js +1586 -0
- package/dist/server/src/services/session-timeline-service.js +181 -0
- package/dist/server/src/types/codex-launch.js +2 -0
- package/dist/server/src/types/models.js +37 -0
- package/dist/server/src/utils/ansi.js +143 -0
- package/dist/server/src/utils/codex-launch.js +102 -0
- package/dist/server/src/utils/codex-quota.js +179 -0
- package/dist/server/src/utils/codex-status.js +163 -0
- package/dist/server/src/utils/codex-ui-options.js +114 -0
- package/dist/server/src/utils/command.js +46 -0
- package/dist/server/src/utils/errors.js +16 -0
- package/dist/server/src/utils/ids.js +7 -0
- package/dist/server/src/utils/node-pty.js +29 -0
- package/package.json +36 -0
- package/scripts/fix-node-pty-helper.js +36 -0
- package/web/api.js +175 -0
- package/web/app.js +8082 -0
- package/web/components/composer.js +627 -0
- package/web/components/session-workbench.js +173 -0
- package/web/i18n/index.js +171 -0
- package/web/i18n/locales/de.js +50 -0
- package/web/i18n/locales/en.js +320 -0
- package/web/i18n/locales/es.js +50 -0
- package/web/i18n/locales/fr.js +50 -0
- package/web/i18n/locales/ja.js +50 -0
- package/web/i18n/locales/ko.js +50 -0
- package/web/i18n/locales/pt-BR.js +50 -0
- package/web/i18n/locales/ru.js +50 -0
- package/web/i18n/locales/zh-CN.js +320 -0
- package/web/i18n/locales/zh-Hant.js +53 -0
- package/web/index.html +23 -0
- package/web/message-rich-text.js +218 -0
- package/web/session-command-activity.js +980 -0
- package/web/session-event-adapter.js +826 -0
- package/web/session-timeline-reducer.js +728 -0
- package/web/session-timeline-renderer.js +656 -0
- package/web/session-ws.js +31 -0
- package/web/styles.css +5665 -0
- package/web/vendor/markdown-it.js +6969 -0
|
@@ -0,0 +1,977 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CodexRolloutSyncService = void 0;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const errors_1 = require("../utils/errors");
|
|
11
|
+
const ids_1 = require("../utils/ids");
|
|
12
|
+
function computeSourceRolloutHasOpenTurnFromRecords(records) {
|
|
13
|
+
const openTurnIds = new Set();
|
|
14
|
+
for (const record of records) {
|
|
15
|
+
const payload = record.payload && typeof record.payload === "object"
|
|
16
|
+
? record.payload
|
|
17
|
+
: {};
|
|
18
|
+
if (record.type === "event_msg" && payload.type === "task_started") {
|
|
19
|
+
const turnId = typeof payload.turn_id === "string" && payload.turn_id.trim() ? payload.turn_id.trim() : "";
|
|
20
|
+
if (turnId) {
|
|
21
|
+
openTurnIds.add(turnId);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (record.type === "event_msg" && (payload.type === "task_complete" || payload.type === "turn_aborted")) {
|
|
25
|
+
const turnId = typeof payload.turn_id === "string" && payload.turn_id.trim() ? payload.turn_id.trim() : "";
|
|
26
|
+
if (turnId) {
|
|
27
|
+
openTurnIds.delete(turnId);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return openTurnIds.size > 0;
|
|
32
|
+
}
|
|
33
|
+
function nowIso() {
|
|
34
|
+
return new Date().toISOString();
|
|
35
|
+
}
|
|
36
|
+
function resolveCodexHomeDir() {
|
|
37
|
+
const override = process.env.CODEX_HOME?.trim();
|
|
38
|
+
if (override) {
|
|
39
|
+
return node_path_1.default.resolve(override);
|
|
40
|
+
}
|
|
41
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".codex");
|
|
42
|
+
}
|
|
43
|
+
function readJsonlLines(filePath) {
|
|
44
|
+
return (0, node_fs_1.readFileSync)(filePath, "utf8")
|
|
45
|
+
.split(/\r?\n/)
|
|
46
|
+
.map((line) => line.trimEnd())
|
|
47
|
+
.filter(Boolean);
|
|
48
|
+
}
|
|
49
|
+
function parseJsonlRecords(filePath) {
|
|
50
|
+
return readJsonlLines(filePath).map((line) => JSON.parse(line));
|
|
51
|
+
}
|
|
52
|
+
function safeJsonParse(value) {
|
|
53
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(value);
|
|
58
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function shorten(text, max = 72) {
|
|
65
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
66
|
+
if (normalized.length <= max) {
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
return `${normalized.slice(0, max - 1)}…`;
|
|
70
|
+
}
|
|
71
|
+
function extractMessageText(content) {
|
|
72
|
+
if (!Array.isArray(content)) {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
return content
|
|
76
|
+
.map((part) => {
|
|
77
|
+
if (!part || typeof part !== "object") {
|
|
78
|
+
return "";
|
|
79
|
+
}
|
|
80
|
+
return typeof part.text === "string"
|
|
81
|
+
? String(part.text)
|
|
82
|
+
: "";
|
|
83
|
+
})
|
|
84
|
+
.join("")
|
|
85
|
+
.trim();
|
|
86
|
+
}
|
|
87
|
+
function extractReasoningSummary(summary) {
|
|
88
|
+
if (!Array.isArray(summary)) {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
return summary
|
|
92
|
+
.map((part) => {
|
|
93
|
+
if (!part || typeof part !== "object") {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
return typeof part.text === "string"
|
|
97
|
+
? String(part.text)
|
|
98
|
+
: "";
|
|
99
|
+
})
|
|
100
|
+
.join("")
|
|
101
|
+
.trim();
|
|
102
|
+
}
|
|
103
|
+
function parseExecOutput(output) {
|
|
104
|
+
const text = String(output || "");
|
|
105
|
+
const exitMatch = text.match(/Process exited with code (-?\d+)/);
|
|
106
|
+
const durationMatch = text.match(/Wall time: ([0-9.]+) seconds/);
|
|
107
|
+
const commandMatch = text.match(/^Command:\s+(.+)$/m);
|
|
108
|
+
const splitMarker = "\nOutput:\n";
|
|
109
|
+
const splitIndex = text.indexOf(splitMarker);
|
|
110
|
+
const outputText = splitIndex >= 0 ? text.slice(splitIndex + splitMarker.length) : text;
|
|
111
|
+
return {
|
|
112
|
+
commandLine: commandMatch?.[1] ?? null,
|
|
113
|
+
exitCode: exitMatch ? Number.parseInt(exitMatch[1], 10) : null,
|
|
114
|
+
durationMs: durationMatch
|
|
115
|
+
? Math.round(Number.parseFloat(durationMatch[1]) * 1000)
|
|
116
|
+
: null,
|
|
117
|
+
outputText,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function buildCommandPayload(toolName, args) {
|
|
121
|
+
if (toolName === "exec_command") {
|
|
122
|
+
return {
|
|
123
|
+
command: typeof args.cmd === "string" ? args.cmd : "exec_command",
|
|
124
|
+
cwd: typeof args.workdir === "string" ? args.workdir : null,
|
|
125
|
+
justification: typeof args.justification === "string" ? args.justification : null,
|
|
126
|
+
sandboxMode: typeof args.sandbox_permissions === "string" ? args.sandbox_permissions : null,
|
|
127
|
+
approvalRequired: args.sandbox_permissions === "require_escalated",
|
|
128
|
+
grantRoot: null,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
command: `${toolName} ${shorten(JSON.stringify(args || {}), 160)}`.trim(),
|
|
133
|
+
cwd: typeof args.workdir === "string" ? args.workdir : null,
|
|
134
|
+
justification: null,
|
|
135
|
+
sandboxMode: null,
|
|
136
|
+
approvalRequired: false,
|
|
137
|
+
grantRoot: null,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function buildPatchStartPayload(toolName, input) {
|
|
141
|
+
return {
|
|
142
|
+
summary: `${toolName} ${shorten(String(input || ""), 160)}`.trim(),
|
|
143
|
+
target: null,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function buildTokenPayload(payload, timestamp) {
|
|
147
|
+
const info = payload.info && typeof payload.info === "object"
|
|
148
|
+
? payload.info
|
|
149
|
+
: {};
|
|
150
|
+
return {
|
|
151
|
+
rateLimits: payload.rate_limits && typeof payload.rate_limits === "object"
|
|
152
|
+
? payload.rate_limits
|
|
153
|
+
: payload.rateLimits && typeof payload.rateLimits === "object"
|
|
154
|
+
? payload.rateLimits
|
|
155
|
+
: {},
|
|
156
|
+
totalTokenUsage: info.total_token_usage && typeof info.total_token_usage === "object"
|
|
157
|
+
? info.total_token_usage
|
|
158
|
+
: {},
|
|
159
|
+
lastTokenUsage: info.last_token_usage && typeof info.last_token_usage === "object"
|
|
160
|
+
? info.last_token_usage
|
|
161
|
+
: {},
|
|
162
|
+
modelContextWindow: typeof info.model_context_window === "number" ? info.model_context_window : undefined,
|
|
163
|
+
receivedAt: timestamp,
|
|
164
|
+
rawPayload: payload,
|
|
165
|
+
source: "rollout",
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function runInTransaction(db, callback) {
|
|
169
|
+
db.exec("BEGIN");
|
|
170
|
+
try {
|
|
171
|
+
callback();
|
|
172
|
+
db.exec("COMMIT");
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
db.exec("ROLLBACK");
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function scanRolloutPaths(root) {
|
|
180
|
+
if (!(0, node_fs_1.existsSync)(root)) {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
const results = [];
|
|
184
|
+
const visit = (currentPath) => {
|
|
185
|
+
const entries = (0, node_fs_1.readdirSync)(currentPath, { withFileTypes: true });
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
const entryPath = node_path_1.default.join(currentPath, entry.name);
|
|
188
|
+
if (entry.isDirectory()) {
|
|
189
|
+
visit(entryPath);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
if (entry.isFile() && /^rollout-.*\.jsonl$/i.test(entry.name)) {
|
|
193
|
+
results.push(entryPath);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
visit(root);
|
|
198
|
+
return results.sort((left, right) => {
|
|
199
|
+
const leftMtime = (0, node_fs_1.statSync)(left).mtimeMs;
|
|
200
|
+
const rightMtime = (0, node_fs_1.statSync)(right).mtimeMs;
|
|
201
|
+
return rightMtime - leftMtime;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function translateRolloutRecords(records, emitFromRecordIndex = 0) {
|
|
205
|
+
const sessionMeta = records.find((record) => record.type === "session_meta")?.payload;
|
|
206
|
+
const codexSessionId = String(sessionMeta?.id || "").trim();
|
|
207
|
+
if (!codexSessionId) {
|
|
208
|
+
throw new errors_1.AppError(400, "Unable to find session_meta.id in rollout.");
|
|
209
|
+
}
|
|
210
|
+
const workspacePath = String(sessionMeta?.cwd || process.cwd()).trim() || process.cwd();
|
|
211
|
+
const semanticEvents = [];
|
|
212
|
+
let currentTurnId = null;
|
|
213
|
+
let activeTurnId = null;
|
|
214
|
+
let assistantCounter = 0;
|
|
215
|
+
let reasoningCounter = 0;
|
|
216
|
+
const commandStarts = new Map();
|
|
217
|
+
const lastFinalAssistantMessageIdByTurn = new Map();
|
|
218
|
+
let firstUserMessage = "";
|
|
219
|
+
function appendSemantic(recordIndex, event) {
|
|
220
|
+
if (recordIndex < emitFromRecordIndex) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
semanticEvents.push(event);
|
|
224
|
+
}
|
|
225
|
+
function downgradePreviousFinalAssistant(turnId) {
|
|
226
|
+
if (!turnId) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
const previousMessageId = lastFinalAssistantMessageIdByTurn.get(turnId);
|
|
230
|
+
if (!previousMessageId) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
for (const event of semanticEvents) {
|
|
234
|
+
if (event.turnId === turnId &&
|
|
235
|
+
event.messageId === previousMessageId &&
|
|
236
|
+
(event.type === "message.assistant.start" ||
|
|
237
|
+
event.type === "message.assistant.delta" ||
|
|
238
|
+
event.type === "message.assistant.end")) {
|
|
239
|
+
event.phase = "commentary";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
244
|
+
const record = records[index];
|
|
245
|
+
const timestamp = typeof record.timestamp === "string" && record.timestamp.trim()
|
|
246
|
+
? record.timestamp.trim()
|
|
247
|
+
: nowIso();
|
|
248
|
+
if (record.type === "turn_context") {
|
|
249
|
+
const payload = record.payload && typeof record.payload === "object"
|
|
250
|
+
? record.payload
|
|
251
|
+
: {};
|
|
252
|
+
currentTurnId =
|
|
253
|
+
typeof payload.turn_id === "string" && payload.turn_id.trim()
|
|
254
|
+
? payload.turn_id.trim()
|
|
255
|
+
: currentTurnId;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (record.type === "event_msg") {
|
|
259
|
+
const payload = record.payload && typeof record.payload === "object"
|
|
260
|
+
? record.payload
|
|
261
|
+
: {};
|
|
262
|
+
switch (payload.type) {
|
|
263
|
+
case "task_started": {
|
|
264
|
+
const nextTurnId = typeof payload.turn_id === "string" && payload.turn_id.trim()
|
|
265
|
+
? payload.turn_id.trim()
|
|
266
|
+
: currentTurnId;
|
|
267
|
+
if (activeTurnId && nextTurnId && activeTurnId !== nextTurnId) {
|
|
268
|
+
appendSemantic(index, {
|
|
269
|
+
type: "turn.completed",
|
|
270
|
+
turnId: activeTurnId,
|
|
271
|
+
messageId: null,
|
|
272
|
+
callId: null,
|
|
273
|
+
requestId: null,
|
|
274
|
+
phase: null,
|
|
275
|
+
stream: null,
|
|
276
|
+
payload: {
|
|
277
|
+
completedAt: timestamp,
|
|
278
|
+
reason: "implicit_rollover",
|
|
279
|
+
},
|
|
280
|
+
timestamp,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
currentTurnId = nextTurnId;
|
|
284
|
+
activeTurnId = nextTurnId;
|
|
285
|
+
appendSemantic(index, {
|
|
286
|
+
type: "turn.started",
|
|
287
|
+
turnId: currentTurnId,
|
|
288
|
+
messageId: null,
|
|
289
|
+
callId: null,
|
|
290
|
+
requestId: null,
|
|
291
|
+
phase: null,
|
|
292
|
+
stream: null,
|
|
293
|
+
payload: {
|
|
294
|
+
createdAt: timestamp,
|
|
295
|
+
},
|
|
296
|
+
timestamp,
|
|
297
|
+
});
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
case "user_message": {
|
|
301
|
+
const text = typeof payload.message === "string" ? payload.message.trim() : "";
|
|
302
|
+
if (!text) {
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (!firstUserMessage) {
|
|
306
|
+
firstUserMessage = text;
|
|
307
|
+
}
|
|
308
|
+
appendSemantic(index, {
|
|
309
|
+
type: "message.user",
|
|
310
|
+
turnId: currentTurnId,
|
|
311
|
+
messageId: null,
|
|
312
|
+
callId: null,
|
|
313
|
+
requestId: null,
|
|
314
|
+
phase: null,
|
|
315
|
+
stream: null,
|
|
316
|
+
payload: { text },
|
|
317
|
+
timestamp,
|
|
318
|
+
});
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
case "token_count": {
|
|
322
|
+
appendSemantic(index, {
|
|
323
|
+
type: "token_count",
|
|
324
|
+
turnId: currentTurnId,
|
|
325
|
+
messageId: null,
|
|
326
|
+
callId: null,
|
|
327
|
+
requestId: null,
|
|
328
|
+
phase: null,
|
|
329
|
+
stream: null,
|
|
330
|
+
payload: buildTokenPayload(payload, timestamp),
|
|
331
|
+
timestamp,
|
|
332
|
+
});
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
case "task_complete": {
|
|
336
|
+
const turnId = typeof payload.turn_id === "string" && payload.turn_id.trim()
|
|
337
|
+
? payload.turn_id.trim()
|
|
338
|
+
: currentTurnId;
|
|
339
|
+
if (turnId && activeTurnId === turnId) {
|
|
340
|
+
activeTurnId = null;
|
|
341
|
+
}
|
|
342
|
+
appendSemantic(index, {
|
|
343
|
+
type: "turn.completed",
|
|
344
|
+
turnId,
|
|
345
|
+
messageId: null,
|
|
346
|
+
callId: null,
|
|
347
|
+
requestId: null,
|
|
348
|
+
phase: null,
|
|
349
|
+
stream: null,
|
|
350
|
+
payload: {
|
|
351
|
+
completedAt: timestamp,
|
|
352
|
+
reason: null,
|
|
353
|
+
},
|
|
354
|
+
timestamp,
|
|
355
|
+
});
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
case "turn_aborted": {
|
|
359
|
+
const turnId = typeof payload.turn_id === "string" && payload.turn_id.trim()
|
|
360
|
+
? payload.turn_id.trim()
|
|
361
|
+
: currentTurnId;
|
|
362
|
+
if (turnId && activeTurnId === turnId) {
|
|
363
|
+
activeTurnId = null;
|
|
364
|
+
}
|
|
365
|
+
appendSemantic(index, {
|
|
366
|
+
type: "turn.aborted",
|
|
367
|
+
turnId,
|
|
368
|
+
messageId: null,
|
|
369
|
+
callId: null,
|
|
370
|
+
requestId: null,
|
|
371
|
+
phase: null,
|
|
372
|
+
stream: null,
|
|
373
|
+
payload: {
|
|
374
|
+
abortedAt: timestamp,
|
|
375
|
+
reason: typeof payload.reason === "string" ? payload.reason : null,
|
|
376
|
+
},
|
|
377
|
+
timestamp,
|
|
378
|
+
});
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
case "agent_reasoning": {
|
|
382
|
+
const text = typeof payload.text === "string" ? payload.text.trim() : "";
|
|
383
|
+
if (!text) {
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
const messageId = `msg_reasoning_${String((reasoningCounter += 1)).padStart(4, "0")}`;
|
|
387
|
+
appendSemantic(index, {
|
|
388
|
+
type: "reasoning.start",
|
|
389
|
+
turnId: currentTurnId,
|
|
390
|
+
messageId,
|
|
391
|
+
callId: null,
|
|
392
|
+
requestId: null,
|
|
393
|
+
phase: null,
|
|
394
|
+
stream: null,
|
|
395
|
+
payload: {
|
|
396
|
+
summary: "",
|
|
397
|
+
},
|
|
398
|
+
timestamp,
|
|
399
|
+
});
|
|
400
|
+
appendSemantic(index, {
|
|
401
|
+
type: "reasoning.delta",
|
|
402
|
+
turnId: currentTurnId,
|
|
403
|
+
messageId,
|
|
404
|
+
callId: null,
|
|
405
|
+
requestId: null,
|
|
406
|
+
phase: null,
|
|
407
|
+
stream: null,
|
|
408
|
+
payload: {
|
|
409
|
+
textDelta: text,
|
|
410
|
+
summary: text,
|
|
411
|
+
},
|
|
412
|
+
timestamp,
|
|
413
|
+
});
|
|
414
|
+
appendSemantic(index, {
|
|
415
|
+
type: "reasoning.end",
|
|
416
|
+
turnId: currentTurnId,
|
|
417
|
+
messageId,
|
|
418
|
+
callId: null,
|
|
419
|
+
requestId: null,
|
|
420
|
+
phase: null,
|
|
421
|
+
stream: null,
|
|
422
|
+
payload: {
|
|
423
|
+
summary: text,
|
|
424
|
+
},
|
|
425
|
+
timestamp,
|
|
426
|
+
});
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
default:
|
|
430
|
+
break;
|
|
431
|
+
}
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
if (record.type !== "response_item") {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const payload = record.payload && typeof record.payload === "object"
|
|
438
|
+
? record.payload
|
|
439
|
+
: {};
|
|
440
|
+
switch (payload.type) {
|
|
441
|
+
case "message": {
|
|
442
|
+
if (payload.role !== "assistant") {
|
|
443
|
+
break;
|
|
444
|
+
}
|
|
445
|
+
const text = extractMessageText(payload.content);
|
|
446
|
+
if (!text) {
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
const phase = payload.phase === "commentary" ? "commentary" : "final_answer";
|
|
450
|
+
const messageId = `msg_assistant_${String((assistantCounter += 1)).padStart(4, "0")}`;
|
|
451
|
+
if (phase === "final_answer") {
|
|
452
|
+
downgradePreviousFinalAssistant(currentTurnId);
|
|
453
|
+
if (currentTurnId) {
|
|
454
|
+
lastFinalAssistantMessageIdByTurn.set(currentTurnId, messageId);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
appendSemantic(index, {
|
|
458
|
+
type: "message.assistant.start",
|
|
459
|
+
turnId: currentTurnId,
|
|
460
|
+
messageId,
|
|
461
|
+
callId: null,
|
|
462
|
+
requestId: null,
|
|
463
|
+
phase,
|
|
464
|
+
stream: null,
|
|
465
|
+
payload: { text: "" },
|
|
466
|
+
timestamp,
|
|
467
|
+
});
|
|
468
|
+
appendSemantic(index, {
|
|
469
|
+
type: "message.assistant.delta",
|
|
470
|
+
turnId: currentTurnId,
|
|
471
|
+
messageId,
|
|
472
|
+
callId: null,
|
|
473
|
+
requestId: null,
|
|
474
|
+
phase,
|
|
475
|
+
stream: null,
|
|
476
|
+
payload: { textDelta: text },
|
|
477
|
+
timestamp,
|
|
478
|
+
});
|
|
479
|
+
appendSemantic(index, {
|
|
480
|
+
type: "message.assistant.end",
|
|
481
|
+
turnId: currentTurnId,
|
|
482
|
+
messageId,
|
|
483
|
+
callId: null,
|
|
484
|
+
requestId: null,
|
|
485
|
+
phase,
|
|
486
|
+
stream: null,
|
|
487
|
+
payload: {
|
|
488
|
+
text,
|
|
489
|
+
finishReason: null,
|
|
490
|
+
},
|
|
491
|
+
timestamp,
|
|
492
|
+
});
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
case "reasoning": {
|
|
496
|
+
const summary = extractReasoningSummary(payload.summary);
|
|
497
|
+
if (!summary) {
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
const messageId = `msg_reasoning_${String((reasoningCounter += 1)).padStart(4, "0")}`;
|
|
501
|
+
appendSemantic(index, {
|
|
502
|
+
type: "reasoning.start",
|
|
503
|
+
turnId: currentTurnId,
|
|
504
|
+
messageId,
|
|
505
|
+
callId: null,
|
|
506
|
+
requestId: null,
|
|
507
|
+
phase: null,
|
|
508
|
+
stream: null,
|
|
509
|
+
payload: {
|
|
510
|
+
summary: "",
|
|
511
|
+
},
|
|
512
|
+
timestamp,
|
|
513
|
+
});
|
|
514
|
+
appendSemantic(index, {
|
|
515
|
+
type: "reasoning.delta",
|
|
516
|
+
turnId: currentTurnId,
|
|
517
|
+
messageId,
|
|
518
|
+
callId: null,
|
|
519
|
+
requestId: null,
|
|
520
|
+
phase: null,
|
|
521
|
+
stream: null,
|
|
522
|
+
payload: {
|
|
523
|
+
textDelta: summary,
|
|
524
|
+
summary,
|
|
525
|
+
},
|
|
526
|
+
timestamp,
|
|
527
|
+
});
|
|
528
|
+
appendSemantic(index, {
|
|
529
|
+
type: "reasoning.end",
|
|
530
|
+
turnId: currentTurnId,
|
|
531
|
+
messageId,
|
|
532
|
+
callId: null,
|
|
533
|
+
requestId: null,
|
|
534
|
+
phase: null,
|
|
535
|
+
stream: null,
|
|
536
|
+
payload: {
|
|
537
|
+
summary,
|
|
538
|
+
},
|
|
539
|
+
timestamp,
|
|
540
|
+
});
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
case "function_call": {
|
|
544
|
+
const callId = typeof payload.call_id === "string" && payload.call_id.trim()
|
|
545
|
+
? payload.call_id.trim()
|
|
546
|
+
: `call_${index}`;
|
|
547
|
+
const args = safeJsonParse(payload.arguments) || {};
|
|
548
|
+
const commandPayload = buildCommandPayload(typeof payload.name === "string" ? payload.name : "tool_call", args);
|
|
549
|
+
commandStarts.set(callId, {
|
|
550
|
+
commandPayload: {
|
|
551
|
+
command: commandPayload.command,
|
|
552
|
+
cwd: commandPayload.cwd,
|
|
553
|
+
},
|
|
554
|
+
});
|
|
555
|
+
appendSemantic(index, {
|
|
556
|
+
type: "command.start",
|
|
557
|
+
turnId: currentTurnId,
|
|
558
|
+
messageId: null,
|
|
559
|
+
callId,
|
|
560
|
+
requestId: null,
|
|
561
|
+
phase: null,
|
|
562
|
+
stream: null,
|
|
563
|
+
payload: commandPayload,
|
|
564
|
+
timestamp,
|
|
565
|
+
});
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
case "function_call_output": {
|
|
569
|
+
const callId = typeof payload.call_id === "string" && payload.call_id.trim()
|
|
570
|
+
? payload.call_id.trim()
|
|
571
|
+
: `call_${index}`;
|
|
572
|
+
const started = commandStarts.get(callId) || null;
|
|
573
|
+
const parsed = parseExecOutput(payload.output);
|
|
574
|
+
if (parsed.outputText) {
|
|
575
|
+
appendSemantic(index, {
|
|
576
|
+
type: "command.output.delta",
|
|
577
|
+
turnId: currentTurnId,
|
|
578
|
+
messageId: null,
|
|
579
|
+
callId,
|
|
580
|
+
requestId: null,
|
|
581
|
+
phase: null,
|
|
582
|
+
stream: "stdout",
|
|
583
|
+
payload: {
|
|
584
|
+
stream: "stdout",
|
|
585
|
+
textDelta: parsed.outputText,
|
|
586
|
+
},
|
|
587
|
+
timestamp,
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
appendSemantic(index, {
|
|
591
|
+
type: "command.end",
|
|
592
|
+
turnId: currentTurnId,
|
|
593
|
+
messageId: null,
|
|
594
|
+
callId,
|
|
595
|
+
requestId: null,
|
|
596
|
+
phase: null,
|
|
597
|
+
stream: null,
|
|
598
|
+
payload: {
|
|
599
|
+
command: parsed.commandLine || started?.commandPayload.command || null,
|
|
600
|
+
cwd: started?.commandPayload.cwd || null,
|
|
601
|
+
status: parsed.exitCode == null
|
|
602
|
+
? "completed"
|
|
603
|
+
: parsed.exitCode === 0
|
|
604
|
+
? "completed"
|
|
605
|
+
: "failed",
|
|
606
|
+
exitCode: parsed.exitCode,
|
|
607
|
+
durationMs: parsed.durationMs,
|
|
608
|
+
rejected: false,
|
|
609
|
+
},
|
|
610
|
+
timestamp,
|
|
611
|
+
});
|
|
612
|
+
break;
|
|
613
|
+
}
|
|
614
|
+
case "custom_tool_call": {
|
|
615
|
+
const callId = typeof payload.call_id === "string" && payload.call_id.trim()
|
|
616
|
+
? payload.call_id.trim()
|
|
617
|
+
: `patch_${index}`;
|
|
618
|
+
appendSemantic(index, {
|
|
619
|
+
type: "patch.start",
|
|
620
|
+
turnId: currentTurnId,
|
|
621
|
+
messageId: null,
|
|
622
|
+
callId,
|
|
623
|
+
requestId: null,
|
|
624
|
+
phase: null,
|
|
625
|
+
stream: null,
|
|
626
|
+
payload: buildPatchStartPayload(typeof payload.name === "string" ? payload.name : "custom_tool", payload.input),
|
|
627
|
+
timestamp,
|
|
628
|
+
});
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
case "custom_tool_call_output": {
|
|
632
|
+
const callId = typeof payload.call_id === "string" && payload.call_id.trim()
|
|
633
|
+
? payload.call_id.trim()
|
|
634
|
+
: `patch_${index}`;
|
|
635
|
+
const outputPayload = safeJsonParse(payload.output) || {};
|
|
636
|
+
const text = typeof outputPayload.output === "string"
|
|
637
|
+
? outputPayload.output
|
|
638
|
+
: typeof payload.output === "string"
|
|
639
|
+
? payload.output
|
|
640
|
+
: "";
|
|
641
|
+
const metadata = outputPayload.metadata && typeof outputPayload.metadata === "object"
|
|
642
|
+
? outputPayload.metadata
|
|
643
|
+
: {};
|
|
644
|
+
const durationSeconds = typeof metadata.duration_seconds === "number"
|
|
645
|
+
? metadata.duration_seconds
|
|
646
|
+
: Number(metadata.duration_seconds);
|
|
647
|
+
const exitCode = typeof metadata.exit_code === "number"
|
|
648
|
+
? metadata.exit_code
|
|
649
|
+
: Number.isFinite(Number(metadata.exit_code))
|
|
650
|
+
? Number(metadata.exit_code)
|
|
651
|
+
: null;
|
|
652
|
+
if (text) {
|
|
653
|
+
appendSemantic(index, {
|
|
654
|
+
type: "patch.output.delta",
|
|
655
|
+
turnId: currentTurnId,
|
|
656
|
+
messageId: null,
|
|
657
|
+
callId,
|
|
658
|
+
requestId: null,
|
|
659
|
+
phase: null,
|
|
660
|
+
stream: null,
|
|
661
|
+
payload: { textDelta: text },
|
|
662
|
+
timestamp,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
appendSemantic(index, {
|
|
666
|
+
type: "patch.end",
|
|
667
|
+
turnId: currentTurnId,
|
|
668
|
+
messageId: null,
|
|
669
|
+
callId,
|
|
670
|
+
requestId: null,
|
|
671
|
+
phase: null,
|
|
672
|
+
stream: null,
|
|
673
|
+
payload: {
|
|
674
|
+
status: typeof exitCode === "number"
|
|
675
|
+
? exitCode === 0
|
|
676
|
+
? "completed"
|
|
677
|
+
: "failed"
|
|
678
|
+
: "completed",
|
|
679
|
+
durationMs: Number.isFinite(durationSeconds) && durationSeconds >= 0
|
|
680
|
+
? Math.round(durationSeconds * 1000)
|
|
681
|
+
: null,
|
|
682
|
+
success: typeof exitCode === "number" ? exitCode === 0 : true,
|
|
683
|
+
},
|
|
684
|
+
timestamp,
|
|
685
|
+
});
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
default:
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
const firstTimestamp = semanticEvents[0]?.timestamp || nowIso();
|
|
693
|
+
const lastTimestamp = semanticEvents[semanticEvents.length - 1]?.timestamp || firstTimestamp || nowIso();
|
|
694
|
+
const sourceRolloutHasOpenTurn = computeSourceRolloutHasOpenTurnFromRecords(records);
|
|
695
|
+
return {
|
|
696
|
+
codexSessionId,
|
|
697
|
+
workspacePath,
|
|
698
|
+
sessionTitle: firstUserMessage
|
|
699
|
+
? `Imported Codex: ${shorten(firstUserMessage, 60)}`
|
|
700
|
+
: `Imported Codex Session ${codexSessionId.slice(0, 8)}`,
|
|
701
|
+
sessionStatus: "waiting_input",
|
|
702
|
+
sourceRolloutHasOpenTurn,
|
|
703
|
+
firstTimestamp,
|
|
704
|
+
lastTimestamp,
|
|
705
|
+
events: semanticEvents,
|
|
706
|
+
rawCursor: records.length,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
class CodexRolloutSyncService {
|
|
710
|
+
db;
|
|
711
|
+
constructor(db) {
|
|
712
|
+
this.db = db;
|
|
713
|
+
}
|
|
714
|
+
listImportableSessions(limit = 20) {
|
|
715
|
+
const rolloutRoot = node_path_1.default.join(resolveCodexHomeDir(), "sessions");
|
|
716
|
+
const rolloutPaths = scanRolloutPaths(rolloutRoot).slice(0, Math.max(1, limit));
|
|
717
|
+
const importedByPath = new Map(this.db
|
|
718
|
+
.prepare(`
|
|
719
|
+
SELECT id AS session_id, source_rollout_path, source_last_synced_at AS imported_at
|
|
720
|
+
FROM sessions
|
|
721
|
+
WHERE source_kind = 'imported_rollout'
|
|
722
|
+
AND source_rollout_path IS NOT NULL
|
|
723
|
+
`)
|
|
724
|
+
.all()
|
|
725
|
+
.filter((row) => typeof row.source_rollout_path === "string" && row.source_rollout_path)
|
|
726
|
+
.map((row) => [
|
|
727
|
+
node_path_1.default.resolve(String(row.source_rollout_path)),
|
|
728
|
+
{ session_id: row.session_id, imported_at: row.imported_at },
|
|
729
|
+
]));
|
|
730
|
+
const nativeThreadIds = new Set(this.db
|
|
731
|
+
.prepare(`
|
|
732
|
+
SELECT codex_thread_id
|
|
733
|
+
FROM sessions
|
|
734
|
+
WHERE source_kind = 'native'
|
|
735
|
+
AND codex_thread_id IS NOT NULL
|
|
736
|
+
AND codex_thread_id != ''
|
|
737
|
+
`)
|
|
738
|
+
.all()
|
|
739
|
+
.map((row) => String(row.codex_thread_id || "").trim())
|
|
740
|
+
.filter(Boolean));
|
|
741
|
+
return rolloutPaths
|
|
742
|
+
.map((rolloutPath) => {
|
|
743
|
+
const records = parseJsonlRecords(rolloutPath);
|
|
744
|
+
const sessionMeta = records.find((record) => record.type === "session_meta")?.payload;
|
|
745
|
+
const codexSessionId = String(sessionMeta?.id || "").trim();
|
|
746
|
+
const cwd = typeof sessionMeta?.cwd === "string" && sessionMeta.cwd.trim()
|
|
747
|
+
? sessionMeta.cwd.trim()
|
|
748
|
+
: null;
|
|
749
|
+
const firstUserEvent = records.find((record) => record.type === "event_msg" &&
|
|
750
|
+
record.payload &&
|
|
751
|
+
typeof record.payload === "object" &&
|
|
752
|
+
record.payload.type === "user_message");
|
|
753
|
+
const firstUserMessage = firstUserEvent &&
|
|
754
|
+
typeof firstUserEvent.payload.message === "string"
|
|
755
|
+
? String(firstUserEvent.payload.message).trim()
|
|
756
|
+
: "";
|
|
757
|
+
const resolvedPath = node_path_1.default.resolve(rolloutPath);
|
|
758
|
+
const imported = importedByPath.get(resolvedPath);
|
|
759
|
+
return {
|
|
760
|
+
codexSessionId,
|
|
761
|
+
rolloutPath: resolvedPath,
|
|
762
|
+
cwd,
|
|
763
|
+
updatedAt: (0, node_fs_1.statSync)(rolloutPath).mtime.toISOString(),
|
|
764
|
+
title: firstUserMessage ? shorten(firstUserMessage, 80) : null,
|
|
765
|
+
importedSessionId: imported?.session_id ?? null,
|
|
766
|
+
importedAt: imported?.imported_at ?? null,
|
|
767
|
+
};
|
|
768
|
+
})
|
|
769
|
+
.filter((item) => !item.codexSessionId || !nativeThreadIds.has(item.codexSessionId));
|
|
770
|
+
}
|
|
771
|
+
importRollout(rolloutPathInput) {
|
|
772
|
+
const rolloutPath = node_path_1.default.resolve(rolloutPathInput.trim());
|
|
773
|
+
if (!rolloutPathInput.trim()) {
|
|
774
|
+
throw new errors_1.AppError(400, "rolloutPath is required.");
|
|
775
|
+
}
|
|
776
|
+
if (!(0, node_fs_1.existsSync)(rolloutPath)) {
|
|
777
|
+
throw new errors_1.AppError(404, "Rollout file not found.");
|
|
778
|
+
}
|
|
779
|
+
const existing = this.db
|
|
780
|
+
.prepare(`
|
|
781
|
+
SELECT id
|
|
782
|
+
FROM sessions
|
|
783
|
+
WHERE source_kind = 'imported_rollout'
|
|
784
|
+
AND source_rollout_path = ?
|
|
785
|
+
LIMIT 1
|
|
786
|
+
`)
|
|
787
|
+
.get(rolloutPath);
|
|
788
|
+
if (existing) {
|
|
789
|
+
const sync = this.syncImportedSession(existing.id);
|
|
790
|
+
return {
|
|
791
|
+
sessionId: existing.id,
|
|
792
|
+
imported: false,
|
|
793
|
+
syncedEvents: sync.appendedEvents,
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
const translated = translateRolloutRecords(parseJsonlRecords(rolloutPath), 0);
|
|
797
|
+
const sessionId = (0, ids_1.createId)("sess");
|
|
798
|
+
const projectId = this.findOrCreateProjectForImportedRollout(translated.workspacePath, translated.firstTimestamp);
|
|
799
|
+
const insertEvent = this.db.prepare(`
|
|
800
|
+
INSERT INTO session_events (
|
|
801
|
+
id,
|
|
802
|
+
session_id,
|
|
803
|
+
turn_id,
|
|
804
|
+
seq,
|
|
805
|
+
event_type,
|
|
806
|
+
message_id,
|
|
807
|
+
call_id,
|
|
808
|
+
request_id,
|
|
809
|
+
phase,
|
|
810
|
+
stream,
|
|
811
|
+
payload_json,
|
|
812
|
+
created_at
|
|
813
|
+
)
|
|
814
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
815
|
+
`);
|
|
816
|
+
runInTransaction(this.db, () => {
|
|
817
|
+
this.db
|
|
818
|
+
.prepare(`
|
|
819
|
+
INSERT INTO sessions (
|
|
820
|
+
id,
|
|
821
|
+
title,
|
|
822
|
+
project_id,
|
|
823
|
+
status,
|
|
824
|
+
pid,
|
|
825
|
+
codex_thread_id,
|
|
826
|
+
source_kind,
|
|
827
|
+
source_rollout_path,
|
|
828
|
+
source_thread_id,
|
|
829
|
+
source_sync_cursor,
|
|
830
|
+
source_last_synced_at,
|
|
831
|
+
source_rollout_has_open_turn,
|
|
832
|
+
created_at,
|
|
833
|
+
updated_at
|
|
834
|
+
)
|
|
835
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
836
|
+
`)
|
|
837
|
+
.run(sessionId, translated.sessionTitle, projectId, translated.sessionStatus, null, translated.codexSessionId, "imported_rollout", rolloutPath, translated.codexSessionId, translated.rawCursor, translated.lastTimestamp, translated.sourceRolloutHasOpenTurn ? 1 : 0, translated.firstTimestamp, translated.lastTimestamp);
|
|
838
|
+
translated.events.forEach((event, index) => {
|
|
839
|
+
insertEvent.run(`${sessionId}_import_${String(index + 1).padStart(6, "0")}`, sessionId, event.turnId, index + 1, event.type, event.messageId, event.callId, event.requestId, event.phase, event.stream, JSON.stringify(event.payload ?? {}), event.timestamp);
|
|
840
|
+
});
|
|
841
|
+
});
|
|
842
|
+
return {
|
|
843
|
+
sessionId,
|
|
844
|
+
imported: true,
|
|
845
|
+
syncedEvents: translated.events.length,
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
syncImportedSession(sessionId) {
|
|
849
|
+
const session = this.db
|
|
850
|
+
.prepare(`
|
|
851
|
+
SELECT *
|
|
852
|
+
FROM sessions
|
|
853
|
+
WHERE id = ?
|
|
854
|
+
LIMIT 1
|
|
855
|
+
`)
|
|
856
|
+
.get(sessionId);
|
|
857
|
+
if (!session) {
|
|
858
|
+
throw new errors_1.AppError(404, "Session not found.");
|
|
859
|
+
}
|
|
860
|
+
if (session.source_kind !== "imported_rollout" || !session.source_rollout_path) {
|
|
861
|
+
return {
|
|
862
|
+
sessionId,
|
|
863
|
+
synced: false,
|
|
864
|
+
appendedEvents: 0,
|
|
865
|
+
reason: "not-imported",
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
if (session.status === "starting" || session.status === "running" || session.status === "stopping") {
|
|
869
|
+
return {
|
|
870
|
+
sessionId,
|
|
871
|
+
synced: false,
|
|
872
|
+
appendedEvents: 0,
|
|
873
|
+
reason: "live-runtime",
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
const rolloutPath = node_path_1.default.resolve(session.source_rollout_path);
|
|
877
|
+
if (!(0, node_fs_1.existsSync)(rolloutPath)) {
|
|
878
|
+
throw new errors_1.AppError(404, "Imported rollout source no longer exists.");
|
|
879
|
+
}
|
|
880
|
+
const records = parseJsonlRecords(rolloutPath);
|
|
881
|
+
const cursor = Math.max(0, session.source_sync_cursor ?? 0);
|
|
882
|
+
if (records.length <= cursor) {
|
|
883
|
+
return {
|
|
884
|
+
sessionId,
|
|
885
|
+
synced: false,
|
|
886
|
+
appendedEvents: 0,
|
|
887
|
+
reason: "up-to-date",
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
const translated = translateRolloutRecords(records, cursor);
|
|
891
|
+
if (translated.events.length === 0) {
|
|
892
|
+
this.db
|
|
893
|
+
.prepare(`
|
|
894
|
+
UPDATE sessions
|
|
895
|
+
SET
|
|
896
|
+
source_sync_cursor = ?,
|
|
897
|
+
source_last_synced_at = ?,
|
|
898
|
+
source_rollout_has_open_turn = ?,
|
|
899
|
+
updated_at = ?
|
|
900
|
+
WHERE id = ?
|
|
901
|
+
`)
|
|
902
|
+
.run(records.length, translated.lastTimestamp, translated.sourceRolloutHasOpenTurn ? 1 : 0, nowIso(), sessionId);
|
|
903
|
+
return {
|
|
904
|
+
sessionId,
|
|
905
|
+
synced: true,
|
|
906
|
+
appendedEvents: 0,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const currentMaxSeq = this.db
|
|
910
|
+
.prepare("SELECT COALESCE(MAX(seq), 0) AS value FROM session_events WHERE session_id = ?")
|
|
911
|
+
.get(sessionId).value;
|
|
912
|
+
const insertEvent = this.db.prepare(`
|
|
913
|
+
INSERT INTO session_events (
|
|
914
|
+
id,
|
|
915
|
+
session_id,
|
|
916
|
+
turn_id,
|
|
917
|
+
seq,
|
|
918
|
+
event_type,
|
|
919
|
+
message_id,
|
|
920
|
+
call_id,
|
|
921
|
+
request_id,
|
|
922
|
+
phase,
|
|
923
|
+
stream,
|
|
924
|
+
payload_json,
|
|
925
|
+
created_at
|
|
926
|
+
)
|
|
927
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
928
|
+
`);
|
|
929
|
+
runInTransaction(this.db, () => {
|
|
930
|
+
translated.events.forEach((event, index) => {
|
|
931
|
+
const seq = currentMaxSeq + index + 1;
|
|
932
|
+
insertEvent.run(`${sessionId}_sync_${String(seq).padStart(6, "0")}`, sessionId, event.turnId, seq, event.type, event.messageId, event.callId, event.requestId, event.phase, event.stream, JSON.stringify(event.payload ?? {}), event.timestamp);
|
|
933
|
+
});
|
|
934
|
+
this.db
|
|
935
|
+
.prepare(`
|
|
936
|
+
UPDATE sessions
|
|
937
|
+
SET
|
|
938
|
+
title = COALESCE(title, ?),
|
|
939
|
+
codex_thread_id = COALESCE(codex_thread_id, ?),
|
|
940
|
+
source_thread_id = COALESCE(source_thread_id, ?),
|
|
941
|
+
source_sync_cursor = ?,
|
|
942
|
+
source_last_synced_at = ?,
|
|
943
|
+
source_rollout_has_open_turn = ?,
|
|
944
|
+
updated_at = ?
|
|
945
|
+
WHERE id = ?
|
|
946
|
+
`)
|
|
947
|
+
.run(translated.sessionTitle, translated.codexSessionId, translated.codexSessionId, translated.rawCursor, translated.lastTimestamp, translated.sourceRolloutHasOpenTurn ? 1 : 0, nowIso(), sessionId);
|
|
948
|
+
});
|
|
949
|
+
return {
|
|
950
|
+
sessionId,
|
|
951
|
+
synced: true,
|
|
952
|
+
appendedEvents: translated.events.length,
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
findOrCreateProjectForImportedRollout(workspacePath, createdAt) {
|
|
956
|
+
const existing = this.db
|
|
957
|
+
.prepare(`
|
|
958
|
+
SELECT id
|
|
959
|
+
FROM projects
|
|
960
|
+
WHERE path = ?
|
|
961
|
+
LIMIT 1
|
|
962
|
+
`)
|
|
963
|
+
.get(workspacePath);
|
|
964
|
+
if (existing?.id) {
|
|
965
|
+
return existing.id;
|
|
966
|
+
}
|
|
967
|
+
const projectId = (0, ids_1.createId)("proj");
|
|
968
|
+
this.db
|
|
969
|
+
.prepare(`
|
|
970
|
+
INSERT INTO projects (id, name, path, created_at)
|
|
971
|
+
VALUES (?, ?, ?, ?)
|
|
972
|
+
`)
|
|
973
|
+
.run(projectId, `Imported Rollout: ${node_path_1.default.basename(workspacePath)}`, workspacePath, createdAt);
|
|
974
|
+
return projectId;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
exports.CodexRolloutSyncService = CodexRolloutSyncService;
|