@ycodium-ai/plugin-session-import 0.2.3018 → 0.2.3024
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 +148 -19
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -164,9 +164,10 @@ var sessionImportSyncResultSchema = {
|
|
|
164
164
|
};
|
|
165
165
|
|
|
166
166
|
// src/sessionImportRuntime.ts
|
|
167
|
-
import * as
|
|
167
|
+
import * as NodeCrypto4 from "node:crypto";
|
|
168
168
|
import {
|
|
169
|
-
projectOrNull as projectOrNull2
|
|
169
|
+
projectOrNull as projectOrNull2,
|
|
170
|
+
threadOrNull as threadOrNull2
|
|
170
171
|
} from "@ycodium-ai/plugin-api";
|
|
171
172
|
|
|
172
173
|
// src/claudeSessionScan.ts
|
|
@@ -1130,8 +1131,126 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
|
|
|
1130
1131
|
return model;
|
|
1131
1132
|
}
|
|
1132
1133
|
|
|
1133
|
-
// src/
|
|
1134
|
+
// src/existingTimeline.ts
|
|
1134
1135
|
import * as NodeCrypto from "node:crypto";
|
|
1136
|
+
import {
|
|
1137
|
+
isThreadNotFound,
|
|
1138
|
+
threadOrNull,
|
|
1139
|
+
THREAD_ACTIVITIES_LIST_LIMIT,
|
|
1140
|
+
THREAD_MESSAGES_LIST_LIMIT,
|
|
1141
|
+
TOOL_LIFECYCLE_ACTIVITY_KINDS
|
|
1142
|
+
} from "@ycodium-ai/plugin-api";
|
|
1143
|
+
var MESSAGES_PAGE = THREAD_MESSAGES_LIST_LIMIT;
|
|
1144
|
+
var ACTIVITIES_PAGE = THREAD_ACTIVITIES_LIST_LIMIT;
|
|
1145
|
+
var TOOL_KINDS = [...TOOL_LIFECYCLE_ACTIVITY_KINDS];
|
|
1146
|
+
var IMPORTED_KINDS = [...TOOL_KINDS, "reasoning"];
|
|
1147
|
+
var MAX_FULL_READS = 4;
|
|
1148
|
+
var TimelineKeptChangingError = class extends Error {
|
|
1149
|
+
constructor() {
|
|
1150
|
+
super("The thread's timeline kept changing while it was being read.");
|
|
1151
|
+
this.name = "TimelineKeptChangingError";
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
async function readEveryPage(listPage) {
|
|
1155
|
+
const rows = [];
|
|
1156
|
+
let cursor = null;
|
|
1157
|
+
let sequence;
|
|
1158
|
+
let steady = true;
|
|
1159
|
+
do {
|
|
1160
|
+
const page = await listPage(cursor);
|
|
1161
|
+
if (sequence !== void 0 && page.snapshotSequence !== sequence) steady = false;
|
|
1162
|
+
sequence = page.snapshotSequence;
|
|
1163
|
+
rows.push(...page.items);
|
|
1164
|
+
cursor = page.nextCursor;
|
|
1165
|
+
} while (cursor !== null);
|
|
1166
|
+
return { rows, steady };
|
|
1167
|
+
}
|
|
1168
|
+
function digestOfRows(rows) {
|
|
1169
|
+
const hash = NodeCrypto.createHash("sha256");
|
|
1170
|
+
for (const row of rows) hash.update(JSON.stringify(row)).update("\n");
|
|
1171
|
+
return hash.digest("hex");
|
|
1172
|
+
}
|
|
1173
|
+
async function readStable(listPage) {
|
|
1174
|
+
let previousDigest;
|
|
1175
|
+
for (let attempt = 0; attempt < MAX_FULL_READS; attempt++) {
|
|
1176
|
+
const { rows, steady } = await readEveryPage(listPage);
|
|
1177
|
+
if (new Set(rows.map((row) => row.id)).size !== rows.length) {
|
|
1178
|
+
previousDigest = void 0;
|
|
1179
|
+
continue;
|
|
1180
|
+
}
|
|
1181
|
+
if (steady) return rows;
|
|
1182
|
+
const digest = digestOfRows(rows);
|
|
1183
|
+
if (digest === previousDigest) return rows;
|
|
1184
|
+
previousDigest = digest;
|
|
1185
|
+
}
|
|
1186
|
+
throw new TimelineKeptChangingError();
|
|
1187
|
+
}
|
|
1188
|
+
async function listAllMessages(threads, threadId) {
|
|
1189
|
+
const newestFirst = await readStable(
|
|
1190
|
+
(cursor) => threads.messages.list({ threadId, limit: MESSAGES_PAGE, cursor })
|
|
1191
|
+
);
|
|
1192
|
+
return newestFirst.toReversed().map((item) => ({
|
|
1193
|
+
messageId: item.id,
|
|
1194
|
+
role: item.role,
|
|
1195
|
+
text: item.text,
|
|
1196
|
+
createdAt: item.createdAt,
|
|
1197
|
+
...item.source === void 0 ? {} : { source: item.source },
|
|
1198
|
+
...item.modelSelection === void 0 ? {} : { modelSelection: item.modelSelection }
|
|
1199
|
+
}));
|
|
1200
|
+
}
|
|
1201
|
+
async function listAllActivities(threads, threadId, query) {
|
|
1202
|
+
const newestFirst = await readStable(
|
|
1203
|
+
(cursor) => threads.activities.list({ threadId, ...query, limit: ACTIVITIES_PAGE, cursor })
|
|
1204
|
+
);
|
|
1205
|
+
return newestFirst.toReversed();
|
|
1206
|
+
}
|
|
1207
|
+
var activityOf = (item, payload) => ({
|
|
1208
|
+
id: item.id,
|
|
1209
|
+
kind: item.kind,
|
|
1210
|
+
tone: item.tone,
|
|
1211
|
+
summary: item.summary,
|
|
1212
|
+
payload,
|
|
1213
|
+
createdAt: item.createdAt,
|
|
1214
|
+
turnId: item.turnId
|
|
1215
|
+
});
|
|
1216
|
+
async function readExistingThread(threads, threadId) {
|
|
1217
|
+
const shell = await threadOrNull(threads, threadId);
|
|
1218
|
+
if (shell === null) return null;
|
|
1219
|
+
const timeline = await readExistingTimeline(threads, threadId);
|
|
1220
|
+
return timeline === null ? null : { ...shell, ...timeline };
|
|
1221
|
+
}
|
|
1222
|
+
async function readExistingTimeline(threads, threadId) {
|
|
1223
|
+
try {
|
|
1224
|
+
const messages = await listAllMessages(threads, threadId);
|
|
1225
|
+
const unturned = await listAllActivities(threads, threadId, {
|
|
1226
|
+
kinds: IMPORTED_KINDS,
|
|
1227
|
+
inTurn: "none"
|
|
1228
|
+
});
|
|
1229
|
+
const turned = await listAllActivities(threads, threadId, {
|
|
1230
|
+
kinds: TOOL_KINDS,
|
|
1231
|
+
inTurn: "any",
|
|
1232
|
+
maxPayloadBytes: 0
|
|
1233
|
+
});
|
|
1234
|
+
const runtimeCalls = turned.filter((item) => item.turnId !== null).map((item) => {
|
|
1235
|
+
const toolCallId2 = "toolCallId" in item ? item.toolCallId : void 0;
|
|
1236
|
+
return activityOf(item, toolCallId2 === void 0 ? void 0 : { toolCallId: toolCallId2 });
|
|
1237
|
+
});
|
|
1238
|
+
return {
|
|
1239
|
+
messages,
|
|
1240
|
+
activities: [
|
|
1241
|
+
// 这一趟没限载荷大小,行都带载荷;只有被 `maxPayloadBytes` 省掉的行才没有。
|
|
1242
|
+
...unturned.map((item) => activityOf(item, "payload" in item ? item.payload : void 0)),
|
|
1243
|
+
...runtimeCalls
|
|
1244
|
+
]
|
|
1245
|
+
};
|
|
1246
|
+
} catch (error) {
|
|
1247
|
+
if (isThreadNotFound(error)) return null;
|
|
1248
|
+
throw error;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// src/reconcileImportedTimeline.ts
|
|
1253
|
+
import * as NodeCrypto2 from "node:crypto";
|
|
1135
1254
|
function record(value) {
|
|
1136
1255
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
1137
1256
|
}
|
|
@@ -1140,7 +1259,7 @@ function sourceId(value, input) {
|
|
|
1140
1259
|
return source2?.kind === "native-session" && source2.provider === input.provider && source2.sessionId === input.nativeSessionId && typeof source2.messageId === "string" ? source2.messageId : void 0;
|
|
1141
1260
|
}
|
|
1142
1261
|
function stableId(input, kind, nativeId) {
|
|
1143
|
-
const digest =
|
|
1262
|
+
const digest = NodeCrypto2.createHash("sha256").update(JSON.stringify([input.threadId, input.provider, input.nativeSessionId, kind, nativeId])).digest("hex");
|
|
1144
1263
|
return `native-${kind}:${digest}`;
|
|
1145
1264
|
}
|
|
1146
1265
|
function sourceFor(input, item) {
|
|
@@ -1745,7 +1864,7 @@ async function listSessions(ctx, input) {
|
|
|
1745
1864
|
var describeError = (error) => error instanceof Error ? error.message : String(error);
|
|
1746
1865
|
|
|
1747
1866
|
// src/sessionTranscript.ts
|
|
1748
|
-
import * as
|
|
1867
|
+
import * as NodeCrypto3 from "node:crypto";
|
|
1749
1868
|
var SESSION_IMPORT_ACTIVITY_LIMIT = 1e3;
|
|
1750
1869
|
var SESSION_IMPORT_ACTIVITY_TEXT_LIMIT = 32e3;
|
|
1751
1870
|
var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -1761,7 +1880,7 @@ function fallbackId(value) {
|
|
|
1761
1880
|
} catch {
|
|
1762
1881
|
text = "";
|
|
1763
1882
|
}
|
|
1764
|
-
return `record:${
|
|
1883
|
+
return `record:${NodeCrypto3.createHash("sha256").update(text).digest("hex").slice(0, 20)}`;
|
|
1765
1884
|
}
|
|
1766
1885
|
function readableContent(value) {
|
|
1767
1886
|
if (typeof value === "string") return value;
|
|
@@ -2153,6 +2272,7 @@ var SKIPPED_ACTIVE = {
|
|
|
2153
2272
|
status: "skipped-active",
|
|
2154
2273
|
appended: 0
|
|
2155
2274
|
};
|
|
2275
|
+
var EXISTING_THREAD_NOT_IDLE = "The existing thread must be idle and unarchived before its history can be refreshed.";
|
|
2156
2276
|
var IMPORTED_TITLE_MAX_LENGTH = 80;
|
|
2157
2277
|
function resolveImportedTitle(transcript) {
|
|
2158
2278
|
const first = transcript.messages.find((message) => message.role === "user");
|
|
@@ -2215,7 +2335,7 @@ function importedHistoryBatches(history) {
|
|
|
2215
2335
|
if (count > 0) batches.push(batch);
|
|
2216
2336
|
return batches;
|
|
2217
2337
|
}
|
|
2218
|
-
var historyCommandId = (threadId, batch) => `native-history:${
|
|
2338
|
+
var historyCommandId = (threadId, batch) => `native-history:${NodeCrypto4.createHash("sha256").update(JSON.stringify([threadId, batch])).digest("hex")}`;
|
|
2219
2339
|
var importsInFlight = /* @__PURE__ */ new WeakMap();
|
|
2220
2340
|
async function runImport(ctx, input, emit) {
|
|
2221
2341
|
let pending = importsInFlight.get(ctx);
|
|
@@ -2260,12 +2380,15 @@ async function runImportOnce(ctx, input, emit) {
|
|
|
2260
2380
|
return fail("The selected provider does not own this native session.");
|
|
2261
2381
|
}
|
|
2262
2382
|
const bindings = await ctx.providers.sessions.list();
|
|
2263
|
-
const existing = await ctx.
|
|
2383
|
+
const existing = await readExistingThread(ctx.threads, input.threadId).catch((error) => {
|
|
2384
|
+
if (error instanceof TimelineKeptChangingError) return fail(EXISTING_THREAD_NOT_IDLE);
|
|
2385
|
+
throw error;
|
|
2386
|
+
});
|
|
2264
2387
|
const binding = bindings.find((candidate2) => candidate2.threadId === input.threadId);
|
|
2265
2388
|
if (existing !== null) {
|
|
2266
2389
|
const savedSources = [
|
|
2267
2390
|
...existing.messages.map((message) => message.source),
|
|
2268
|
-
...
|
|
2391
|
+
...existing.activities.map(
|
|
2269
2392
|
(activity) => activity.payload !== null && typeof activity.payload === "object" ? activity.payload.source : void 0
|
|
2270
2393
|
)
|
|
2271
2394
|
].filter((source2) => source2 !== void 0);
|
|
@@ -2275,15 +2398,13 @@ async function runImportOnce(ctx, input, emit) {
|
|
|
2275
2398
|
if (existing.projectId !== input.projectId || !pendingLink && (binding?.instanceId !== input.instanceId || providerKindOfDriver(binding.driver) !== input.provider || nativeSessionIdOf(input.provider, binding.resumeCursor) !== input.nativeSessionId))
|
|
2276
2399
|
return fail("This thread is not linked to the selected native session.");
|
|
2277
2400
|
if (existing.archivedAt !== null || existing.session?.activeTurnId != null || existing.session?.status === "running" || existing.session?.status === "starting")
|
|
2278
|
-
return fail(
|
|
2279
|
-
"The existing thread must be idle and unarchived before its history can be refreshed."
|
|
2280
|
-
);
|
|
2401
|
+
return fail(EXISTING_THREAD_NOT_IDLE);
|
|
2281
2402
|
threadCreated = true;
|
|
2282
2403
|
} else {
|
|
2283
2404
|
for (const candidate2 of bindings) {
|
|
2284
2405
|
if (providerKindOfDriver(candidate2.driver) !== input.provider || nativeSessionIdOf(input.provider, candidate2.resumeCursor) !== input.nativeSessionId)
|
|
2285
2406
|
continue;
|
|
2286
|
-
const imported = await ctx.
|
|
2407
|
+
const imported = await threadOrNull2(ctx.threads, candidate2.threadId);
|
|
2287
2408
|
if (imported?.projectId === input.projectId)
|
|
2288
2409
|
return fail(
|
|
2289
2410
|
"This native session is already imported. Refresh its existing thread instead.",
|
|
@@ -2472,11 +2593,19 @@ var NativeSessionTail = class {
|
|
|
2472
2593
|
if (modifiedAt === null) return NOT_IMPORTED;
|
|
2473
2594
|
const seen = await this.readBaseline(threadId);
|
|
2474
2595
|
if (seen !== void 0 && modifiedAt <= seen) return UNCHANGED;
|
|
2475
|
-
const thread = await this.ctx.
|
|
2476
|
-
if (thread === null
|
|
2596
|
+
const thread = await threadOrNull2(this.ctx.threads, threadId);
|
|
2597
|
+
if (thread === null) return NOT_IMPORTED;
|
|
2477
2598
|
if (thread.session?.activeTurnId != null) return SKIPPED_ACTIVE;
|
|
2478
2599
|
if (thread.session?.status === "running" || thread.session?.status === "starting")
|
|
2479
2600
|
return SKIPPED_ACTIVE;
|
|
2601
|
+
const timeline = await readExistingTimeline(this.ctx.threads, threadId).catch(
|
|
2602
|
+
(error) => {
|
|
2603
|
+
if (error instanceof TimelineKeptChangingError) return "busy";
|
|
2604
|
+
throw error;
|
|
2605
|
+
}
|
|
2606
|
+
);
|
|
2607
|
+
if (timeline === "busy") return SKIPPED_ACTIVE;
|
|
2608
|
+
if (timeline === null) return NOT_IMPORTED;
|
|
2480
2609
|
const records = await readClaudeSessionRecords({
|
|
2481
2610
|
fs: this.ctx.fs,
|
|
2482
2611
|
transcriptDir,
|
|
@@ -2490,8 +2619,8 @@ var NativeSessionTail = class {
|
|
|
2490
2619
|
nativeSessionId,
|
|
2491
2620
|
instanceId: binding.instanceId,
|
|
2492
2621
|
transcript: readClaudeTranscript(records),
|
|
2493
|
-
existingMessages:
|
|
2494
|
-
existingActivities:
|
|
2622
|
+
existingMessages: timeline.messages,
|
|
2623
|
+
existingActivities: timeline.activities,
|
|
2495
2624
|
fallbackIso: appendedAt
|
|
2496
2625
|
});
|
|
2497
2626
|
const batches = importedHistoryBatches(history);
|
|
@@ -2525,7 +2654,7 @@ var NativeSessionTail = class {
|
|
|
2525
2654
|
});
|
|
2526
2655
|
if (history.appendedMessageCount === 0 && (history.hasLegacyMatches || history.activities.length === 0))
|
|
2527
2656
|
return { status: "synced", appended: history.appendedMessageCount };
|
|
2528
|
-
const current = await this.ctx.
|
|
2657
|
+
const current = await threadOrNull2(this.ctx.threads, threadId);
|
|
2529
2658
|
if (current?.session == null || current.session.status === "stopped" || current.session.activeTurnId !== null || current.session.status === "running" || current.session.status === "starting")
|
|
2530
2659
|
return { status: "synced", appended: history.appendedMessageCount };
|
|
2531
2660
|
await this.ctx.providers.sessions.stop(threadId).catch((error) => {
|
|
@@ -2541,7 +2670,7 @@ var NativeSessionTail = class {
|
|
|
2541
2670
|
// src/index.ts
|
|
2542
2671
|
var index_default = definePlugin({
|
|
2543
2672
|
id: "session-import",
|
|
2544
|
-
apiVersion:
|
|
2673
|
+
apiVersion: 9,
|
|
2545
2674
|
activate: (ctx) => {
|
|
2546
2675
|
ctx.command({
|
|
2547
2676
|
name: "scan",
|
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.3024",
|
|
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.3024",
|
|
24
24
|
"ycodium-jsonl-stream": "0.0.0"
|
|
25
25
|
},
|
|
26
26
|
"ycodium": {
|