@makerbi/remodex 1.5.1 → 1.5.3
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/bin/remodex-jsonl-diagnose.js +501 -0
- package/bin/remodex.js +41 -2
- package/package.json +4 -2
- package/src/bridge.js +748 -52
- package/src/macos-launch-agent.js +50 -3
- package/src/private-defaults.json +4 -0
- package/src/secure-device-state.js +2 -2
- package/src/secure-transport.js +4 -1
- package/src/session-jsonl-history.js +197 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// FILE: remodex-jsonl-diagnose.js
|
|
3
|
+
// Purpose: Standalone diagnostic for Codex session JSONL history parsing.
|
|
4
|
+
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
|
|
8
|
+
const DEFAULT_RECENT_TURN_LIMIT = 5;
|
|
9
|
+
const DEFAULT_TEXT_PREVIEW_CHARS = 180;
|
|
10
|
+
const RELAY_SOFT_LIMIT_BYTES = 4 * 1024 * 1024;
|
|
11
|
+
|
|
12
|
+
function main(argv) {
|
|
13
|
+
const options = parseArgs(argv);
|
|
14
|
+
if (options.help || !options.filePath) {
|
|
15
|
+
printUsage();
|
|
16
|
+
process.exit(options.help ? 0 : 1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const absolutePath = path.resolve(options.filePath);
|
|
20
|
+
const result = diagnoseSessionJsonl(absolutePath, options);
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
|
|
23
|
+
if (result.errors.file) {
|
|
24
|
+
process.exit(2);
|
|
25
|
+
}
|
|
26
|
+
if (result.parse.invalidJsonLines > 0 || result.history.turnCount === 0) {
|
|
27
|
+
process.exit(3);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseArgs(argv) {
|
|
32
|
+
const options = {
|
|
33
|
+
filePath: "",
|
|
34
|
+
recentTurns: DEFAULT_RECENT_TURN_LIMIT,
|
|
35
|
+
previewChars: DEFAULT_TEXT_PREVIEW_CHARS,
|
|
36
|
+
includeText: false,
|
|
37
|
+
help: false,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
41
|
+
const arg = argv[index];
|
|
42
|
+
if (arg === "-h" || arg === "--help") {
|
|
43
|
+
options.help = true;
|
|
44
|
+
} else if (arg === "--show-text") {
|
|
45
|
+
options.includeText = true;
|
|
46
|
+
} else if (arg === "--recent-turns") {
|
|
47
|
+
options.recentTurns = readPositiveInteger(argv[index + 1], DEFAULT_RECENT_TURN_LIMIT);
|
|
48
|
+
index += 1;
|
|
49
|
+
} else if (arg === "--preview-chars") {
|
|
50
|
+
options.previewChars = readPositiveInteger(argv[index + 1], DEFAULT_TEXT_PREVIEW_CHARS);
|
|
51
|
+
index += 1;
|
|
52
|
+
} else if (!options.filePath) {
|
|
53
|
+
options.filePath = arg;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return options;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function printUsage() {
|
|
61
|
+
console.log([
|
|
62
|
+
"Usage:",
|
|
63
|
+
" remodex-jsonl-diagnose /path/to/session.jsonl",
|
|
64
|
+
"",
|
|
65
|
+
"Options:",
|
|
66
|
+
" --recent-turns N Number of recent parsed turns to summarize. Default: 5",
|
|
67
|
+
" --preview-chars N Text preview length per item. Default: 180",
|
|
68
|
+
" --show-text Include short text previews in the output",
|
|
69
|
+
].join("\n"));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function diagnoseSessionJsonl(filePath, options = {}) {
|
|
73
|
+
const includeText = Boolean(options.includeText);
|
|
74
|
+
const previewChars = readPositiveInteger(options.previewChars, DEFAULT_TEXT_PREVIEW_CHARS);
|
|
75
|
+
const recentTurnsLimit = readPositiveInteger(options.recentTurns, DEFAULT_RECENT_TURN_LIMIT);
|
|
76
|
+
const summary = {
|
|
77
|
+
file: {
|
|
78
|
+
path: filePath,
|
|
79
|
+
exists: false,
|
|
80
|
+
bytes: 0,
|
|
81
|
+
},
|
|
82
|
+
parse: {
|
|
83
|
+
totalLines: 0,
|
|
84
|
+
blankLines: 0,
|
|
85
|
+
validJsonLines: 0,
|
|
86
|
+
invalidJsonLines: 0,
|
|
87
|
+
invalidSamples: [],
|
|
88
|
+
},
|
|
89
|
+
session: {
|
|
90
|
+
threadId: null,
|
|
91
|
+
cwd: null,
|
|
92
|
+
originator: null,
|
|
93
|
+
source: null,
|
|
94
|
+
sessionMetaCount: 0,
|
|
95
|
+
},
|
|
96
|
+
observed: {
|
|
97
|
+
topLevelTypes: {},
|
|
98
|
+
eventTypes: {},
|
|
99
|
+
responseItemTypes: {},
|
|
100
|
+
threadIds: [],
|
|
101
|
+
turnIds: [],
|
|
102
|
+
},
|
|
103
|
+
history: {
|
|
104
|
+
turnCount: 0,
|
|
105
|
+
itemCount: 0,
|
|
106
|
+
turnsWithNoItems: 0,
|
|
107
|
+
recentTurns: [],
|
|
108
|
+
},
|
|
109
|
+
relaySimulation: {
|
|
110
|
+
recentPageBytes: 0,
|
|
111
|
+
recentPageWithin4MiB: false,
|
|
112
|
+
compactSingleTurnBytes: 0,
|
|
113
|
+
compactSingleTurnWithin4MiB: false,
|
|
114
|
+
wouldReturnAtLeastOneTurn: false,
|
|
115
|
+
},
|
|
116
|
+
likelyIssue: "unknown",
|
|
117
|
+
errors: {
|
|
118
|
+
file: null,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let stat;
|
|
123
|
+
try {
|
|
124
|
+
stat = fs.statSync(filePath);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
summary.errors.file = error.message;
|
|
127
|
+
return summary;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!stat.isFile()) {
|
|
131
|
+
summary.errors.file = "Path is not a file.";
|
|
132
|
+
return summary;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
summary.file.exists = true;
|
|
136
|
+
summary.file.bytes = stat.size;
|
|
137
|
+
|
|
138
|
+
const turns = [];
|
|
139
|
+
const turnsById = new Map();
|
|
140
|
+
const threadIds = new Set();
|
|
141
|
+
const turnIds = new Set();
|
|
142
|
+
let activeTurnId = "";
|
|
143
|
+
|
|
144
|
+
const raw = fs.readFileSync(filePath, "utf8");
|
|
145
|
+
const lines = raw.split(/\r?\n/);
|
|
146
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
147
|
+
const lineNumber = index + 1;
|
|
148
|
+
const line = lines[index];
|
|
149
|
+
summary.parse.totalLines += 1;
|
|
150
|
+
if (!line.trim()) {
|
|
151
|
+
summary.parse.blankLines += 1;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let entry;
|
|
156
|
+
try {
|
|
157
|
+
entry = JSON.parse(line);
|
|
158
|
+
summary.parse.validJsonLines += 1;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
summary.parse.invalidJsonLines += 1;
|
|
161
|
+
if (summary.parse.invalidSamples.length < 5) {
|
|
162
|
+
summary.parse.invalidSamples.push({
|
|
163
|
+
line: lineNumber,
|
|
164
|
+
error: error.message,
|
|
165
|
+
preview: line.slice(0, 240),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
increment(summary.observed.topLevelTypes, readString(entry?.type) || "unknown");
|
|
172
|
+
collectKnownThreadIds(entry, threadIds);
|
|
173
|
+
|
|
174
|
+
if (entry?.type === "session_meta") {
|
|
175
|
+
summary.session.sessionMetaCount += 1;
|
|
176
|
+
const payload = objectValue(entry.payload);
|
|
177
|
+
summary.session.threadId ||= readString(payload?.id) || readString(payload?.thread_id) || readString(payload?.threadId);
|
|
178
|
+
summary.session.cwd ||= readString(payload?.cwd);
|
|
179
|
+
summary.session.originator ||= readString(payload?.originator);
|
|
180
|
+
summary.session.source ||= readString(payload?.source);
|
|
181
|
+
if (summary.session.threadId) {
|
|
182
|
+
threadIds.add(summary.session.threadId);
|
|
183
|
+
}
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const embeddedTurns = findEmbeddedTurns(entry);
|
|
188
|
+
for (const turn of embeddedTurns) {
|
|
189
|
+
const turnId = readTurnId(turn) || `embedded-turn-line-${lineNumber}-${turns.length + 1}`;
|
|
190
|
+
const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
|
|
191
|
+
record.sourceKinds.add("embedded_turn");
|
|
192
|
+
if (Array.isArray(turn.items)) {
|
|
193
|
+
for (const item of turn.items) {
|
|
194
|
+
addItem(record, item, lineNumber, includeText, previewChars);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
turnIds.add(turnId);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (entry?.type === "event_msg") {
|
|
201
|
+
const payload = objectValue(entry.payload);
|
|
202
|
+
const eventType = readString(payload?.type) || "unknown";
|
|
203
|
+
increment(summary.observed.eventTypes, eventType);
|
|
204
|
+
|
|
205
|
+
if (eventType === "task_started") {
|
|
206
|
+
activeTurnId = readString(payload?.turn_id) || readString(payload?.turnId) || activeTurnId || `turn-line-${lineNumber}`;
|
|
207
|
+
const record = ensureTurn(turns, turnsById, activeTurnId, lineNumber, entry.timestamp);
|
|
208
|
+
record.status ||= "running";
|
|
209
|
+
record.sourceKinds.add("task_started");
|
|
210
|
+
turnIds.add(activeTurnId);
|
|
211
|
+
} else if (eventType === "task_complete") {
|
|
212
|
+
const turnId = readString(payload?.turn_id) || readString(payload?.turnId) || activeTurnId || `turn-line-${lineNumber}`;
|
|
213
|
+
const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
|
|
214
|
+
record.status = "completed";
|
|
215
|
+
record.sourceKinds.add("task_complete");
|
|
216
|
+
turnIds.add(turnId);
|
|
217
|
+
} else if (eventType === "user_message" || eventType === "agent_message" || eventType === "agent_reasoning") {
|
|
218
|
+
const turnId = readString(payload?.turn_id) || readString(payload?.turnId) || activeTurnId || `turn-line-${lineNumber}`;
|
|
219
|
+
const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
|
|
220
|
+
record.sourceKinds.add(eventType);
|
|
221
|
+
turnIds.add(turnId);
|
|
222
|
+
addItem(record, itemFromEventPayload(eventType, payload, lineNumber), lineNumber, includeText, previewChars);
|
|
223
|
+
}
|
|
224
|
+
} else if (entry?.type === "response_item") {
|
|
225
|
+
const payload = objectValue(entry.payload) || {};
|
|
226
|
+
const itemType = normalizeType(readString(payload.type)) || "unknown";
|
|
227
|
+
increment(summary.observed.responseItemTypes, itemType);
|
|
228
|
+
const turnId = readString(payload.turn_id) || readString(payload.turnId) || activeTurnId || `response-items-line-${lineNumber}`;
|
|
229
|
+
const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
|
|
230
|
+
record.sourceKinds.add("response_item");
|
|
231
|
+
turnIds.add(turnId);
|
|
232
|
+
addItem(record, payload, lineNumber, includeText, previewChars);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const sortedTurns = turns.sort((a, b) => a.firstLine - b.firstLine);
|
|
237
|
+
summary.observed.threadIds = Array.from(threadIds).sort();
|
|
238
|
+
summary.observed.turnIds = Array.from(turnIds).sort();
|
|
239
|
+
summary.history.turnCount = sortedTurns.length;
|
|
240
|
+
summary.history.itemCount = sortedTurns.reduce((total, turn) => total + turn.items.length, 0);
|
|
241
|
+
summary.history.turnsWithNoItems = sortedTurns.filter((turn) => turn.items.length === 0).length;
|
|
242
|
+
summary.history.recentTurns = sortedTurns.slice(-recentTurnsLimit).map((turn) => summarizeTurn(turn, includeText));
|
|
243
|
+
|
|
244
|
+
const recentPage = {
|
|
245
|
+
id: "diagnostic-thread-turns-list",
|
|
246
|
+
result: {
|
|
247
|
+
data: sortedTurns.slice(-recentTurnsLimit).reverse().map(toWireTurn),
|
|
248
|
+
nextCursor: sortedTurns.length > recentTurnsLimit ? "diagnostic-has-older-turns" : null,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
const recentPageBytes = Buffer.byteLength(JSON.stringify(recentPage), "utf8");
|
|
252
|
+
const compactSingleTurn = sortedTurns.length > 0 ? {
|
|
253
|
+
id: "diagnostic-thread-turns-list",
|
|
254
|
+
result: {
|
|
255
|
+
data: [compactTurn(toWireTurn(sortedTurns[sortedTurns.length - 1]))],
|
|
256
|
+
nextCursor: sortedTurns.length > 1 ? "diagnostic-has-older-turns" : null,
|
|
257
|
+
},
|
|
258
|
+
} : null;
|
|
259
|
+
const compactSingleTurnBytes = compactSingleTurn
|
|
260
|
+
? Buffer.byteLength(JSON.stringify(compactSingleTurn), "utf8")
|
|
261
|
+
: 0;
|
|
262
|
+
|
|
263
|
+
summary.relaySimulation.recentPageBytes = recentPageBytes;
|
|
264
|
+
summary.relaySimulation.recentPageWithin4MiB = recentPageBytes <= RELAY_SOFT_LIMIT_BYTES;
|
|
265
|
+
summary.relaySimulation.compactSingleTurnBytes = compactSingleTurnBytes;
|
|
266
|
+
summary.relaySimulation.compactSingleTurnWithin4MiB = compactSingleTurnBytes > 0
|
|
267
|
+
&& compactSingleTurnBytes <= RELAY_SOFT_LIMIT_BYTES;
|
|
268
|
+
summary.relaySimulation.wouldReturnAtLeastOneTurn = summary.relaySimulation.recentPageWithin4MiB
|
|
269
|
+
? sortedTurns.length > 0
|
|
270
|
+
: summary.relaySimulation.compactSingleTurnWithin4MiB;
|
|
271
|
+
|
|
272
|
+
summary.likelyIssue = classifyIssue(summary);
|
|
273
|
+
return summary;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function classifyIssue(summary) {
|
|
277
|
+
if (summary.errors.file) {
|
|
278
|
+
return "file_not_readable";
|
|
279
|
+
}
|
|
280
|
+
if (summary.parse.invalidJsonLines > 0 && summary.parse.validJsonLines === 0) {
|
|
281
|
+
return "jsonl_not_parseable";
|
|
282
|
+
}
|
|
283
|
+
if (summary.history.turnCount === 0 && summary.parse.validJsonLines > 0) {
|
|
284
|
+
return "jsonl_parseable_but_no_recognized_turns";
|
|
285
|
+
}
|
|
286
|
+
if (!summary.relaySimulation.wouldReturnAtLeastOneTurn) {
|
|
287
|
+
return "recognized_turns_but_payload_still_too_large";
|
|
288
|
+
}
|
|
289
|
+
return "jsonl_parseable_and_turns_extractable";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function ensureTurn(turns, turnsById, turnId, lineNumber, timestamp) {
|
|
293
|
+
const normalizedTurnId = readString(turnId) || `turn-line-${lineNumber}`;
|
|
294
|
+
let turn = turnsById.get(normalizedTurnId);
|
|
295
|
+
if (!turn) {
|
|
296
|
+
turn = {
|
|
297
|
+
id: normalizedTurnId,
|
|
298
|
+
firstLine: lineNumber,
|
|
299
|
+
lastLine: lineNumber,
|
|
300
|
+
createdAt: readString(timestamp) || null,
|
|
301
|
+
status: "",
|
|
302
|
+
sourceKinds: new Set(),
|
|
303
|
+
items: [],
|
|
304
|
+
};
|
|
305
|
+
turnsById.set(normalizedTurnId, turn);
|
|
306
|
+
turns.push(turn);
|
|
307
|
+
}
|
|
308
|
+
turn.lastLine = lineNumber;
|
|
309
|
+
return turn;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function addItem(turn, item, lineNumber, includeText, previewChars) {
|
|
313
|
+
if (!item || typeof item !== "object") {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const text = firstText(item);
|
|
317
|
+
turn.items.push({
|
|
318
|
+
id: readString(item.id) || readString(item.item_id) || readString(item.itemId) || `item-line-${lineNumber}-${turn.items.length + 1}`,
|
|
319
|
+
type: readString(item.type) || "unknown",
|
|
320
|
+
role: readString(item.role) || null,
|
|
321
|
+
line: lineNumber,
|
|
322
|
+
textBytes: text ? Buffer.byteLength(text, "utf8") : 0,
|
|
323
|
+
textPreview: includeText && text ? truncateText(text, previewChars) : undefined,
|
|
324
|
+
rawBytes: Buffer.byteLength(JSON.stringify(item), "utf8"),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function itemFromEventPayload(eventType, payload, lineNumber) {
|
|
329
|
+
const role = eventType === "user_message" ? "user" : "assistant";
|
|
330
|
+
return {
|
|
331
|
+
id: readString(payload.id) || `${eventType}-line-${lineNumber}`,
|
|
332
|
+
type: eventType,
|
|
333
|
+
role,
|
|
334
|
+
text: readString(payload.message) || readString(payload.text) || readString(payload.summary) || "",
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function summarizeTurn(turn, includeText) {
|
|
339
|
+
return {
|
|
340
|
+
id: turn.id,
|
|
341
|
+
firstLine: turn.firstLine,
|
|
342
|
+
lastLine: turn.lastLine,
|
|
343
|
+
status: turn.status || null,
|
|
344
|
+
sourceKinds: Array.from(turn.sourceKinds).sort(),
|
|
345
|
+
itemCount: turn.items.length,
|
|
346
|
+
rawItemBytes: turn.items.reduce((total, item) => total + item.rawBytes, 0),
|
|
347
|
+
textBytes: turn.items.reduce((total, item) => total + item.textBytes, 0),
|
|
348
|
+
items: turn.items.slice(-5).map((item) => ({
|
|
349
|
+
id: item.id,
|
|
350
|
+
type: item.type,
|
|
351
|
+
role: item.role,
|
|
352
|
+
line: item.line,
|
|
353
|
+
textBytes: item.textBytes,
|
|
354
|
+
rawBytes: item.rawBytes,
|
|
355
|
+
...(includeText && item.textPreview !== undefined ? { textPreview: item.textPreview } : {}),
|
|
356
|
+
})),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function toWireTurn(turn) {
|
|
361
|
+
return {
|
|
362
|
+
id: turn.id,
|
|
363
|
+
createdAt: turn.createdAt,
|
|
364
|
+
status: turn.status || undefined,
|
|
365
|
+
items: turn.items.map((item) => ({
|
|
366
|
+
id: item.id,
|
|
367
|
+
type: item.type,
|
|
368
|
+
role: item.role || undefined,
|
|
369
|
+
text: item.textPreview || undefined,
|
|
370
|
+
remodexDiagnosticRawBytes: item.rawBytes,
|
|
371
|
+
remodexDiagnosticTextBytes: item.textBytes,
|
|
372
|
+
})),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function compactTurn(turn) {
|
|
377
|
+
return {
|
|
378
|
+
id: turn.id,
|
|
379
|
+
createdAt: turn.createdAt,
|
|
380
|
+
status: turn.status,
|
|
381
|
+
remodexDiagnosticCompacted: true,
|
|
382
|
+
items: (Array.isArray(turn.items) ? turn.items : []).slice(-1).map((item) => ({
|
|
383
|
+
id: item.id,
|
|
384
|
+
type: item.type || "relay_truncated_item",
|
|
385
|
+
role: item.role,
|
|
386
|
+
relayPayloadTruncated: true,
|
|
387
|
+
text: item.text ? truncateText(item.text, 1000) : undefined,
|
|
388
|
+
})),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function findEmbeddedTurns(entry) {
|
|
393
|
+
const candidates = [
|
|
394
|
+
entry?.turns,
|
|
395
|
+
entry?.thread?.turns,
|
|
396
|
+
entry?.payload?.turns,
|
|
397
|
+
entry?.payload?.thread?.turns,
|
|
398
|
+
entry?.result?.turns,
|
|
399
|
+
entry?.result?.thread?.turns,
|
|
400
|
+
entry?.result?.data,
|
|
401
|
+
entry?.result?.items,
|
|
402
|
+
entry?.result?.payload?.turns,
|
|
403
|
+
entry?.result?.payload?.data,
|
|
404
|
+
entry?.result?.payload?.items,
|
|
405
|
+
];
|
|
406
|
+
return candidates.find((value) => Array.isArray(value)) || [];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function collectKnownThreadIds(value, output, depth = 0) {
|
|
410
|
+
if (!value || depth > 4) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
if (Array.isArray(value)) {
|
|
414
|
+
for (const item of value.slice(0, 50)) {
|
|
415
|
+
collectKnownThreadIds(item, output, depth + 1);
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (typeof value !== "object") {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (const key of ["thread_id", "threadId", "conversation_id", "conversationId"]) {
|
|
424
|
+
const id = readString(value[key]);
|
|
425
|
+
if (id) {
|
|
426
|
+
output.add(id);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
for (const key of ["payload", "result", "thread"]) {
|
|
430
|
+
collectKnownThreadIds(value[key], output, depth + 1);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function readTurnId(turn) {
|
|
435
|
+
return readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function firstText(value, depth = 0) {
|
|
439
|
+
if (!value || depth > 5) {
|
|
440
|
+
return "";
|
|
441
|
+
}
|
|
442
|
+
if (typeof value === "string") {
|
|
443
|
+
return value;
|
|
444
|
+
}
|
|
445
|
+
if (Array.isArray(value)) {
|
|
446
|
+
for (const item of value) {
|
|
447
|
+
const text = firstText(item, depth + 1);
|
|
448
|
+
if (text) {
|
|
449
|
+
return text;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return "";
|
|
453
|
+
}
|
|
454
|
+
if (typeof value !== "object") {
|
|
455
|
+
return "";
|
|
456
|
+
}
|
|
457
|
+
for (const key of ["text", "message", "summary", "output", "outputText", "output_text", "content"]) {
|
|
458
|
+
const text = firstText(value[key], depth + 1);
|
|
459
|
+
if (text) {
|
|
460
|
+
return text;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return "";
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function objectValue(value) {
|
|
467
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function readString(value) {
|
|
471
|
+
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function readPositiveInteger(value, fallback) {
|
|
475
|
+
const number = Number(value);
|
|
476
|
+
return Number.isInteger(number) && number > 0 ? number : fallback;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function increment(record, key) {
|
|
480
|
+
record[key] = (record[key] || 0) + 1;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function normalizeType(value) {
|
|
484
|
+
return readString(value).toLowerCase().replace(/[\s_-]+/g, "");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function truncateText(value, maxChars) {
|
|
488
|
+
const text = readString(value);
|
|
489
|
+
if (!text || text.length <= maxChars) {
|
|
490
|
+
return text;
|
|
491
|
+
}
|
|
492
|
+
return `...\n${text.slice(-maxChars)}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (require.main === module) {
|
|
496
|
+
main(process.argv.slice(2));
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
module.exports = {
|
|
500
|
+
diagnoseSessionJsonl,
|
|
501
|
+
};
|
package/bin/remodex.js
CHANGED
|
@@ -49,7 +49,12 @@ async function main({
|
|
|
49
49
|
exitImpl = process.exit,
|
|
50
50
|
deps = defaultDeps,
|
|
51
51
|
} = {}) {
|
|
52
|
-
const {
|
|
52
|
+
const {
|
|
53
|
+
command,
|
|
54
|
+
extraRelaySessions,
|
|
55
|
+
jsonOutput,
|
|
56
|
+
watchThreadId,
|
|
57
|
+
} = parseCliArgs(argv.slice(2));
|
|
53
58
|
|
|
54
59
|
if (isVersionCommand(command)) {
|
|
55
60
|
emitVersion({ jsonOutput, consoleImpl });
|
|
@@ -73,6 +78,7 @@ async function main({
|
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
if (command === "run") {
|
|
81
|
+
applyExtraRelaySessions(extraRelaySessions);
|
|
76
82
|
deps.startBridge();
|
|
77
83
|
return;
|
|
78
84
|
}
|
|
@@ -233,7 +239,7 @@ async function main({
|
|
|
233
239
|
|
|
234
240
|
consoleImpl.error(`Unknown command: ${command}`);
|
|
235
241
|
consoleImpl.error(
|
|
236
|
-
"Usage: remodex up | remodex run | remodex start | remodex restart | remodex stop | remodex status | "
|
|
242
|
+
"Usage: remodex up | remodex run [--extra-device|--extra-devices=N] | remodex start | remodex restart | remodex stop | remodex status | "
|
|
237
243
|
+ "remodex reset-pairing | remodex resume | remodex watch [threadId] | remodex --version | "
|
|
238
244
|
+ "append --json to start/restart/stop/status/reset-pairing/resume for machine-readable output"
|
|
239
245
|
);
|
|
@@ -242,6 +248,7 @@ async function main({
|
|
|
242
248
|
|
|
243
249
|
function parseCliArgs(rawArgs) {
|
|
244
250
|
const positionals = [];
|
|
251
|
+
let extraRelaySessions = 0;
|
|
245
252
|
let jsonOutput = false;
|
|
246
253
|
|
|
247
254
|
for (const arg of rawArgs) {
|
|
@@ -250,16 +257,47 @@ function parseCliArgs(rawArgs) {
|
|
|
250
257
|
continue;
|
|
251
258
|
}
|
|
252
259
|
|
|
260
|
+
if (arg === "--extra-device" || arg === "--second-device" || arg === "--multi-device") {
|
|
261
|
+
extraRelaySessions = Math.max(extraRelaySessions, 1);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (arg.startsWith("--extra-devices=")) {
|
|
266
|
+
extraRelaySessions = Math.max(
|
|
267
|
+
extraRelaySessions,
|
|
268
|
+
parsePositiveInteger(arg.slice("--extra-devices=".length))
|
|
269
|
+
);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
|
|
253
273
|
positionals.push(arg);
|
|
254
274
|
}
|
|
255
275
|
|
|
256
276
|
return {
|
|
257
277
|
command: positionals[0] || "up",
|
|
278
|
+
extraRelaySessions,
|
|
258
279
|
jsonOutput,
|
|
259
280
|
watchThreadId: positionals[1] || "",
|
|
260
281
|
};
|
|
261
282
|
}
|
|
262
283
|
|
|
284
|
+
function applyExtraRelaySessions(extraRelaySessions) {
|
|
285
|
+
if (!extraRelaySessions || process.env.REMODEX_EXTRA_RELAY_SESSIONS) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
process.env.REMODEX_EXTRA_RELAY_SESSIONS = String(Math.min(extraRelaySessions, 3));
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function parsePositiveInteger(value) {
|
|
293
|
+
const parsed = Number.parseInt(value, 10);
|
|
294
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
295
|
+
return 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return parsed;
|
|
299
|
+
}
|
|
300
|
+
|
|
263
301
|
function emitVersion({
|
|
264
302
|
jsonOutput = false,
|
|
265
303
|
consoleImpl = console,
|
|
@@ -312,4 +350,5 @@ function isVersionCommand(value) {
|
|
|
312
350
|
module.exports = {
|
|
313
351
|
isVersionCommand,
|
|
314
352
|
main,
|
|
353
|
+
parseCliArgs,
|
|
315
354
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@makerbi/remodex",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.3",
|
|
4
4
|
"description": "Local bridge between Codex and the Remodex mobile app. Run `remodex up` to start.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
},
|
|
10
10
|
"main": "src/index.js",
|
|
11
11
|
"bin": {
|
|
12
|
-
"remodex": "bin/remodex.js"
|
|
12
|
+
"remodex": "bin/remodex.js",
|
|
13
|
+
"remodex-jsonl-diagnose": "bin/remodex-jsonl-diagnose.js"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
15
16
|
"bin/",
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
],
|
|
18
19
|
"scripts": {
|
|
19
20
|
"start": "node ./bin/remodex.js up",
|
|
21
|
+
"start:multi": "node ./bin/remodex.js run --extra-device",
|
|
20
22
|
"test": "node --test ./test/*.test.js",
|
|
21
23
|
"postinstall": "node ./src/bootstrap-codex-cli.js",
|
|
22
24
|
"prepack": "node ./scripts/prepare-private-defaults.js",
|