@nordbyte/nordrelay 0.3.1 → 0.4.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/.env.example +45 -2
- package/README.md +204 -30
- package/dist/agent-activity.js +300 -0
- package/dist/agent-adapter.js +17 -30
- package/dist/agent-factory.js +27 -0
- package/dist/agent.js +123 -9
- package/dist/artifacts.js +1 -1
- package/dist/audit-log.js +1 -1
- package/dist/bot-ui.js +1 -1
- package/dist/bot.js +328 -159
- package/dist/claude-code-auth.js +121 -0
- package/dist/claude-code-cli.js +19 -0
- package/dist/claude-code-launch.js +73 -0
- package/dist/claude-code-session.js +660 -0
- package/dist/claude-code-state.js +590 -0
- package/dist/codex-session.js +12 -1
- package/dist/config.js +113 -9
- package/dist/hermes-api.js +150 -0
- package/dist/hermes-auth.js +96 -0
- package/dist/hermes-cli.js +19 -0
- package/dist/hermes-launch.js +57 -0
- package/dist/hermes-session.js +477 -0
- package/dist/hermes-state.js +609 -0
- package/dist/index.js +51 -8
- package/dist/openclaw-auth.js +27 -0
- package/dist/openclaw-cli.js +19 -0
- package/dist/openclaw-gateway.js +285 -0
- package/dist/openclaw-launch.js +65 -0
- package/dist/openclaw-session.js +549 -0
- package/dist/openclaw-state.js +409 -0
- package/dist/operations.js +83 -2
- package/dist/pi-auth.js +59 -0
- package/dist/pi-launch.js +61 -0
- package/dist/pi-rpc.js +18 -0
- package/dist/pi-session.js +103 -15
- package/dist/pi-state.js +253 -0
- package/dist/relay-runtime.js +673 -51
- package/dist/session-format.js +28 -18
- package/dist/session-registry.js +40 -15
- package/dist/settings-service.js +35 -4
- package/dist/web-dashboard-ui.js +18 -0
- package/dist/web-dashboard.js +329 -47
- package/package.json +8 -3
- package/plugins/nordrelay/.codex-plugin/plugin.json +7 -4
- package/plugins/nordrelay/commands/remote.md +2 -2
- package/plugins/nordrelay/scripts/nordrelay.mjs +131 -3
- package/plugins/nordrelay/skills/telegram-remote/SKILL.md +2 -2
- package/CHANGELOG.md +0 -26
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function getDefaultClaudeCodeHome() {
|
|
5
|
+
return path.join(os.homedir(), ".claude");
|
|
6
|
+
}
|
|
7
|
+
export function resolveClaudeCodeProjectsDir(options = {}) {
|
|
8
|
+
const configDir = options.configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
9
|
+
const home = options.claudeHome ?? getDefaultClaudeCodeHome();
|
|
10
|
+
return path.join(configDir || home, "projects");
|
|
11
|
+
}
|
|
12
|
+
export function listClaudeCodeSessions(limit = 20, options = {}) {
|
|
13
|
+
const projectsDir = resolveClaudeCodeProjectsDir(options);
|
|
14
|
+
if (!existsSync(projectsDir)) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
const records = [];
|
|
18
|
+
for (const projectKey of safeReadDir(projectsDir)) {
|
|
19
|
+
const projectPath = path.join(projectsDir, projectKey);
|
|
20
|
+
if (!safeStat(projectPath)?.isDirectory()) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
for (const fileName of safeReadDir(projectPath)) {
|
|
24
|
+
if (!fileName.endsWith(".jsonl")) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const sessionPath = path.join(projectPath, fileName);
|
|
28
|
+
const record = readClaudeCodeSessionRecord(sessionPath, projectKey, options);
|
|
29
|
+
if (record) {
|
|
30
|
+
records.push(record);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return records
|
|
35
|
+
.sort((left, right) => right.updatedAt.getTime() - left.updatedAt.getTime())
|
|
36
|
+
.slice(0, Math.max(1, limit));
|
|
37
|
+
}
|
|
38
|
+
export function getClaudeCodeSession(idOrPath, options = {}) {
|
|
39
|
+
const normalized = idOrPath.trim();
|
|
40
|
+
if (!normalized) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (existsSync(normalized)) {
|
|
44
|
+
return readClaudeCodeSessionRecord(normalized, path.basename(path.dirname(normalized)), options);
|
|
45
|
+
}
|
|
46
|
+
return listClaudeCodeSessions(500, options).find((record) => record.id === normalized ||
|
|
47
|
+
record.id.startsWith(normalized) ||
|
|
48
|
+
path.basename(record.sessionPath, ".jsonl") === normalized ||
|
|
49
|
+
record.sessionPath === normalized) ?? null;
|
|
50
|
+
}
|
|
51
|
+
export function getClaudeCodeSessionActivity(idOrPath, options = {}) {
|
|
52
|
+
return getClaudeCodeSessionSnapshot(idOrPath, { ...options, maxEvents: 0 })?.activity ?? null;
|
|
53
|
+
}
|
|
54
|
+
export function getClaudeCodeSessionActivityLog(idOrPath, limit = 50, options = {}) {
|
|
55
|
+
return getClaudeCodeSessionSnapshot(idOrPath, { ...options, maxEvents: Math.max(1, limit) })?.events ?? [];
|
|
56
|
+
}
|
|
57
|
+
export function getClaudeCodeSessionSnapshot(idOrPath, options = {}) {
|
|
58
|
+
const record = getClaudeCodeSession(idOrPath, options);
|
|
59
|
+
if (!record) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return readClaudeCodeSessionSnapshot(record, options);
|
|
63
|
+
}
|
|
64
|
+
export function getClaudeCodeSessionDiagnostics(idOrPath, options = {}) {
|
|
65
|
+
const projectsDir = resolveClaudeCodeProjectsDir(options);
|
|
66
|
+
if (!idOrPath) {
|
|
67
|
+
return {
|
|
68
|
+
projectsDir,
|
|
69
|
+
sessionPath: null,
|
|
70
|
+
lineCount: 0,
|
|
71
|
+
status: "unavailable",
|
|
72
|
+
reason: "no active Claude Code session",
|
|
73
|
+
updatedAt: null,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const snapshot = getClaudeCodeSessionSnapshot(idOrPath, { ...options, maxEvents: 0 });
|
|
77
|
+
if (!snapshot) {
|
|
78
|
+
return {
|
|
79
|
+
projectsDir,
|
|
80
|
+
sessionPath: null,
|
|
81
|
+
lineCount: 0,
|
|
82
|
+
status: "unavailable",
|
|
83
|
+
reason: "Claude Code session file not found or unreadable",
|
|
84
|
+
updatedAt: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const status = snapshot.activity.active ? "active" : snapshot.activity.stale ? "stale" : "idle";
|
|
88
|
+
return {
|
|
89
|
+
projectsDir,
|
|
90
|
+
sessionPath: snapshot.sourcePath,
|
|
91
|
+
lineCount: snapshot.lineCount,
|
|
92
|
+
status,
|
|
93
|
+
reason: snapshot.activity.active
|
|
94
|
+
? "latest Claude Code user turn has no terminal response yet"
|
|
95
|
+
: snapshot.activity.stale
|
|
96
|
+
? "open Claude Code turn exceeded stale timeout"
|
|
97
|
+
: "latest Claude Code turn has a terminal response",
|
|
98
|
+
updatedAt: snapshot.activity.updatedAt,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export function listClaudeCodeWorkspaces(options = {}) {
|
|
102
|
+
const workspaces = new Set();
|
|
103
|
+
if (options.workspace) {
|
|
104
|
+
workspaces.add(options.workspace);
|
|
105
|
+
}
|
|
106
|
+
for (const record of listClaudeCodeSessions(500, options)) {
|
|
107
|
+
if (record.cwd) {
|
|
108
|
+
workspaces.add(record.cwd);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return [...workspaces].sort((left, right) => left.localeCompare(right));
|
|
112
|
+
}
|
|
113
|
+
export function readClaudeCodeSessionRecord(sessionPath, projectKey, options = {}) {
|
|
114
|
+
try {
|
|
115
|
+
const fileStat = statSync(sessionPath);
|
|
116
|
+
const lines = readJsonlLines(sessionPath);
|
|
117
|
+
const fileSessionId = path.basename(sessionPath, ".jsonl");
|
|
118
|
+
let id = fileSessionId;
|
|
119
|
+
let createdAt = fileStat.birthtimeMs > 0 ? fileStat.birthtime : fileStat.mtime;
|
|
120
|
+
let updatedAt = fileStat.mtime;
|
|
121
|
+
let cwd = options.workspace ?? (projectKey ? decodeClaudeCodeProjectKey(projectKey) : path.dirname(sessionPath));
|
|
122
|
+
let model = null;
|
|
123
|
+
let reasoningEffort = null;
|
|
124
|
+
let firstUserMessage = null;
|
|
125
|
+
let lastAssistantText = null;
|
|
126
|
+
let title = null;
|
|
127
|
+
let messageCount = 0;
|
|
128
|
+
let usage;
|
|
129
|
+
for (const { entry } of lines) {
|
|
130
|
+
id = stringValue(entry.session_id) ?? stringValue(entry.sessionId) ?? id;
|
|
131
|
+
cwd = stringValue(entry.cwd) ?? stringValue(objectValue(entry.message)?.cwd) ?? cwd;
|
|
132
|
+
title = stringValue(entry.customTitle) ?? stringValue(entry.summary) ?? title;
|
|
133
|
+
model = stringValue(entry.model) ?? stringValue(objectValue(entry.message)?.model) ?? model;
|
|
134
|
+
reasoningEffort = reasoningValue(entry) ?? reasoningEffort;
|
|
135
|
+
const timestamp = dateValue(entry.timestamp) ?? dateValue(entry.created_at) ?? dateValue(entry.createdAt);
|
|
136
|
+
if (timestamp) {
|
|
137
|
+
if (timestamp < createdAt) {
|
|
138
|
+
createdAt = timestamp;
|
|
139
|
+
}
|
|
140
|
+
if (timestamp > updatedAt) {
|
|
141
|
+
updatedAt = timestamp;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const type = stringValue(entry.type);
|
|
145
|
+
if (type === "summary") {
|
|
146
|
+
title = stringValue(entry.summary) ?? title;
|
|
147
|
+
}
|
|
148
|
+
if (type === "user" || type === "assistant") {
|
|
149
|
+
messageCount += 1;
|
|
150
|
+
}
|
|
151
|
+
if (type === "user" && !isToolResultEntry(entry) && !firstUserMessage) {
|
|
152
|
+
firstUserMessage = extractEntryText(entry);
|
|
153
|
+
}
|
|
154
|
+
else if (type === "assistant") {
|
|
155
|
+
const assistantText = extractEntryText(entry);
|
|
156
|
+
if (assistantText) {
|
|
157
|
+
lastAssistantText = assistantText;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (type === "result") {
|
|
161
|
+
usage = usageFromObject(entry.usage) ?? usage;
|
|
162
|
+
}
|
|
163
|
+
usage = usageFromObject(objectValue(entry.message)?.usage) ?? usage;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
id,
|
|
167
|
+
title: title ?? summarizeTitle(firstUserMessage ?? lastAssistantText),
|
|
168
|
+
cwd,
|
|
169
|
+
model,
|
|
170
|
+
reasoningEffort,
|
|
171
|
+
createdAt,
|
|
172
|
+
updatedAt,
|
|
173
|
+
firstUserMessage,
|
|
174
|
+
agentId: "claude-code",
|
|
175
|
+
sessionPath,
|
|
176
|
+
projectKey: projectKey ?? path.basename(path.dirname(sessionPath)),
|
|
177
|
+
messageCount,
|
|
178
|
+
usage,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function readClaudeCodeSessionSnapshot(record, options = {}) {
|
|
186
|
+
try {
|
|
187
|
+
const fileStat = statSync(record.sessionPath);
|
|
188
|
+
const rows = readJsonlLines(record.sessionPath);
|
|
189
|
+
const parsed = parseClaudeCodeActivityEvents(rows, record.id, options.afterLine ?? 0);
|
|
190
|
+
const latestUser = [...parsed.events].reverse().find((event) => event.kind === "user");
|
|
191
|
+
const latestAgent = [...parsed.events].reverse().find((event) => event.kind === "agent");
|
|
192
|
+
const latestTerminal = [...parsed.events].reverse().find((event) => event.kind === "task" && event.status && event.status !== "started");
|
|
193
|
+
const latestTool = [...parsed.events].reverse().find((event) => event.kind === "tool" && event.toolName);
|
|
194
|
+
const latestTimestamp = parsed.latestTimestamp ?? fileStat.mtime;
|
|
195
|
+
const hasAssistantAfterUser = Boolean(latestUser && latestAgent && latestAgent.lineNumber > latestUser.lineNumber);
|
|
196
|
+
const terminalAfterUser = Boolean(latestUser && latestTerminal && latestTerminal.lineNumber > latestUser.lineNumber);
|
|
197
|
+
const openTurn = Boolean(latestUser && !hasAssistantAfterUser && !terminalAfterUser);
|
|
198
|
+
const staleAfterMs = options.staleAfterMs ?? 5 * 60 * 1000;
|
|
199
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
200
|
+
const stale = openTurn && nowMs - latestTimestamp.getTime() > staleAfterMs;
|
|
201
|
+
const active = openTurn && !stale;
|
|
202
|
+
const maxEvents = options.maxEvents ?? 50;
|
|
203
|
+
const events = maxEvents <= 0 ? [] : parsed.events.slice(-maxEvents);
|
|
204
|
+
return {
|
|
205
|
+
agentId: "claude-code",
|
|
206
|
+
agentLabel: "Claude Code",
|
|
207
|
+
threadId: record.id,
|
|
208
|
+
sourcePath: record.sessionPath,
|
|
209
|
+
sourceLabel: "Claude Code transcript",
|
|
210
|
+
lineCount: rows.length,
|
|
211
|
+
activity: {
|
|
212
|
+
agentId: "claude-code",
|
|
213
|
+
agentLabel: "Claude Code",
|
|
214
|
+
threadId: record.id,
|
|
215
|
+
sourcePath: record.sessionPath,
|
|
216
|
+
sourceLabel: "Claude Code transcript",
|
|
217
|
+
active,
|
|
218
|
+
stale,
|
|
219
|
+
turnId: latestUser?.turnId ?? latestTerminal?.turnId ?? null,
|
|
220
|
+
startedAt: latestUser?.timestamp ?? null,
|
|
221
|
+
updatedAt: latestTimestamp,
|
|
222
|
+
},
|
|
223
|
+
events,
|
|
224
|
+
latestAgentMessage: latestAgent?.text ?? null,
|
|
225
|
+
latestUserMessage: latestUser?.text ?? null,
|
|
226
|
+
latestToolName: latestTool?.toolName ?? null,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function parseClaudeCodeActivityEvents(rows, sessionId, afterLine) {
|
|
234
|
+
const events = [];
|
|
235
|
+
let latestTimestamp = null;
|
|
236
|
+
let currentTurnId = null;
|
|
237
|
+
const toolNamesById = new Map();
|
|
238
|
+
for (const { lineNumber, entry } of rows) {
|
|
239
|
+
const timestamp = dateValue(entry.timestamp) ?? dateValue(entry.createdAt) ?? dateValue(entry.created_at);
|
|
240
|
+
if (timestamp && (!latestTimestamp || timestamp > latestTimestamp)) {
|
|
241
|
+
latestTimestamp = timestamp;
|
|
242
|
+
}
|
|
243
|
+
const type = stringValue(entry.type) ?? "entry";
|
|
244
|
+
const subtype = stringValue(entry.subtype);
|
|
245
|
+
if (type === "user") {
|
|
246
|
+
if (isToolResultEntry(entry)) {
|
|
247
|
+
for (const tool of extractToolResults(entry)) {
|
|
248
|
+
pushEvent(events, afterLine, {
|
|
249
|
+
lineNumber,
|
|
250
|
+
kind: "tool",
|
|
251
|
+
timestamp,
|
|
252
|
+
type: "tool_result",
|
|
253
|
+
turnId: currentTurnId,
|
|
254
|
+
status: tool.isError ? "failed" : "finished",
|
|
255
|
+
text: tool.text,
|
|
256
|
+
toolName: tool.name ?? (tool.id ? toolNamesById.get(tool.id) : undefined) ?? "tool",
|
|
257
|
+
phase: null,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
currentTurnId = `claude-code-${sessionId}-${lineNumber}`;
|
|
263
|
+
const text = extractEntryText(entry);
|
|
264
|
+
pushEvent(events, afterLine, {
|
|
265
|
+
lineNumber,
|
|
266
|
+
kind: "task",
|
|
267
|
+
timestamp,
|
|
268
|
+
type: "turn",
|
|
269
|
+
turnId: currentTurnId,
|
|
270
|
+
status: "started",
|
|
271
|
+
text,
|
|
272
|
+
toolName: null,
|
|
273
|
+
phase: null,
|
|
274
|
+
});
|
|
275
|
+
pushEvent(events, afterLine, {
|
|
276
|
+
lineNumber,
|
|
277
|
+
kind: "user",
|
|
278
|
+
timestamp,
|
|
279
|
+
type,
|
|
280
|
+
turnId: currentTurnId,
|
|
281
|
+
status: null,
|
|
282
|
+
text,
|
|
283
|
+
toolName: null,
|
|
284
|
+
phase: null,
|
|
285
|
+
});
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (type === "assistant") {
|
|
289
|
+
for (const tool of extractToolUses(entry)) {
|
|
290
|
+
if (tool.id && tool.name) {
|
|
291
|
+
toolNamesById.set(tool.id, tool.name);
|
|
292
|
+
}
|
|
293
|
+
pushEvent(events, afterLine, {
|
|
294
|
+
lineNumber,
|
|
295
|
+
kind: "tool",
|
|
296
|
+
timestamp,
|
|
297
|
+
type: "tool_use",
|
|
298
|
+
turnId: currentTurnId,
|
|
299
|
+
status: "started",
|
|
300
|
+
text: tool.text,
|
|
301
|
+
toolName: tool.name ?? "tool",
|
|
302
|
+
phase: null,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const text = extractEntryText(entry);
|
|
306
|
+
if (text) {
|
|
307
|
+
pushEvent(events, afterLine, {
|
|
308
|
+
lineNumber,
|
|
309
|
+
kind: "agent",
|
|
310
|
+
timestamp,
|
|
311
|
+
type,
|
|
312
|
+
turnId: currentTurnId,
|
|
313
|
+
status: "completed",
|
|
314
|
+
text,
|
|
315
|
+
toolName: null,
|
|
316
|
+
phase: null,
|
|
317
|
+
});
|
|
318
|
+
pushEvent(events, afterLine, {
|
|
319
|
+
lineNumber,
|
|
320
|
+
kind: "task",
|
|
321
|
+
timestamp,
|
|
322
|
+
type: "turn",
|
|
323
|
+
turnId: currentTurnId,
|
|
324
|
+
status: "completed",
|
|
325
|
+
text: null,
|
|
326
|
+
toolName: null,
|
|
327
|
+
phase: null,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (type === "result") {
|
|
333
|
+
pushEvent(events, afterLine, {
|
|
334
|
+
lineNumber,
|
|
335
|
+
kind: "task",
|
|
336
|
+
timestamp,
|
|
337
|
+
type: subtype ?? type,
|
|
338
|
+
turnId: currentTurnId,
|
|
339
|
+
status: subtype && subtype !== "success" ? "failed" : "completed",
|
|
340
|
+
text: stringValue(entry.result) ?? firstString(entry.errors),
|
|
341
|
+
toolName: null,
|
|
342
|
+
phase: null,
|
|
343
|
+
});
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (type === "system" && subtype === "session_state_changed") {
|
|
347
|
+
const state = stringValue(entry.state);
|
|
348
|
+
if (state === "running" || state === "requires_action") {
|
|
349
|
+
pushEvent(events, afterLine, {
|
|
350
|
+
lineNumber,
|
|
351
|
+
kind: "task",
|
|
352
|
+
timestamp,
|
|
353
|
+
type: subtype,
|
|
354
|
+
turnId: currentTurnId,
|
|
355
|
+
status: "started",
|
|
356
|
+
text: state,
|
|
357
|
+
toolName: null,
|
|
358
|
+
phase: state,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
else if (state === "idle") {
|
|
362
|
+
pushEvent(events, afterLine, {
|
|
363
|
+
lineNumber,
|
|
364
|
+
kind: "task",
|
|
365
|
+
timestamp,
|
|
366
|
+
type: subtype,
|
|
367
|
+
turnId: currentTurnId,
|
|
368
|
+
status: "completed",
|
|
369
|
+
text: state,
|
|
370
|
+
toolName: null,
|
|
371
|
+
phase: state,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
if (type === "tool_progress" || subtype === "task_progress" || subtype === "task_started" || subtype === "task_notification") {
|
|
377
|
+
pushEvent(events, afterLine, {
|
|
378
|
+
lineNumber,
|
|
379
|
+
kind: "tool",
|
|
380
|
+
timestamp,
|
|
381
|
+
type: subtype ?? type,
|
|
382
|
+
turnId: currentTurnId,
|
|
383
|
+
status: subtype === "task_notification" ? "finished" : "started",
|
|
384
|
+
text: stringValue(entry.description) ?? stringValue(entry.summary) ?? stringValue(entry.output_file),
|
|
385
|
+
toolName: stringValue(entry.tool_name) ?? stringValue(entry.last_tool_name) ?? stringValue(entry.task_type) ?? "task",
|
|
386
|
+
phase: null,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return { events, latestTimestamp };
|
|
391
|
+
}
|
|
392
|
+
function readJsonlLines(filePath) {
|
|
393
|
+
return readFileSync(filePath, "utf8")
|
|
394
|
+
.split(/\r?\n/)
|
|
395
|
+
.map((line, index) => ({ lineNumber: index + 1, line }))
|
|
396
|
+
.filter(({ line }) => line.trim().length > 0)
|
|
397
|
+
.map(({ lineNumber, line }) => ({ lineNumber, entry: safeJsonParse(line) }))
|
|
398
|
+
.filter((row) => Boolean(row.entry));
|
|
399
|
+
}
|
|
400
|
+
function isToolResultEntry(entry) {
|
|
401
|
+
const message = objectValue(entry.message);
|
|
402
|
+
const content = message?.content ?? entry.content;
|
|
403
|
+
return Array.isArray(content) && content.some((part) => stringValue(objectValue(part)?.type) === "tool_result");
|
|
404
|
+
}
|
|
405
|
+
function extractToolUses(entry) {
|
|
406
|
+
const content = contentArray(entry);
|
|
407
|
+
return content
|
|
408
|
+
.map((part) => objectValue(part))
|
|
409
|
+
.filter((part) => part !== null && stringValue(part.type) === "tool_use")
|
|
410
|
+
.map((part) => ({
|
|
411
|
+
id: stringValue(part.id),
|
|
412
|
+
name: stringValue(part.name),
|
|
413
|
+
text: stringifyPreview(part.input),
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
function extractToolResults(entry) {
|
|
417
|
+
const content = contentArray(entry);
|
|
418
|
+
return content
|
|
419
|
+
.map((part) => objectValue(part))
|
|
420
|
+
.filter((part) => part !== null && stringValue(part.type) === "tool_result")
|
|
421
|
+
.map((part) => ({
|
|
422
|
+
id: stringValue(part.tool_use_id) ?? stringValue(part.toolUseId),
|
|
423
|
+
name: stringValue(part.name),
|
|
424
|
+
text: extractContentText(part),
|
|
425
|
+
isError: booleanValue(part.is_error) ?? booleanValue(part.isError) ?? false,
|
|
426
|
+
}));
|
|
427
|
+
}
|
|
428
|
+
function extractEntryText(entry) {
|
|
429
|
+
const message = objectValue(entry.message);
|
|
430
|
+
const direct = stringValue(entry.text) ?? stringValue(entry.result);
|
|
431
|
+
if (direct) {
|
|
432
|
+
return direct;
|
|
433
|
+
}
|
|
434
|
+
return extractContentText(message ?? entry);
|
|
435
|
+
}
|
|
436
|
+
function extractContentText(container) {
|
|
437
|
+
if (!container) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
const direct = stringValue(container.text) ?? stringValue(container.content) ?? stringValue(container.summary);
|
|
441
|
+
if (direct) {
|
|
442
|
+
return direct;
|
|
443
|
+
}
|
|
444
|
+
const content = container.content;
|
|
445
|
+
if (!Array.isArray(content)) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
const parts = content
|
|
449
|
+
.map((part) => {
|
|
450
|
+
const block = objectValue(part);
|
|
451
|
+
if (!block) {
|
|
452
|
+
return "";
|
|
453
|
+
}
|
|
454
|
+
if (stringValue(block.type) === "tool_use" || stringValue(block.type) === "tool_result") {
|
|
455
|
+
return "";
|
|
456
|
+
}
|
|
457
|
+
return stringValue(block.text) ?? stringValue(block.thinking) ?? "";
|
|
458
|
+
})
|
|
459
|
+
.filter(Boolean);
|
|
460
|
+
return parts.join("\n").trim() || null;
|
|
461
|
+
}
|
|
462
|
+
function contentArray(entry) {
|
|
463
|
+
const message = objectValue(entry.message);
|
|
464
|
+
const content = message?.content ?? entry.content;
|
|
465
|
+
return Array.isArray(content) ? content : [];
|
|
466
|
+
}
|
|
467
|
+
function reasoningValue(entry) {
|
|
468
|
+
const effort = objectValue(entry.effort) ?? objectValue(entry.message);
|
|
469
|
+
return stringValue(objectValue(effort?.effort)?.level)
|
|
470
|
+
?? stringValue(objectValue(effort?.thinking)?.level)
|
|
471
|
+
?? stringValue(effort?.reasoningEffort)
|
|
472
|
+
?? stringValue(effort?.reasoning_effort);
|
|
473
|
+
}
|
|
474
|
+
function usageFromObject(value) {
|
|
475
|
+
const usage = objectValue(value);
|
|
476
|
+
if (!usage) {
|
|
477
|
+
return undefined;
|
|
478
|
+
}
|
|
479
|
+
const input = numberValue(usage.input_tokens) ?? numberValue(usage.inputTokens) ?? 0;
|
|
480
|
+
const output = numberValue(usage.output_tokens) ?? numberValue(usage.outputTokens) ?? 0;
|
|
481
|
+
const cacheRead = numberValue(usage.cache_read_input_tokens) ?? numberValue(usage.cacheReadInputTokens) ?? numberValue(usage.cache_read_tokens) ?? 0;
|
|
482
|
+
const cacheWrite = numberValue(usage.cache_creation_input_tokens) ?? numberValue(usage.cacheCreationInputTokens) ?? numberValue(usage.cache_write_tokens) ?? 0;
|
|
483
|
+
const total = input + output + cacheRead + cacheWrite;
|
|
484
|
+
if (total <= 0) {
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
487
|
+
return { input, output, cacheRead, cacheWrite, total, cost: numberValue(usage.cost) ?? undefined };
|
|
488
|
+
}
|
|
489
|
+
function decodeClaudeCodeProjectKey(projectKey) {
|
|
490
|
+
if (!projectKey) {
|
|
491
|
+
return process.cwd();
|
|
492
|
+
}
|
|
493
|
+
if (projectKey.startsWith("file-")) {
|
|
494
|
+
try {
|
|
495
|
+
return decodeURIComponent(projectKey.slice("file-".length));
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
return projectKey;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (projectKey.startsWith("-")) {
|
|
502
|
+
return path.normalize(`/${projectKey.slice(1).replace(/-/g, "/")}`);
|
|
503
|
+
}
|
|
504
|
+
return path.normalize(projectKey.replace(/-/g, path.sep));
|
|
505
|
+
}
|
|
506
|
+
function safeReadDir(directory) {
|
|
507
|
+
try {
|
|
508
|
+
return readdirSync(directory);
|
|
509
|
+
}
|
|
510
|
+
catch {
|
|
511
|
+
return [];
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function safeStat(targetPath) {
|
|
515
|
+
try {
|
|
516
|
+
return statSync(targetPath);
|
|
517
|
+
}
|
|
518
|
+
catch {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function safeJsonParse(line) {
|
|
523
|
+
try {
|
|
524
|
+
return objectValue(JSON.parse(line));
|
|
525
|
+
}
|
|
526
|
+
catch {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
function pushEvent(events, afterLine, event) {
|
|
531
|
+
if (event.lineNumber > afterLine) {
|
|
532
|
+
events.push(event);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function objectValue(value) {
|
|
536
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
537
|
+
}
|
|
538
|
+
function stringValue(value) {
|
|
539
|
+
if (typeof value === "string" && value.trim()) {
|
|
540
|
+
return value;
|
|
541
|
+
}
|
|
542
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
543
|
+
return String(value);
|
|
544
|
+
}
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
function numberValue(value) {
|
|
548
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
549
|
+
}
|
|
550
|
+
function booleanValue(value) {
|
|
551
|
+
return typeof value === "boolean" ? value : null;
|
|
552
|
+
}
|
|
553
|
+
function dateValue(value) {
|
|
554
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
555
|
+
return new Date(value > 10_000_000_000 ? value : value * 1000);
|
|
556
|
+
}
|
|
557
|
+
if (typeof value === "string" && value.trim()) {
|
|
558
|
+
const timestamp = Date.parse(value);
|
|
559
|
+
return Number.isNaN(timestamp) ? null : new Date(timestamp);
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
function firstString(value) {
|
|
564
|
+
if (!Array.isArray(value)) {
|
|
565
|
+
return stringValue(value);
|
|
566
|
+
}
|
|
567
|
+
return value.map(stringValue).find(Boolean) ?? null;
|
|
568
|
+
}
|
|
569
|
+
function stringifyPreview(value) {
|
|
570
|
+
if (value === undefined || value === null) {
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
if (typeof value === "string") {
|
|
574
|
+
return value.trim() || null;
|
|
575
|
+
}
|
|
576
|
+
try {
|
|
577
|
+
const text = JSON.stringify(value);
|
|
578
|
+
return text.length <= 500 ? text : `${text.slice(0, 497)}...`;
|
|
579
|
+
}
|
|
580
|
+
catch {
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function summarizeTitle(text) {
|
|
585
|
+
if (!text) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
589
|
+
return normalized.length <= 60 ? normalized : `${normalized.slice(0, 57)}...`;
|
|
590
|
+
}
|
package/dist/codex-session.js
CHANGED
|
@@ -284,9 +284,20 @@ export class CodexSessionService {
|
|
|
284
284
|
listWorkspaces() {
|
|
285
285
|
return listWorkspaces();
|
|
286
286
|
}
|
|
287
|
+
async refreshModels() {
|
|
288
|
+
// Codex models are read from local state on each listModels() call.
|
|
289
|
+
}
|
|
287
290
|
listModels() {
|
|
288
291
|
return listModels();
|
|
289
292
|
}
|
|
293
|
+
listLaunchProfiles() {
|
|
294
|
+
return this.config.launchProfiles.map((profile) => ({
|
|
295
|
+
id: profile.id,
|
|
296
|
+
label: profile.label,
|
|
297
|
+
behavior: formatLaunchProfileBehavior(profile),
|
|
298
|
+
unsafe: profile.unsafe,
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
290
301
|
getSessionRecord(threadId) {
|
|
291
302
|
const record = getThread(threadId);
|
|
292
303
|
return record ? toAgentThreadRecord(record) : null;
|
|
@@ -338,7 +349,7 @@ export class CodexSessionService {
|
|
|
338
349
|
getSelectedLaunchProfile() {
|
|
339
350
|
return this.currentLaunchProfile;
|
|
340
351
|
}
|
|
341
|
-
|
|
352
|
+
syncFromAgentState(options = {}) {
|
|
342
353
|
const activeThreadId = this.thread?.id ?? this.currentThreadId;
|
|
343
354
|
const before = {
|
|
344
355
|
workspace: this.currentWorkspace,
|