@ycodium-ai/plugin-session-import 0.2.2988 → 0.2.2992
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/index.js +489 -487
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -165,7 +165,6 @@ var sessionImportSyncResultSchema = {
|
|
|
165
165
|
|
|
166
166
|
// src/sessionImportRuntime.ts
|
|
167
167
|
import * as NodeCrypto3 from "node:crypto";
|
|
168
|
-
import * as NodeProcess2 from "node:process";
|
|
169
168
|
|
|
170
169
|
// src/claudeSessionScan.ts
|
|
171
170
|
import * as NodePath from "node:path";
|
|
@@ -962,144 +961,6 @@ async function claudeSessionModifiedAt(input) {
|
|
|
962
961
|
}
|
|
963
962
|
}
|
|
964
963
|
|
|
965
|
-
// src/codexAppServer.ts
|
|
966
|
-
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
967
|
-
var STDERR_TAIL_LIMIT = 2e3;
|
|
968
|
-
var CODEX_INITIALIZE_PARAMS = {
|
|
969
|
-
clientInfo: { name: "ycodium", title: "Ycodium", version: "0" },
|
|
970
|
-
capabilities: { experimentalApi: true }
|
|
971
|
-
};
|
|
972
|
-
async function openCodexAppServer(input) {
|
|
973
|
-
const handle = await input.process.spawn({
|
|
974
|
-
program: input.program,
|
|
975
|
-
args: ["app-server", ...input.args],
|
|
976
|
-
...input.env === void 0 ? {} : { env: input.env }
|
|
977
|
-
});
|
|
978
|
-
const pending = /* @__PURE__ */ new Map();
|
|
979
|
-
const decoder = new TextDecoder();
|
|
980
|
-
let nextId = 1;
|
|
981
|
-
let stderrTail = "";
|
|
982
|
-
let exitReason = null;
|
|
983
|
-
const failAll = (reason) => {
|
|
984
|
-
exitReason = reason;
|
|
985
|
-
for (const [, entry] of pending) {
|
|
986
|
-
entry.reject(new Error(`codex app-server ${entry.method} failed: ${reason}`));
|
|
987
|
-
}
|
|
988
|
-
pending.clear();
|
|
989
|
-
};
|
|
990
|
-
const dispatch = (line) => {
|
|
991
|
-
if (line.trim().length === 0) return;
|
|
992
|
-
let message;
|
|
993
|
-
try {
|
|
994
|
-
message = JSON.parse(line);
|
|
995
|
-
} catch {
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
if (!message || typeof message !== "object") return;
|
|
999
|
-
const typed = message;
|
|
1000
|
-
if (typeof typed.id !== "number") return;
|
|
1001
|
-
const entry = pending.get(typed.id);
|
|
1002
|
-
if (entry === void 0) return;
|
|
1003
|
-
pending.delete(typed.id);
|
|
1004
|
-
if (typed.error !== void 0 && typed.error !== null) {
|
|
1005
|
-
const detail = typeof typed.error.message === "string" ? typed.error.message : JSON.stringify(typed.error);
|
|
1006
|
-
entry.reject(new Error(`codex app-server ${entry.method} failed: ${detail}`));
|
|
1007
|
-
return;
|
|
1008
|
-
}
|
|
1009
|
-
entry.resolve(typed.result);
|
|
1010
|
-
};
|
|
1011
|
-
const pumpStdout = (async () => {
|
|
1012
|
-
const fragments = [];
|
|
1013
|
-
for await (const chunk of handle.stdout) {
|
|
1014
|
-
const text = decoder.decode(chunk, { stream: true });
|
|
1015
|
-
let start = 0;
|
|
1016
|
-
for (let newline = text.indexOf("\n"); newline !== -1; newline = text.indexOf("\n", start)) {
|
|
1017
|
-
fragments.push(text.slice(start, newline));
|
|
1018
|
-
const line = fragments.join("");
|
|
1019
|
-
fragments.length = 0;
|
|
1020
|
-
dispatch(line);
|
|
1021
|
-
start = newline + 1;
|
|
1022
|
-
}
|
|
1023
|
-
if (start < text.length) fragments.push(text.slice(start));
|
|
1024
|
-
}
|
|
1025
|
-
dispatch(fragments.join(""));
|
|
1026
|
-
})().catch(() => void 0);
|
|
1027
|
-
const pumpStderr = (async () => {
|
|
1028
|
-
const errorDecoder = new TextDecoder();
|
|
1029
|
-
for await (const chunk of handle.stderr) {
|
|
1030
|
-
stderrTail = `${stderrTail}${errorDecoder.decode(chunk, { stream: true })}`.slice(
|
|
1031
|
-
-STDERR_TAIL_LIMIT
|
|
1032
|
-
);
|
|
1033
|
-
}
|
|
1034
|
-
})().catch(() => void 0);
|
|
1035
|
-
void handle.exited.then((code) => {
|
|
1036
|
-
failAll(
|
|
1037
|
-
`the process exited with code ${String(code)}${stderrTail.length > 0 ? `: ${stderrTail.trim()}` : ""}`
|
|
1038
|
-
);
|
|
1039
|
-
}).catch(() => failAll("the process ended unexpectedly"));
|
|
1040
|
-
const timeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1041
|
-
const request = async (method, params) => {
|
|
1042
|
-
if (exitReason !== null) throw new Error(`codex app-server ${method} failed: ${exitReason}`);
|
|
1043
|
-
const id = nextId;
|
|
1044
|
-
nextId += 1;
|
|
1045
|
-
const settled = new Promise((resolve, reject) => {
|
|
1046
|
-
pending.set(id, { resolve, reject, method });
|
|
1047
|
-
});
|
|
1048
|
-
try {
|
|
1049
|
-
await Promise.race([
|
|
1050
|
-
handle.writeStdin(
|
|
1051
|
-
`${JSON.stringify({ id, method, ...params === void 0 ? {} : { params } })}
|
|
1052
|
-
`
|
|
1053
|
-
),
|
|
1054
|
-
settled
|
|
1055
|
-
]);
|
|
1056
|
-
} catch (error) {
|
|
1057
|
-
pending.delete(id);
|
|
1058
|
-
throw error;
|
|
1059
|
-
}
|
|
1060
|
-
let timer;
|
|
1061
|
-
try {
|
|
1062
|
-
return await Promise.race([
|
|
1063
|
-
settled,
|
|
1064
|
-
new Promise((_, reject) => {
|
|
1065
|
-
timer = setTimeout(() => {
|
|
1066
|
-
pending.delete(id);
|
|
1067
|
-
reject(new Error(`codex app-server ${method} timed out after ${String(timeoutMs)}ms`));
|
|
1068
|
-
}, timeoutMs);
|
|
1069
|
-
})
|
|
1070
|
-
]);
|
|
1071
|
-
} finally {
|
|
1072
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1075
|
-
const notify = async (method, params) => {
|
|
1076
|
-
await handle.writeStdin(
|
|
1077
|
-
`${JSON.stringify({ method, ...params === void 0 ? {} : { params } })}
|
|
1078
|
-
`
|
|
1079
|
-
);
|
|
1080
|
-
};
|
|
1081
|
-
const close = async () => {
|
|
1082
|
-
try {
|
|
1083
|
-
await handle.closeStdin();
|
|
1084
|
-
} catch {
|
|
1085
|
-
}
|
|
1086
|
-
try {
|
|
1087
|
-
await handle.kill();
|
|
1088
|
-
} catch {
|
|
1089
|
-
}
|
|
1090
|
-
failAll("the client was closed");
|
|
1091
|
-
await Promise.all([pumpStdout, pumpStderr]);
|
|
1092
|
-
};
|
|
1093
|
-
try {
|
|
1094
|
-
await request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
1095
|
-
await notify("initialized");
|
|
1096
|
-
} catch (error) {
|
|
1097
|
-
await close();
|
|
1098
|
-
throw error;
|
|
1099
|
-
}
|
|
1100
|
-
return { request, notify, close, pendingCountForTests: () => pending.size };
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
964
|
// src/codexSessionScan.ts
|
|
1104
965
|
import * as NodePath2 from "node:path";
|
|
1105
966
|
var PAGE_LIMIT = 50;
|
|
@@ -1468,102 +1329,498 @@ function reconcileImportedTimeline(input) {
|
|
|
1468
1329
|
return { messages, activities, hasLegacyMatches, appendedMessageCount };
|
|
1469
1330
|
}
|
|
1470
1331
|
|
|
1471
|
-
// src/
|
|
1472
|
-
import * as
|
|
1332
|
+
// src/sessionImportListing.ts
|
|
1333
|
+
import * as NodeProcess2 from "node:process";
|
|
1473
1334
|
|
|
1474
|
-
// src/
|
|
1475
|
-
var
|
|
1476
|
-
var
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
this.name = "SessionImportFailure";
|
|
1481
|
-
this.code = code;
|
|
1482
|
-
}
|
|
1335
|
+
// src/codexAppServer.ts
|
|
1336
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
1337
|
+
var STDERR_TAIL_LIMIT = 2e3;
|
|
1338
|
+
var CODEX_INITIALIZE_PARAMS = {
|
|
1339
|
+
clientInfo: { name: "ycodium", title: "Ycodium", version: "0" },
|
|
1340
|
+
capabilities: { experimentalApi: true }
|
|
1483
1341
|
};
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
const
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
return `record:${NodeCrypto2.createHash("sha256").update(text).digest("hex").slice(0, 20)}`;
|
|
1502
|
-
}
|
|
1503
|
-
function readableContent(value) {
|
|
1504
|
-
if (typeof value === "string") return value;
|
|
1505
|
-
if (!Array.isArray(value)) return "";
|
|
1506
|
-
return value.flatMap((entry) => {
|
|
1507
|
-
if (typeof entry === "string") return [entry];
|
|
1508
|
-
const block = record2(entry);
|
|
1509
|
-
return block && ["text", "input_text", "output_text", "summary_text", "reasoning_text"].includes(
|
|
1510
|
-
String(block.type)
|
|
1511
|
-
) && typeof block.text === "string" ? [block.text] : [];
|
|
1512
|
-
}).join("\n\n");
|
|
1513
|
-
}
|
|
1514
|
-
function toolInput(value) {
|
|
1515
|
-
if (typeof value === "string") return value;
|
|
1516
|
-
const input = record2(value);
|
|
1517
|
-
if (input && Object.keys(input).length === 1 && typeof input.command === "string")
|
|
1518
|
-
return input.command;
|
|
1519
|
-
if (input && Object.keys(input).length === 1 && typeof input.cmd === "string") return input.cmd;
|
|
1520
|
-
if (value === void 0 || value === null) return void 0;
|
|
1521
|
-
try {
|
|
1522
|
-
return JSON.stringify(value, null, 2);
|
|
1523
|
-
} catch {
|
|
1524
|
-
return void 0;
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
function toolType(name) {
|
|
1528
|
-
const normalized = name.toLowerCase();
|
|
1529
|
-
if (/^(bash|shell|command|terminal|exec_command|write_stdin)$/u.test(normalized))
|
|
1530
|
-
return "command_execution";
|
|
1531
|
-
if (/^(edit|write|multiedit|apply_patch|applypatch|patch)$/u.test(normalized))
|
|
1532
|
-
return "file_change";
|
|
1533
|
-
if (/^(agent|task|workflow)$/u.test(normalized)) return "dynamic_tool_call";
|
|
1534
|
-
if (normalized.startsWith("mcp")) return "mcp_tool_call";
|
|
1535
|
-
if (/^(websearch|webfetch|web_search)$/u.test(normalized)) return "web_search";
|
|
1536
|
-
return "dynamic_tool_call";
|
|
1537
|
-
}
|
|
1538
|
-
function limit(messages, activities) {
|
|
1539
|
-
const availableMessages = messages.length;
|
|
1540
|
-
const retainedMessages = messages.slice(-SESSION_IMPORT_MESSAGE_LIMIT);
|
|
1541
|
-
const floor = availableMessages > SESSION_IMPORT_MESSAGE_LIMIT ? retainedMessages[0]?.sourceOrder ?? 0 : 0;
|
|
1542
|
-
const visibleActivities = activities.filter((activity) => activity.sourceOrder >= floor);
|
|
1543
|
-
const retainedActivities = visibleActivities.slice(-SESSION_IMPORT_ACTIVITY_LIMIT);
|
|
1544
|
-
return {
|
|
1545
|
-
availableMessages,
|
|
1546
|
-
availableActivities: activities.length,
|
|
1547
|
-
...retainedActivities.length < activities.length ? { activityTruncated: true } : {},
|
|
1548
|
-
messages: retainedMessages,
|
|
1549
|
-
activities: retainedActivities
|
|
1550
|
-
};
|
|
1551
|
-
}
|
|
1552
|
-
function bounded(value, tail = false) {
|
|
1553
|
-
if (value.length <= SESSION_IMPORT_ACTIVITY_TEXT_LIMIT) return { text: value, truncated: false };
|
|
1554
|
-
const offset = tail ? value.length - SESSION_IMPORT_ACTIVITY_TEXT_LIMIT : SESSION_IMPORT_ACTIVITY_TEXT_LIMIT;
|
|
1555
|
-
const character = value.charCodeAt(offset);
|
|
1556
|
-
const previous = value.charCodeAt(offset - 1);
|
|
1557
|
-
const splitPair = character >= 56320 && character <= 57343 && previous >= 55296 && previous <= 56319;
|
|
1558
|
-
return {
|
|
1559
|
-
text: tail ? value.slice(offset + (splitPair ? 1 : 0)) : value.slice(0, offset - (splitPair ? 1 : 0)),
|
|
1560
|
-
truncated: true
|
|
1342
|
+
async function openCodexAppServer(input) {
|
|
1343
|
+
const handle = await input.process.spawn({
|
|
1344
|
+
program: input.program,
|
|
1345
|
+
args: ["app-server", ...input.args],
|
|
1346
|
+
...input.env === void 0 ? {} : { env: input.env }
|
|
1347
|
+
});
|
|
1348
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1349
|
+
const decoder = new TextDecoder();
|
|
1350
|
+
let nextId = 1;
|
|
1351
|
+
let stderrTail = "";
|
|
1352
|
+
let exitReason = null;
|
|
1353
|
+
const failAll = (reason) => {
|
|
1354
|
+
exitReason = reason;
|
|
1355
|
+
for (const [, entry] of pending) {
|
|
1356
|
+
entry.reject(new Error(`codex app-server ${entry.method} failed: ${reason}`));
|
|
1357
|
+
}
|
|
1358
|
+
pending.clear();
|
|
1561
1359
|
};
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1360
|
+
const dispatch = (line) => {
|
|
1361
|
+
if (line.trim().length === 0) return;
|
|
1362
|
+
let message;
|
|
1363
|
+
try {
|
|
1364
|
+
message = JSON.parse(line);
|
|
1365
|
+
} catch {
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
if (!message || typeof message !== "object") return;
|
|
1369
|
+
const typed = message;
|
|
1370
|
+
if (typeof typed.id !== "number") return;
|
|
1371
|
+
const entry = pending.get(typed.id);
|
|
1372
|
+
if (entry === void 0) return;
|
|
1373
|
+
pending.delete(typed.id);
|
|
1374
|
+
if (typed.error !== void 0 && typed.error !== null) {
|
|
1375
|
+
const detail = typeof typed.error.message === "string" ? typed.error.message : JSON.stringify(typed.error);
|
|
1376
|
+
entry.reject(new Error(`codex app-server ${entry.method} failed: ${detail}`));
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
entry.resolve(typed.result);
|
|
1380
|
+
};
|
|
1381
|
+
const pumpStdout = (async () => {
|
|
1382
|
+
const fragments = [];
|
|
1383
|
+
for await (const chunk of handle.stdout) {
|
|
1384
|
+
const text = decoder.decode(chunk, { stream: true });
|
|
1385
|
+
let start = 0;
|
|
1386
|
+
for (let newline = text.indexOf("\n"); newline !== -1; newline = text.indexOf("\n", start)) {
|
|
1387
|
+
fragments.push(text.slice(start, newline));
|
|
1388
|
+
const line = fragments.join("");
|
|
1389
|
+
fragments.length = 0;
|
|
1390
|
+
dispatch(line);
|
|
1391
|
+
start = newline + 1;
|
|
1392
|
+
}
|
|
1393
|
+
if (start < text.length) fragments.push(text.slice(start));
|
|
1394
|
+
}
|
|
1395
|
+
dispatch(fragments.join(""));
|
|
1396
|
+
})().catch(() => void 0);
|
|
1397
|
+
const pumpStderr = (async () => {
|
|
1398
|
+
const errorDecoder = new TextDecoder();
|
|
1399
|
+
for await (const chunk of handle.stderr) {
|
|
1400
|
+
stderrTail = `${stderrTail}${errorDecoder.decode(chunk, { stream: true })}`.slice(
|
|
1401
|
+
-STDERR_TAIL_LIMIT
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
})().catch(() => void 0);
|
|
1405
|
+
void handle.exited.then((code) => {
|
|
1406
|
+
failAll(
|
|
1407
|
+
`the process exited with code ${String(code)}${stderrTail.length > 0 ? `: ${stderrTail.trim()}` : ""}`
|
|
1408
|
+
);
|
|
1409
|
+
}).catch(() => failAll("the process ended unexpectedly"));
|
|
1410
|
+
const timeoutMs = input.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
1411
|
+
const request = async (method, params) => {
|
|
1412
|
+
if (exitReason !== null) throw new Error(`codex app-server ${method} failed: ${exitReason}`);
|
|
1413
|
+
const id = nextId;
|
|
1414
|
+
nextId += 1;
|
|
1415
|
+
const settled = new Promise((resolve, reject) => {
|
|
1416
|
+
pending.set(id, { resolve, reject, method });
|
|
1417
|
+
});
|
|
1418
|
+
try {
|
|
1419
|
+
await Promise.race([
|
|
1420
|
+
handle.writeStdin(
|
|
1421
|
+
`${JSON.stringify({ id, method, ...params === void 0 ? {} : { params } })}
|
|
1422
|
+
`
|
|
1423
|
+
),
|
|
1424
|
+
settled
|
|
1425
|
+
]);
|
|
1426
|
+
} catch (error) {
|
|
1427
|
+
pending.delete(id);
|
|
1428
|
+
throw error;
|
|
1429
|
+
}
|
|
1430
|
+
let timer;
|
|
1431
|
+
try {
|
|
1432
|
+
return await Promise.race([
|
|
1433
|
+
settled,
|
|
1434
|
+
new Promise((_, reject) => {
|
|
1435
|
+
timer = setTimeout(() => {
|
|
1436
|
+
pending.delete(id);
|
|
1437
|
+
reject(new Error(`codex app-server ${method} timed out after ${String(timeoutMs)}ms`));
|
|
1438
|
+
}, timeoutMs);
|
|
1439
|
+
})
|
|
1440
|
+
]);
|
|
1441
|
+
} finally {
|
|
1442
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
1443
|
+
}
|
|
1444
|
+
};
|
|
1445
|
+
const notify = async (method, params) => {
|
|
1446
|
+
await handle.writeStdin(
|
|
1447
|
+
`${JSON.stringify({ method, ...params === void 0 ? {} : { params } })}
|
|
1448
|
+
`
|
|
1449
|
+
);
|
|
1450
|
+
};
|
|
1451
|
+
const close = async () => {
|
|
1452
|
+
try {
|
|
1453
|
+
await handle.closeStdin();
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
try {
|
|
1457
|
+
await handle.kill();
|
|
1458
|
+
} catch {
|
|
1459
|
+
}
|
|
1460
|
+
failAll("the client was closed");
|
|
1461
|
+
await Promise.all([pumpStdout, pumpStderr]);
|
|
1462
|
+
};
|
|
1463
|
+
try {
|
|
1464
|
+
await request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
1465
|
+
await notify("initialized");
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
await close();
|
|
1468
|
+
throw error;
|
|
1469
|
+
}
|
|
1470
|
+
return { request, notify, close, pendingCountForTests: () => pending.size };
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// src/wireTypes.ts
|
|
1474
|
+
var SESSION_IMPORT_MESSAGE_LIMIT = 200;
|
|
1475
|
+
var SessionImportFailure = class extends Error {
|
|
1476
|
+
code;
|
|
1477
|
+
constructor(code, message) {
|
|
1478
|
+
super(message);
|
|
1479
|
+
this.name = "SessionImportFailure";
|
|
1480
|
+
this.code = code;
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
|
|
1484
|
+
// src/sessionImportListing.ts
|
|
1485
|
+
var CLAUDE_DRIVER = "claudeAgent";
|
|
1486
|
+
var CODEX_DRIVER = "codex";
|
|
1487
|
+
var providerKindOfDriver = (driverKind) => driverKind === CLAUDE_DRIVER ? "claude" : driverKind === CODEX_DRIVER ? "codex" : void 0;
|
|
1488
|
+
var importedThreadKey = (provider, nativeSessionId) => `${provider}:${nativeSessionId}`;
|
|
1489
|
+
var caseInsensitivePaths = () => NodeProcess2.platform === "win32";
|
|
1490
|
+
function tokenizeLaunchArgs(value) {
|
|
1491
|
+
const input = value.trim();
|
|
1492
|
+
if (input.length === 0) return [];
|
|
1493
|
+
const tokens = [];
|
|
1494
|
+
let current = "";
|
|
1495
|
+
let quote = null;
|
|
1496
|
+
let quoted = false;
|
|
1497
|
+
for (const char of input) {
|
|
1498
|
+
if (quote !== null) {
|
|
1499
|
+
if (char === quote) quote = null;
|
|
1500
|
+
else current += char;
|
|
1501
|
+
continue;
|
|
1502
|
+
}
|
|
1503
|
+
if (char === "'" || char === '"') {
|
|
1504
|
+
quote = char;
|
|
1505
|
+
quoted = true;
|
|
1506
|
+
continue;
|
|
1507
|
+
}
|
|
1508
|
+
if (/\s/u.test(char)) {
|
|
1509
|
+
if (current.length > 0 || quoted) tokens.push(current);
|
|
1510
|
+
current = "";
|
|
1511
|
+
quoted = false;
|
|
1512
|
+
continue;
|
|
1513
|
+
}
|
|
1514
|
+
current += char;
|
|
1515
|
+
}
|
|
1516
|
+
if (current.length > 0 || quoted) tokens.push(current);
|
|
1517
|
+
return tokens;
|
|
1518
|
+
}
|
|
1519
|
+
var nativeSessionIdOf = (provider, cursor) => {
|
|
1520
|
+
if (cursor === null || typeof cursor !== "object") return null;
|
|
1521
|
+
const record3 = cursor;
|
|
1522
|
+
const value = provider === "claude" ? record3.resume ?? record3.sessionId : record3.threadId;
|
|
1523
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1524
|
+
};
|
|
1525
|
+
async function readImportedThreads(ctx) {
|
|
1526
|
+
const imported = /* @__PURE__ */ new Map();
|
|
1527
|
+
const bindings = await ctx.providers.sessions.list().catch(() => []);
|
|
1528
|
+
for (const binding of bindings) {
|
|
1529
|
+
const provider = providerKindOfDriver(binding.driver);
|
|
1530
|
+
if (provider === void 0) continue;
|
|
1531
|
+
const nativeId = nativeSessionIdOf(provider, binding.resumeCursor);
|
|
1532
|
+
if (nativeId === null) continue;
|
|
1533
|
+
imported.set(importedThreadKey(provider, nativeId), binding.threadId);
|
|
1534
|
+
}
|
|
1535
|
+
return imported;
|
|
1536
|
+
}
|
|
1537
|
+
var candidateOf = (input) => ({
|
|
1538
|
+
provider: input.provider,
|
|
1539
|
+
instanceId: input.instanceId,
|
|
1540
|
+
nativeSessionId: input.nativeSessionId,
|
|
1541
|
+
cwd: input.cwd,
|
|
1542
|
+
title: input.title,
|
|
1543
|
+
preview: input.preview,
|
|
1544
|
+
model: input.model,
|
|
1545
|
+
turnCount: input.turnCount,
|
|
1546
|
+
updatedAt: input.updatedAt,
|
|
1547
|
+
availability: input.importedThreadId !== void 0 ? "alreadyImported" : "importable",
|
|
1548
|
+
openElsewhere: input.openElsewhere,
|
|
1549
|
+
...input.importedThreadId !== void 0 ? { importedThreadId: input.importedThreadId } : {}
|
|
1550
|
+
});
|
|
1551
|
+
async function openCodexFor(ctx, instance) {
|
|
1552
|
+
const binaryPath = ctx.config.codexBinaries?.[instance.instanceId];
|
|
1553
|
+
if (binaryPath === void 0 || binaryPath.length === 0) {
|
|
1554
|
+
throw new Error(`No codex binary is configured for provider instance ${instance.instanceId}.`);
|
|
1555
|
+
}
|
|
1556
|
+
return openCodexAppServer({
|
|
1557
|
+
process: ctx.process,
|
|
1558
|
+
// 白名单条目的键就是 instanceId:路径由宿主查,插件手上不留第二份。
|
|
1559
|
+
program: { key: instance.instanceId },
|
|
1560
|
+
args: [...tokenizeLaunchArgs(instance.launchArgs.trim())],
|
|
1561
|
+
// 转录始终在 CODEX_HOME 下;实例的 home 是宿主解析过的那个(含 auth overlay)。
|
|
1562
|
+
...instance.homePath === null ? {} : { env: { CODEX_HOME: instance.homePath } }
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
async function withCodex(ctx, instance, use) {
|
|
1566
|
+
const client = await openCodexFor(ctx, instance);
|
|
1567
|
+
try {
|
|
1568
|
+
return await use(client);
|
|
1569
|
+
} finally {
|
|
1570
|
+
await client.close();
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
async function nativeSessionsOf(ctx, instanceId) {
|
|
1574
|
+
const resolve = ctx.providers.nativeSessions;
|
|
1575
|
+
if (typeof resolve !== "function") return null;
|
|
1576
|
+
try {
|
|
1577
|
+
return await resolve(instanceId);
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
ctx.log.warn("session-import could not reach the executor's own session scan", {
|
|
1580
|
+
instanceId,
|
|
1581
|
+
detail: describeError(error)
|
|
1582
|
+
});
|
|
1583
|
+
return null;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
async function listSessions(ctx, input) {
|
|
1587
|
+
const project = await ctx.projections.getProject(input.projectId);
|
|
1588
|
+
if (project === null) {
|
|
1589
|
+
throw new SessionImportFailure("notFound", "Project not found.");
|
|
1590
|
+
}
|
|
1591
|
+
const directory = input.directory ?? project.workspaceRoot;
|
|
1592
|
+
const caseInsensitive = caseInsensitivePaths();
|
|
1593
|
+
const instances = await ctx.providers.listInstances();
|
|
1594
|
+
const imported = await readImportedThreads(ctx);
|
|
1595
|
+
const sources = [];
|
|
1596
|
+
const scannedKeys = /* @__PURE__ */ new Set();
|
|
1597
|
+
for (const instance of instances) {
|
|
1598
|
+
if (!instance.enabled) continue;
|
|
1599
|
+
const provider = providerKindOfDriver(instance.driver);
|
|
1600
|
+
if (provider === void 0) continue;
|
|
1601
|
+
const hand = await nativeSessionsOf(ctx, instance.instanceId);
|
|
1602
|
+
if (hand !== null) {
|
|
1603
|
+
const scanKey2 = `hand:${instance.driver}:${instance.homePath ?? instance.instanceId}`;
|
|
1604
|
+
if (scannedKeys.has(scanKey2)) continue;
|
|
1605
|
+
scannedKeys.add(scanKey2);
|
|
1606
|
+
try {
|
|
1607
|
+
const scanned = await hand.scan({ projectId: input.projectId, directory });
|
|
1608
|
+
sources.push({
|
|
1609
|
+
instanceId: instance.instanceId,
|
|
1610
|
+
provider,
|
|
1611
|
+
candidates: scanned.candidates.map(
|
|
1612
|
+
(row) => candidateOf({
|
|
1613
|
+
provider,
|
|
1614
|
+
instanceId: instance.instanceId,
|
|
1615
|
+
nativeSessionId: row.nativeSessionId,
|
|
1616
|
+
cwd: row.cwd,
|
|
1617
|
+
title: row.title,
|
|
1618
|
+
preview: row.preview,
|
|
1619
|
+
model: row.model,
|
|
1620
|
+
turnCount: row.turnCount,
|
|
1621
|
+
updatedAt: row.updatedAt,
|
|
1622
|
+
openElsewhere: row.openElsewhere,
|
|
1623
|
+
// 「已经有 thread 绑着它」是宿主的账,手报不出来,一律盖上去。
|
|
1624
|
+
importedThreadId: imported.get(importedThreadKey(provider, row.nativeSessionId))
|
|
1625
|
+
})
|
|
1626
|
+
),
|
|
1627
|
+
unavailableReason: null
|
|
1628
|
+
});
|
|
1629
|
+
} catch (error) {
|
|
1630
|
+
ctx.log.warn("session-import could not read sessions from the executor", {
|
|
1631
|
+
instanceId: instance.instanceId,
|
|
1632
|
+
detail: describeError(error)
|
|
1633
|
+
});
|
|
1634
|
+
sources.push({
|
|
1635
|
+
instanceId: instance.instanceId,
|
|
1636
|
+
provider,
|
|
1637
|
+
candidates: [],
|
|
1638
|
+
unavailableReason: provider === "codex" ? "Codex sessions could not be read." : "Claude sessions could not be read."
|
|
1639
|
+
});
|
|
1640
|
+
}
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
if (instance.driver === CODEX_DRIVER) {
|
|
1644
|
+
const binaryPath = ctx.config.codexBinaries?.[instance.instanceId];
|
|
1645
|
+
if (binaryPath === void 0) continue;
|
|
1646
|
+
const scanKey2 = `codex:${instance.homePath ?? ""}:${binaryPath}`;
|
|
1647
|
+
if (scannedKeys.has(scanKey2)) continue;
|
|
1648
|
+
scannedKeys.add(scanKey2);
|
|
1649
|
+
try {
|
|
1650
|
+
const rows = await withCodex(
|
|
1651
|
+
ctx,
|
|
1652
|
+
instance,
|
|
1653
|
+
(client) => scanCodexSessions({ client, directory, caseInsensitive })
|
|
1654
|
+
);
|
|
1655
|
+
sources.push({
|
|
1656
|
+
instanceId: instance.instanceId,
|
|
1657
|
+
provider: "codex",
|
|
1658
|
+
candidates: rows.map(
|
|
1659
|
+
(row) => candidateOf({
|
|
1660
|
+
provider: "codex",
|
|
1661
|
+
instanceId: instance.instanceId,
|
|
1662
|
+
nativeSessionId: row.threadId,
|
|
1663
|
+
cwd: row.cwd,
|
|
1664
|
+
title: row.title,
|
|
1665
|
+
preview: row.preview,
|
|
1666
|
+
model: row.model,
|
|
1667
|
+
turnCount: row.turnCount,
|
|
1668
|
+
updatedAt: row.updatedAt,
|
|
1669
|
+
// Codex 判不了活,双开由它自己在 resume 上挡。
|
|
1670
|
+
openElsewhere: false,
|
|
1671
|
+
importedThreadId: imported.get(importedThreadKey("codex", row.threadId))
|
|
1672
|
+
})
|
|
1673
|
+
),
|
|
1674
|
+
unavailableReason: null
|
|
1675
|
+
});
|
|
1676
|
+
} catch (error) {
|
|
1677
|
+
ctx.log.warn("session-import could not read codex sessions", {
|
|
1678
|
+
instanceId: instance.instanceId,
|
|
1679
|
+
detail: describeError(error)
|
|
1680
|
+
});
|
|
1681
|
+
sources.push({
|
|
1682
|
+
instanceId: instance.instanceId,
|
|
1683
|
+
provider: "codex",
|
|
1684
|
+
candidates: [],
|
|
1685
|
+
unavailableReason: "Codex sessions could not be read."
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
continue;
|
|
1689
|
+
}
|
|
1690
|
+
if (instance.driver !== CLAUDE_DRIVER) continue;
|
|
1691
|
+
if (instance.transcriptDir === null || instance.configDirPath === null) continue;
|
|
1692
|
+
const scanKey = `claude:${instance.transcriptDir}`;
|
|
1693
|
+
if (scannedKeys.has(scanKey)) continue;
|
|
1694
|
+
scannedKeys.add(scanKey);
|
|
1695
|
+
try {
|
|
1696
|
+
const rows = await scanClaudeSessions({
|
|
1697
|
+
fs: ctx.fs,
|
|
1698
|
+
cache: ctx.cache,
|
|
1699
|
+
transcriptDir: instance.transcriptDir,
|
|
1700
|
+
configDir: instance.configDirPath,
|
|
1701
|
+
directory,
|
|
1702
|
+
caseInsensitive
|
|
1703
|
+
});
|
|
1704
|
+
sources.push({
|
|
1705
|
+
instanceId: instance.instanceId,
|
|
1706
|
+
provider: "claude",
|
|
1707
|
+
candidates: rows.map(
|
|
1708
|
+
(row) => candidateOf({
|
|
1709
|
+
provider: "claude",
|
|
1710
|
+
instanceId: instance.instanceId,
|
|
1711
|
+
nativeSessionId: row.sessionId,
|
|
1712
|
+
cwd: row.cwd,
|
|
1713
|
+
title: row.title,
|
|
1714
|
+
preview: row.preview,
|
|
1715
|
+
model: row.model,
|
|
1716
|
+
turnCount: row.turnCount,
|
|
1717
|
+
updatedAt: row.updatedAt,
|
|
1718
|
+
openElsewhere: row.openElsewhere,
|
|
1719
|
+
importedThreadId: imported.get(importedThreadKey("claude", row.sessionId))
|
|
1720
|
+
})
|
|
1721
|
+
),
|
|
1722
|
+
unavailableReason: null
|
|
1723
|
+
});
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
ctx.log.warn("session-import could not read claude sessions", {
|
|
1726
|
+
instanceId: instance.instanceId,
|
|
1727
|
+
detail: describeError(error)
|
|
1728
|
+
});
|
|
1729
|
+
sources.push({
|
|
1730
|
+
instanceId: instance.instanceId,
|
|
1731
|
+
provider: "claude",
|
|
1732
|
+
candidates: [],
|
|
1733
|
+
unavailableReason: "Claude sessions could not be read."
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
return { sources, directory };
|
|
1738
|
+
}
|
|
1739
|
+
var describeError = (error) => error instanceof Error ? error.message : String(error);
|
|
1740
|
+
|
|
1741
|
+
// src/sessionTranscript.ts
|
|
1742
|
+
import * as NodeCrypto2 from "node:crypto";
|
|
1743
|
+
var SESSION_IMPORT_ACTIVITY_LIMIT = 1e3;
|
|
1744
|
+
var SESSION_IMPORT_ACTIVITY_TEXT_LIMIT = 32e3;
|
|
1745
|
+
var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1746
|
+
var nonempty = (value) => typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
1747
|
+
function nativeModel2(value) {
|
|
1748
|
+
const model = nonempty(value);
|
|
1749
|
+
return model?.trim() === "<synthetic>" ? void 0 : model;
|
|
1750
|
+
}
|
|
1751
|
+
function fallbackId(value) {
|
|
1752
|
+
let text;
|
|
1753
|
+
try {
|
|
1754
|
+
text = JSON.stringify(value) ?? "";
|
|
1755
|
+
} catch {
|
|
1756
|
+
text = "";
|
|
1757
|
+
}
|
|
1758
|
+
return `record:${NodeCrypto2.createHash("sha256").update(text).digest("hex").slice(0, 20)}`;
|
|
1759
|
+
}
|
|
1760
|
+
function readableContent(value) {
|
|
1761
|
+
if (typeof value === "string") return value;
|
|
1762
|
+
if (!Array.isArray(value)) return "";
|
|
1763
|
+
return value.flatMap((entry) => {
|
|
1764
|
+
if (typeof entry === "string") return [entry];
|
|
1765
|
+
const block = record2(entry);
|
|
1766
|
+
return block && ["text", "input_text", "output_text", "summary_text", "reasoning_text"].includes(
|
|
1767
|
+
String(block.type)
|
|
1768
|
+
) && typeof block.text === "string" ? [block.text] : [];
|
|
1769
|
+
}).join("\n\n");
|
|
1770
|
+
}
|
|
1771
|
+
function toolInput(value) {
|
|
1772
|
+
if (typeof value === "string") return value;
|
|
1773
|
+
const input = record2(value);
|
|
1774
|
+
if (input && Object.keys(input).length === 1 && typeof input.command === "string")
|
|
1775
|
+
return input.command;
|
|
1776
|
+
if (input && Object.keys(input).length === 1 && typeof input.cmd === "string") return input.cmd;
|
|
1777
|
+
if (value === void 0 || value === null) return void 0;
|
|
1778
|
+
try {
|
|
1779
|
+
return JSON.stringify(value, null, 2);
|
|
1780
|
+
} catch {
|
|
1781
|
+
return void 0;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
function toolType(name) {
|
|
1785
|
+
const normalized = name.toLowerCase();
|
|
1786
|
+
if (/^(bash|shell|command|terminal|exec_command|write_stdin)$/u.test(normalized))
|
|
1787
|
+
return "command_execution";
|
|
1788
|
+
if (/^(edit|write|multiedit|apply_patch|applypatch|patch)$/u.test(normalized))
|
|
1789
|
+
return "file_change";
|
|
1790
|
+
if (/^(agent|task|workflow)$/u.test(normalized)) return "dynamic_tool_call";
|
|
1791
|
+
if (normalized.startsWith("mcp")) return "mcp_tool_call";
|
|
1792
|
+
if (/^(websearch|webfetch|web_search)$/u.test(normalized)) return "web_search";
|
|
1793
|
+
return "dynamic_tool_call";
|
|
1794
|
+
}
|
|
1795
|
+
function limit(messages, activities) {
|
|
1796
|
+
const availableMessages = messages.length;
|
|
1797
|
+
const retainedMessages = messages.slice(-SESSION_IMPORT_MESSAGE_LIMIT);
|
|
1798
|
+
const floor = availableMessages > SESSION_IMPORT_MESSAGE_LIMIT ? retainedMessages[0]?.sourceOrder ?? 0 : 0;
|
|
1799
|
+
const visibleActivities = activities.filter((activity) => activity.sourceOrder >= floor);
|
|
1800
|
+
const retainedActivities = visibleActivities.slice(-SESSION_IMPORT_ACTIVITY_LIMIT);
|
|
1801
|
+
return {
|
|
1802
|
+
availableMessages,
|
|
1803
|
+
availableActivities: activities.length,
|
|
1804
|
+
...retainedActivities.length < activities.length ? { activityTruncated: true } : {},
|
|
1805
|
+
messages: retainedMessages,
|
|
1806
|
+
activities: retainedActivities
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function bounded(value, tail = false) {
|
|
1810
|
+
if (value.length <= SESSION_IMPORT_ACTIVITY_TEXT_LIMIT) return { text: value, truncated: false };
|
|
1811
|
+
const offset = tail ? value.length - SESSION_IMPORT_ACTIVITY_TEXT_LIMIT : SESSION_IMPORT_ACTIVITY_TEXT_LIMIT;
|
|
1812
|
+
const character = value.charCodeAt(offset);
|
|
1813
|
+
const previous = value.charCodeAt(offset - 1);
|
|
1814
|
+
const splitPair = character >= 56320 && character <= 57343 && previous >= 55296 && previous <= 56319;
|
|
1815
|
+
return {
|
|
1816
|
+
text: tail ? value.slice(offset + (splitPair ? 1 : 0)) : value.slice(0, offset - (splitPair ? 1 : 0)),
|
|
1817
|
+
truncated: true
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
function inputPayload(input) {
|
|
1821
|
+
if (input === void 0) return {};
|
|
1822
|
+
const result = bounded(input);
|
|
1823
|
+
return { input: result.text, ...result.truncated ? { inputTruncated: true } : {} };
|
|
1567
1824
|
}
|
|
1568
1825
|
function outputPayload(output) {
|
|
1569
1826
|
if (output === void 0) return {};
|
|
@@ -1891,261 +2148,6 @@ var SKIPPED_ACTIVE = {
|
|
|
1891
2148
|
appended: 0
|
|
1892
2149
|
};
|
|
1893
2150
|
var IMPORTED_TITLE_MAX_LENGTH = 80;
|
|
1894
|
-
var CLAUDE_DRIVER = "claudeAgent";
|
|
1895
|
-
var CODEX_DRIVER = "codex";
|
|
1896
|
-
var providerKindOfDriver = (driverKind) => driverKind === CLAUDE_DRIVER ? "claude" : driverKind === CODEX_DRIVER ? "codex" : void 0;
|
|
1897
|
-
var importedThreadKey = (provider, nativeSessionId) => `${provider}:${nativeSessionId}`;
|
|
1898
|
-
var caseInsensitivePaths = () => NodeProcess2.platform === "win32";
|
|
1899
|
-
function tokenizeLaunchArgs(value) {
|
|
1900
|
-
const input = value.trim();
|
|
1901
|
-
if (input.length === 0) return [];
|
|
1902
|
-
const tokens = [];
|
|
1903
|
-
let current = "";
|
|
1904
|
-
let quote = null;
|
|
1905
|
-
let quoted = false;
|
|
1906
|
-
for (const char of input) {
|
|
1907
|
-
if (quote !== null) {
|
|
1908
|
-
if (char === quote) quote = null;
|
|
1909
|
-
else current += char;
|
|
1910
|
-
continue;
|
|
1911
|
-
}
|
|
1912
|
-
if (char === "'" || char === '"') {
|
|
1913
|
-
quote = char;
|
|
1914
|
-
quoted = true;
|
|
1915
|
-
continue;
|
|
1916
|
-
}
|
|
1917
|
-
if (/\s/u.test(char)) {
|
|
1918
|
-
if (current.length > 0 || quoted) tokens.push(current);
|
|
1919
|
-
current = "";
|
|
1920
|
-
quoted = false;
|
|
1921
|
-
continue;
|
|
1922
|
-
}
|
|
1923
|
-
current += char;
|
|
1924
|
-
}
|
|
1925
|
-
if (current.length > 0 || quoted) tokens.push(current);
|
|
1926
|
-
return tokens;
|
|
1927
|
-
}
|
|
1928
|
-
var nativeSessionIdOf = (provider, cursor) => {
|
|
1929
|
-
if (cursor === null || typeof cursor !== "object") return null;
|
|
1930
|
-
const record3 = cursor;
|
|
1931
|
-
const value = provider === "claude" ? record3.resume ?? record3.sessionId : record3.threadId;
|
|
1932
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1933
|
-
};
|
|
1934
|
-
async function readImportedThreads(ctx) {
|
|
1935
|
-
const imported = /* @__PURE__ */ new Map();
|
|
1936
|
-
const bindings = await ctx.providers.sessions.list().catch(() => []);
|
|
1937
|
-
for (const binding of bindings) {
|
|
1938
|
-
const provider = providerKindOfDriver(binding.driver);
|
|
1939
|
-
if (provider === void 0) continue;
|
|
1940
|
-
const nativeId = nativeSessionIdOf(provider, binding.resumeCursor);
|
|
1941
|
-
if (nativeId === null) continue;
|
|
1942
|
-
imported.set(importedThreadKey(provider, nativeId), binding.threadId);
|
|
1943
|
-
}
|
|
1944
|
-
return imported;
|
|
1945
|
-
}
|
|
1946
|
-
var candidateOf = (input) => ({
|
|
1947
|
-
provider: input.provider,
|
|
1948
|
-
instanceId: input.instanceId,
|
|
1949
|
-
nativeSessionId: input.nativeSessionId,
|
|
1950
|
-
cwd: input.cwd,
|
|
1951
|
-
title: input.title,
|
|
1952
|
-
preview: input.preview,
|
|
1953
|
-
model: input.model,
|
|
1954
|
-
turnCount: input.turnCount,
|
|
1955
|
-
updatedAt: input.updatedAt,
|
|
1956
|
-
availability: input.importedThreadId !== void 0 ? "alreadyImported" : "importable",
|
|
1957
|
-
openElsewhere: input.openElsewhere,
|
|
1958
|
-
...input.importedThreadId !== void 0 ? { importedThreadId: input.importedThreadId } : {}
|
|
1959
|
-
});
|
|
1960
|
-
async function openCodexFor(ctx, instance) {
|
|
1961
|
-
const binaryPath = ctx.config.codexBinaries?.[instance.instanceId];
|
|
1962
|
-
if (binaryPath === void 0 || binaryPath.length === 0) {
|
|
1963
|
-
throw new Error(`No codex binary is configured for provider instance ${instance.instanceId}.`);
|
|
1964
|
-
}
|
|
1965
|
-
return openCodexAppServer({
|
|
1966
|
-
process: ctx.process,
|
|
1967
|
-
// 白名单条目的键就是 instanceId:路径由宿主查,插件手上不留第二份。
|
|
1968
|
-
program: { key: instance.instanceId },
|
|
1969
|
-
args: [...tokenizeLaunchArgs(instance.launchArgs.trim())],
|
|
1970
|
-
// 转录始终在 CODEX_HOME 下;实例的 home 是宿主解析过的那个(含 auth overlay)。
|
|
1971
|
-
...instance.homePath === null ? {} : { env: { CODEX_HOME: instance.homePath } }
|
|
1972
|
-
});
|
|
1973
|
-
}
|
|
1974
|
-
async function withCodex(ctx, instance, use) {
|
|
1975
|
-
const client = await openCodexFor(ctx, instance);
|
|
1976
|
-
try {
|
|
1977
|
-
return await use(client);
|
|
1978
|
-
} finally {
|
|
1979
|
-
await client.close();
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
async function nativeSessionsOf(ctx, instanceId) {
|
|
1983
|
-
const resolve = ctx.providers.nativeSessions;
|
|
1984
|
-
if (typeof resolve !== "function") return null;
|
|
1985
|
-
try {
|
|
1986
|
-
return await resolve(instanceId);
|
|
1987
|
-
} catch (error) {
|
|
1988
|
-
ctx.log.warn("session-import could not reach the executor's own session scan", {
|
|
1989
|
-
instanceId,
|
|
1990
|
-
detail: describeError(error)
|
|
1991
|
-
});
|
|
1992
|
-
return null;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
async function listSessions(ctx, input) {
|
|
1996
|
-
const project = await ctx.projections.getProject(input.projectId);
|
|
1997
|
-
if (project === null) {
|
|
1998
|
-
throw new SessionImportFailure("notFound", "Project not found.");
|
|
1999
|
-
}
|
|
2000
|
-
const directory = input.directory ?? project.workspaceRoot;
|
|
2001
|
-
const caseInsensitive = caseInsensitivePaths();
|
|
2002
|
-
const instances = await ctx.providers.listInstances();
|
|
2003
|
-
const imported = await readImportedThreads(ctx);
|
|
2004
|
-
const sources = [];
|
|
2005
|
-
const scannedKeys = /* @__PURE__ */ new Set();
|
|
2006
|
-
for (const instance of instances) {
|
|
2007
|
-
if (!instance.enabled) continue;
|
|
2008
|
-
const provider = providerKindOfDriver(instance.driver);
|
|
2009
|
-
if (provider === void 0) continue;
|
|
2010
|
-
const hand = await nativeSessionsOf(ctx, instance.instanceId);
|
|
2011
|
-
if (hand !== null) {
|
|
2012
|
-
const scanKey2 = `hand:${instance.driver}:${instance.homePath ?? instance.instanceId}`;
|
|
2013
|
-
if (scannedKeys.has(scanKey2)) continue;
|
|
2014
|
-
scannedKeys.add(scanKey2);
|
|
2015
|
-
try {
|
|
2016
|
-
const scanned = await hand.scan({ projectId: input.projectId, directory });
|
|
2017
|
-
sources.push({
|
|
2018
|
-
instanceId: instance.instanceId,
|
|
2019
|
-
provider,
|
|
2020
|
-
candidates: scanned.candidates.map(
|
|
2021
|
-
(row) => candidateOf({
|
|
2022
|
-
provider,
|
|
2023
|
-
instanceId: instance.instanceId,
|
|
2024
|
-
nativeSessionId: row.nativeSessionId,
|
|
2025
|
-
cwd: row.cwd,
|
|
2026
|
-
title: row.title,
|
|
2027
|
-
preview: row.preview,
|
|
2028
|
-
model: row.model,
|
|
2029
|
-
turnCount: row.turnCount,
|
|
2030
|
-
updatedAt: row.updatedAt,
|
|
2031
|
-
openElsewhere: row.openElsewhere,
|
|
2032
|
-
// 「已经有 thread 绑着它」是宿主的账,手报不出来,一律盖上去。
|
|
2033
|
-
importedThreadId: imported.get(importedThreadKey(provider, row.nativeSessionId))
|
|
2034
|
-
})
|
|
2035
|
-
),
|
|
2036
|
-
unavailableReason: null
|
|
2037
|
-
});
|
|
2038
|
-
} catch (error) {
|
|
2039
|
-
ctx.log.warn("session-import could not read sessions from the executor", {
|
|
2040
|
-
instanceId: instance.instanceId,
|
|
2041
|
-
detail: describeError(error)
|
|
2042
|
-
});
|
|
2043
|
-
sources.push({
|
|
2044
|
-
instanceId: instance.instanceId,
|
|
2045
|
-
provider,
|
|
2046
|
-
candidates: [],
|
|
2047
|
-
unavailableReason: provider === "codex" ? "Codex sessions could not be read." : "Claude sessions could not be read."
|
|
2048
|
-
});
|
|
2049
|
-
}
|
|
2050
|
-
continue;
|
|
2051
|
-
}
|
|
2052
|
-
if (instance.driver === CODEX_DRIVER) {
|
|
2053
|
-
const binaryPath = ctx.config.codexBinaries?.[instance.instanceId];
|
|
2054
|
-
if (binaryPath === void 0) continue;
|
|
2055
|
-
const scanKey2 = `codex:${instance.homePath ?? ""}:${binaryPath}`;
|
|
2056
|
-
if (scannedKeys.has(scanKey2)) continue;
|
|
2057
|
-
scannedKeys.add(scanKey2);
|
|
2058
|
-
try {
|
|
2059
|
-
const rows = await withCodex(
|
|
2060
|
-
ctx,
|
|
2061
|
-
instance,
|
|
2062
|
-
(client) => scanCodexSessions({ client, directory, caseInsensitive })
|
|
2063
|
-
);
|
|
2064
|
-
sources.push({
|
|
2065
|
-
instanceId: instance.instanceId,
|
|
2066
|
-
provider: "codex",
|
|
2067
|
-
candidates: rows.map(
|
|
2068
|
-
(row) => candidateOf({
|
|
2069
|
-
provider: "codex",
|
|
2070
|
-
instanceId: instance.instanceId,
|
|
2071
|
-
nativeSessionId: row.threadId,
|
|
2072
|
-
cwd: row.cwd,
|
|
2073
|
-
title: row.title,
|
|
2074
|
-
preview: row.preview,
|
|
2075
|
-
model: row.model,
|
|
2076
|
-
turnCount: row.turnCount,
|
|
2077
|
-
updatedAt: row.updatedAt,
|
|
2078
|
-
// Codex 判不了活,双开由它自己在 resume 上挡。
|
|
2079
|
-
openElsewhere: false,
|
|
2080
|
-
importedThreadId: imported.get(importedThreadKey("codex", row.threadId))
|
|
2081
|
-
})
|
|
2082
|
-
),
|
|
2083
|
-
unavailableReason: null
|
|
2084
|
-
});
|
|
2085
|
-
} catch (error) {
|
|
2086
|
-
ctx.log.warn("session-import could not read codex sessions", {
|
|
2087
|
-
instanceId: instance.instanceId,
|
|
2088
|
-
detail: describeError(error)
|
|
2089
|
-
});
|
|
2090
|
-
sources.push({
|
|
2091
|
-
instanceId: instance.instanceId,
|
|
2092
|
-
provider: "codex",
|
|
2093
|
-
candidates: [],
|
|
2094
|
-
unavailableReason: "Codex sessions could not be read."
|
|
2095
|
-
});
|
|
2096
|
-
}
|
|
2097
|
-
continue;
|
|
2098
|
-
}
|
|
2099
|
-
if (instance.driver !== CLAUDE_DRIVER) continue;
|
|
2100
|
-
if (instance.transcriptDir === null || instance.configDirPath === null) continue;
|
|
2101
|
-
const scanKey = `claude:${instance.transcriptDir}`;
|
|
2102
|
-
if (scannedKeys.has(scanKey)) continue;
|
|
2103
|
-
scannedKeys.add(scanKey);
|
|
2104
|
-
try {
|
|
2105
|
-
const rows = await scanClaudeSessions({
|
|
2106
|
-
fs: ctx.fs,
|
|
2107
|
-
cache: ctx.cache,
|
|
2108
|
-
transcriptDir: instance.transcriptDir,
|
|
2109
|
-
configDir: instance.configDirPath,
|
|
2110
|
-
directory,
|
|
2111
|
-
caseInsensitive
|
|
2112
|
-
});
|
|
2113
|
-
sources.push({
|
|
2114
|
-
instanceId: instance.instanceId,
|
|
2115
|
-
provider: "claude",
|
|
2116
|
-
candidates: rows.map(
|
|
2117
|
-
(row) => candidateOf({
|
|
2118
|
-
provider: "claude",
|
|
2119
|
-
instanceId: instance.instanceId,
|
|
2120
|
-
nativeSessionId: row.sessionId,
|
|
2121
|
-
cwd: row.cwd,
|
|
2122
|
-
title: row.title,
|
|
2123
|
-
preview: row.preview,
|
|
2124
|
-
model: row.model,
|
|
2125
|
-
turnCount: row.turnCount,
|
|
2126
|
-
updatedAt: row.updatedAt,
|
|
2127
|
-
openElsewhere: row.openElsewhere,
|
|
2128
|
-
importedThreadId: imported.get(importedThreadKey("claude", row.sessionId))
|
|
2129
|
-
})
|
|
2130
|
-
),
|
|
2131
|
-
unavailableReason: null
|
|
2132
|
-
});
|
|
2133
|
-
} catch (error) {
|
|
2134
|
-
ctx.log.warn("session-import could not read claude sessions", {
|
|
2135
|
-
instanceId: instance.instanceId,
|
|
2136
|
-
detail: describeError(error)
|
|
2137
|
-
});
|
|
2138
|
-
sources.push({
|
|
2139
|
-
instanceId: instance.instanceId,
|
|
2140
|
-
provider: "claude",
|
|
2141
|
-
candidates: [],
|
|
2142
|
-
unavailableReason: "Claude sessions could not be read."
|
|
2143
|
-
});
|
|
2144
|
-
}
|
|
2145
|
-
}
|
|
2146
|
-
return { sources, directory };
|
|
2147
|
-
}
|
|
2148
|
-
var describeError = (error) => error instanceof Error ? error.message : String(error);
|
|
2149
2151
|
function resolveImportedTitle(transcript) {
|
|
2150
2152
|
const first = transcript.messages.find((message) => message.role === "user");
|
|
2151
2153
|
const text = (first?.text ?? "").replace(/\s+/gu, " ").trim();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/plugin-session-import",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2992",
|
|
4
4
|
"description": "Brings a session you started in a terminal into Ycodium.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"session-import",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"effect": "4.0.0-beta.103",
|
|
21
21
|
"esbuild": "0.28.1",
|
|
22
22
|
"vite-plus": "0.2.2",
|
|
23
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
23
|
+
"@ycodium-ai/plugin-api": "0.2.2992",
|
|
24
24
|
"ycodium-jsonl-stream": "0.0.0"
|
|
25
25
|
},
|
|
26
26
|
"ycodium": {
|