chat-logbook 0.7.1 → 0.8.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/README.md +8 -8
- package/api/dist/drizzle/0002_rename_sessions_meta_to_chats_meta.sql +1 -0
- package/api/dist/drizzle/archive/0005_rename_session_to_chat.sql +17 -0
- package/api/dist/drizzle/archive/meta/_journal.json +7 -0
- package/api/dist/drizzle/meta/_journal.json +7 -0
- package/api/dist/index.js +107 -120
- package/package.json +1 -1
- package/web/dist/assets/{index-Cl7dcz04.js → index-1hCPKEIk.js} +2 -2
- package/web/dist/index.html +1 -1
package/api/dist/index.js
CHANGED
|
@@ -4758,12 +4758,12 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
4758
4758
|
var schema_exports = {};
|
|
4759
4759
|
__export(schema_exports, {
|
|
4760
4760
|
archiveMeta: () => archiveMeta,
|
|
4761
|
+
chatScanState: () => chatScanState,
|
|
4762
|
+
chats: () => chats,
|
|
4761
4763
|
ingestionEvents: () => ingestionEvents,
|
|
4762
4764
|
messages: () => messages,
|
|
4763
4765
|
rawMessages: () => rawMessages,
|
|
4764
|
-
schemaVersion: () => schemaVersion
|
|
4765
|
-
sessionScanState: () => sessionScanState,
|
|
4766
|
-
sessions: () => sessions
|
|
4766
|
+
schemaVersion: () => schemaVersion
|
|
4767
4767
|
});
|
|
4768
4768
|
|
|
4769
4769
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
|
|
@@ -8179,26 +8179,24 @@ var schemaVersion = sqliteTable("schema_version", {
|
|
|
8179
8179
|
version: integer("version").primaryKey(),
|
|
8180
8180
|
appliedAt: integer("applied_at", { mode: "timestamp_ms" }).notNull()
|
|
8181
8181
|
});
|
|
8182
|
-
var
|
|
8183
|
-
"
|
|
8182
|
+
var chats = sqliteTable(
|
|
8183
|
+
"chats",
|
|
8184
8184
|
{
|
|
8185
8185
|
id: text("id").primaryKey(),
|
|
8186
|
-
|
|
8186
|
+
chatId: text("chat_id").notNull().unique(),
|
|
8187
8187
|
agent: text("agent").notNull(),
|
|
8188
|
-
|
|
8188
|
+
sourceId: text("source_id").notNull(),
|
|
8189
8189
|
firstSeenAt: integer("first_seen_at", { mode: "timestamp_ms" }).notNull(),
|
|
8190
8190
|
project: text("project")
|
|
8191
8191
|
},
|
|
8192
|
-
(t) => [
|
|
8193
|
-
uniqueIndex("sessions_agent_source_idx").on(t.agent, t.sourceSessionId)
|
|
8194
|
-
]
|
|
8192
|
+
(t) => [uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId)]
|
|
8195
8193
|
);
|
|
8196
8194
|
var rawMessages = sqliteTable(
|
|
8197
8195
|
"raw_messages",
|
|
8198
8196
|
{
|
|
8199
8197
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8200
8198
|
agent: text("agent").notNull(),
|
|
8201
|
-
|
|
8199
|
+
sourceId: text("source_id").notNull(),
|
|
8202
8200
|
sourcePath: text("source_path").notNull(),
|
|
8203
8201
|
sourceLocator: text("source_locator").notNull(),
|
|
8204
8202
|
rawPayload: text("raw_payload").notNull(),
|
|
@@ -8206,11 +8204,7 @@ var rawMessages = sqliteTable(
|
|
|
8206
8204
|
ingestedAt: integer("ingested_at", { mode: "timestamp_ms" }).notNull()
|
|
8207
8205
|
},
|
|
8208
8206
|
(t) => [
|
|
8209
|
-
uniqueIndex("raw_messages_idem_idx").on(
|
|
8210
|
-
t.agent,
|
|
8211
|
-
t.sessionId,
|
|
8212
|
-
t.payloadHash
|
|
8213
|
-
)
|
|
8207
|
+
uniqueIndex("raw_messages_idem_idx").on(t.agent, t.sourceId, t.payloadHash)
|
|
8214
8208
|
]
|
|
8215
8209
|
);
|
|
8216
8210
|
var messages = sqliteTable(
|
|
@@ -8218,7 +8212,7 @@ var messages = sqliteTable(
|
|
|
8218
8212
|
{
|
|
8219
8213
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8220
8214
|
agent: text("agent").notNull(),
|
|
8221
|
-
|
|
8215
|
+
sourceId: text("source_id").notNull(),
|
|
8222
8216
|
messageId: text("message_id").notNull(),
|
|
8223
8217
|
role: text("role").notNull(),
|
|
8224
8218
|
ts: integer("ts", { mode: "timestamp_ms" }).notNull(),
|
|
@@ -8227,15 +8221,15 @@ var messages = sqliteTable(
|
|
|
8227
8221
|
rawId: integer("raw_id").notNull().references(() => rawMessages.id)
|
|
8228
8222
|
},
|
|
8229
8223
|
(t) => [
|
|
8230
|
-
uniqueIndex("messages_canonical_idx").on(t.agent, t.
|
|
8224
|
+
uniqueIndex("messages_canonical_idx").on(t.agent, t.sourceId, t.messageId)
|
|
8231
8225
|
]
|
|
8232
8226
|
);
|
|
8233
|
-
var
|
|
8227
|
+
var chatScanState = sqliteTable(
|
|
8234
8228
|
"session_scan_state",
|
|
8235
8229
|
{
|
|
8236
8230
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8237
8231
|
agent: text("agent").notNull(),
|
|
8238
|
-
|
|
8232
|
+
sourceId: text("source_id").notNull(),
|
|
8239
8233
|
sourcePath: text("source_path").notNull(),
|
|
8240
8234
|
lastMtimeMs: integer("last_mtime_ms").notNull(),
|
|
8241
8235
|
lastSizeBytes: integer("last_size_bytes").notNull(),
|
|
@@ -8243,12 +8237,12 @@ var sessionScanState = sqliteTable(
|
|
|
8243
8237
|
mode: "timestamp_ms"
|
|
8244
8238
|
}).notNull()
|
|
8245
8239
|
},
|
|
8246
|
-
(t) => [uniqueIndex("session_scan_state_idx").on(t.agent, t.
|
|
8240
|
+
(t) => [uniqueIndex("session_scan_state_idx").on(t.agent, t.sourceId)]
|
|
8247
8241
|
);
|
|
8248
8242
|
var ingestionEvents = sqliteTable("ingestion_events", {
|
|
8249
8243
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
|
8250
8244
|
agent: text("agent").notNull(),
|
|
8251
|
-
|
|
8245
|
+
sourceId: text("source_id").notNull(),
|
|
8252
8246
|
sourcePath: text("source_path").notNull(),
|
|
8253
8247
|
eventType: text("event_type").notNull(),
|
|
8254
8248
|
detail: text("detail", { mode: "json" }).notNull(),
|
|
@@ -8256,7 +8250,7 @@ var ingestionEvents = sqliteTable("ingestion_events", {
|
|
|
8256
8250
|
});
|
|
8257
8251
|
|
|
8258
8252
|
// src/visibility.ts
|
|
8259
|
-
function
|
|
8253
|
+
function loadChatVisibility(metadata2, opts) {
|
|
8260
8254
|
const trashed = new Set(metadata2.listDeletedIds());
|
|
8261
8255
|
const showTrashed = opts.includeTrashed === true;
|
|
8262
8256
|
return {
|
|
@@ -8279,59 +8273,59 @@ function toApiBlock(block) {
|
|
|
8279
8273
|
var CLAUDE_CODE_AGENT = "claude-code";
|
|
8280
8274
|
function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDistDir2 }) {
|
|
8281
8275
|
const app2 = new Hono2();
|
|
8282
|
-
function
|
|
8283
|
-
return archive2.db.select().from(
|
|
8276
|
+
function findArchiveChatBySourceId(sourceId) {
|
|
8277
|
+
return archive2.db.select().from(chats).where(eq(chats.sourceId, sourceId)).get();
|
|
8284
8278
|
}
|
|
8285
|
-
app2.get("/api/
|
|
8286
|
-
const visibility =
|
|
8279
|
+
app2.get("/api/chats", (c) => {
|
|
8280
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
8287
8281
|
includeTrashed: c.req.query("includeTrashed") === "true"
|
|
8288
8282
|
});
|
|
8289
|
-
const rows = archive2.db.select().from(
|
|
8290
|
-
const
|
|
8283
|
+
const rows = archive2.db.select().from(chats).all();
|
|
8284
|
+
const chats2 = [];
|
|
8291
8285
|
for (const row of rows) {
|
|
8292
8286
|
if (!visibility.isVisible(row.id)) continue;
|
|
8293
8287
|
const isDeleted = visibility.isTrashed(row.id);
|
|
8294
8288
|
const lastMessage = archive2.db.select({ ts: messages.ts }).from(messages).where(
|
|
8295
8289
|
and(
|
|
8296
8290
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8297
|
-
eq(messages.
|
|
8291
|
+
eq(messages.sourceId, row.sourceId)
|
|
8298
8292
|
)
|
|
8299
8293
|
).orderBy(desc(messages.ts)).limit(1).get();
|
|
8300
|
-
const
|
|
8301
|
-
id: row.
|
|
8294
|
+
const chat = {
|
|
8295
|
+
id: row.sourceId,
|
|
8302
8296
|
title: deriveTitle(archive2, metadata2, row),
|
|
8303
8297
|
project: row.project ?? "",
|
|
8304
8298
|
createdAt: row.firstSeenAt.getTime(),
|
|
8305
8299
|
updatedAt: lastMessage ? lastMessage.ts.getTime() : row.firstSeenAt.getTime()
|
|
8306
8300
|
};
|
|
8307
|
-
if (isDeleted)
|
|
8308
|
-
|
|
8301
|
+
if (isDeleted) chat.isDeleted = true;
|
|
8302
|
+
chats2.push(chat);
|
|
8309
8303
|
}
|
|
8310
|
-
return c.json({
|
|
8304
|
+
return c.json({ chats: chats2 });
|
|
8311
8305
|
});
|
|
8312
|
-
app2.delete("/api/
|
|
8313
|
-
const
|
|
8314
|
-
const row =
|
|
8306
|
+
app2.delete("/api/chats/:id", (c) => {
|
|
8307
|
+
const id = c.req.param("id");
|
|
8308
|
+
const row = findArchiveChatBySourceId(id);
|
|
8315
8309
|
if (!row) {
|
|
8316
|
-
return c.json({ error: "
|
|
8310
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8317
8311
|
}
|
|
8318
8312
|
metadata2.softDelete(row.id);
|
|
8319
8313
|
return c.body(null, 204);
|
|
8320
8314
|
});
|
|
8321
|
-
app2.post("/api/
|
|
8322
|
-
const
|
|
8323
|
-
const row =
|
|
8315
|
+
app2.post("/api/chats/:id/restore", (c) => {
|
|
8316
|
+
const id = c.req.param("id");
|
|
8317
|
+
const row = findArchiveChatBySourceId(id);
|
|
8324
8318
|
if (!row) {
|
|
8325
|
-
return c.json({ error: "
|
|
8319
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8326
8320
|
}
|
|
8327
8321
|
metadata2.restore(row.id);
|
|
8328
8322
|
return c.body(null, 204);
|
|
8329
8323
|
});
|
|
8330
|
-
app2.patch("/api/
|
|
8331
|
-
const
|
|
8332
|
-
const row =
|
|
8324
|
+
app2.patch("/api/chats/:id/title", async (c) => {
|
|
8325
|
+
const id = c.req.param("id");
|
|
8326
|
+
const row = findArchiveChatBySourceId(id);
|
|
8333
8327
|
if (!row) {
|
|
8334
|
-
return c.json({ error: "
|
|
8328
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8335
8329
|
}
|
|
8336
8330
|
let body;
|
|
8337
8331
|
try {
|
|
@@ -8350,22 +8344,22 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
|
|
|
8350
8344
|
metadata2.setCustomTitle(row.id, trimmed.length > 0 ? trimmed : null);
|
|
8351
8345
|
return c.body(null, 204);
|
|
8352
8346
|
});
|
|
8353
|
-
app2.get("/api/
|
|
8354
|
-
const
|
|
8355
|
-
const row =
|
|
8347
|
+
app2.get("/api/chats/:id", (c) => {
|
|
8348
|
+
const id = c.req.param("id");
|
|
8349
|
+
const row = findArchiveChatBySourceId(id);
|
|
8356
8350
|
if (!row) {
|
|
8357
|
-
return c.json({ error: "
|
|
8351
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8358
8352
|
}
|
|
8359
|
-
const visibility =
|
|
8353
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
8360
8354
|
includeTrashed: c.req.query("includeTrashed") === "true"
|
|
8361
8355
|
});
|
|
8362
8356
|
if (!visibility.isVisible(row.id)) {
|
|
8363
|
-
return c.json({ error: "
|
|
8357
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
8364
8358
|
}
|
|
8365
8359
|
const rows = archive2.db.select().from(messages).where(
|
|
8366
8360
|
and(
|
|
8367
8361
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8368
|
-
eq(messages.
|
|
8362
|
+
eq(messages.sourceId, id)
|
|
8369
8363
|
)
|
|
8370
8364
|
).orderBy(asc(messages.ts)).all();
|
|
8371
8365
|
const messages2 = rows.map((m) => ({
|
|
@@ -8381,7 +8375,7 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
|
|
|
8381
8375
|
const firstUser = archive3.db.select({ text: messages.text }).from(messages).where(
|
|
8382
8376
|
and(
|
|
8383
8377
|
eq(messages.agent, CLAUDE_CODE_AGENT),
|
|
8384
|
-
eq(messages.
|
|
8378
|
+
eq(messages.sourceId, row.sourceId),
|
|
8385
8379
|
eq(messages.role, "user")
|
|
8386
8380
|
)
|
|
8387
8381
|
).orderBy(asc(messages.ts)).limit(1).get();
|
|
@@ -8601,18 +8595,18 @@ function migrate(db, config) {
|
|
|
8601
8595
|
db.dialect.migrate(migrations, db.session, config);
|
|
8602
8596
|
}
|
|
8603
8597
|
|
|
8604
|
-
// src/archive/
|
|
8598
|
+
// src/archive/chat-id.ts
|
|
8605
8599
|
import crypto4 from "crypto";
|
|
8606
8600
|
var CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
8607
|
-
var
|
|
8601
|
+
var CHAT_ID_LENGTH = 6;
|
|
8608
8602
|
var MAX_RETRIES = 5;
|
|
8609
|
-
function
|
|
8603
|
+
function generateChatId({
|
|
8610
8604
|
isTaken,
|
|
8611
8605
|
randomIndex = () => crypto4.randomInt(CROCKFORD_ALPHABET.length)
|
|
8612
8606
|
}) {
|
|
8613
8607
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
8614
8608
|
let code = "";
|
|
8615
|
-
for (let i = 0; i <
|
|
8609
|
+
for (let i = 0; i < CHAT_ID_LENGTH; i++) {
|
|
8616
8610
|
code += CROCKFORD_ALPHABET[randomIndex()];
|
|
8617
8611
|
}
|
|
8618
8612
|
if (!isTaken(code)) {
|
|
@@ -8620,7 +8614,7 @@ function generateShortCode({
|
|
|
8620
8614
|
}
|
|
8621
8615
|
}
|
|
8622
8616
|
throw new Error(
|
|
8623
|
-
`Failed to generate unique
|
|
8617
|
+
`Failed to generate unique chat_id after ${MAX_RETRIES + 1} attempts`
|
|
8624
8618
|
);
|
|
8625
8619
|
}
|
|
8626
8620
|
|
|
@@ -8668,9 +8662,9 @@ function createArchiveRepository({
|
|
|
8668
8662
|
getAppliedMigrations() {
|
|
8669
8663
|
return db.select().from(schemaVersion).all().map((r) => ({ version: r.version, appliedAt: r.appliedAt }));
|
|
8670
8664
|
},
|
|
8671
|
-
|
|
8672
|
-
return
|
|
8673
|
-
isTaken: (candidate) => db.select({ id:
|
|
8665
|
+
generateChatId() {
|
|
8666
|
+
return generateChatId({
|
|
8667
|
+
isTaken: (candidate) => db.select({ id: chats.id }).from(chats).where(eq(chats.chatId, candidate)).get() !== void 0
|
|
8674
8668
|
});
|
|
8675
8669
|
},
|
|
8676
8670
|
close() {
|
|
@@ -8686,7 +8680,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
8686
8680
|
import Database2 from "better-sqlite3";
|
|
8687
8681
|
|
|
8688
8682
|
// src/metadata/schema.ts
|
|
8689
|
-
var
|
|
8683
|
+
var chatsMeta = sqliteTable("chats_meta", {
|
|
8690
8684
|
id: text("id").primaryKey(),
|
|
8691
8685
|
isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
|
|
8692
8686
|
customTitle: text("custom_title"),
|
|
@@ -8711,71 +8705,71 @@ var CLAUDE_CODE_AGENT2 = "claude-code";
|
|
|
8711
8705
|
function createMetadataRepository({
|
|
8712
8706
|
dataDir: dataDir2,
|
|
8713
8707
|
lookupInternalId,
|
|
8714
|
-
|
|
8708
|
+
ensureChat: ensureChat2
|
|
8715
8709
|
}) {
|
|
8716
8710
|
fs3.mkdirSync(dataDir2, { recursive: true });
|
|
8717
8711
|
const sqlite = new Database2(path2.join(dataDir2, "data.db"));
|
|
8718
8712
|
const db = drizzle(sqlite);
|
|
8719
8713
|
migrate(db, { migrationsFolder: resolveMigrationsFolder2() });
|
|
8720
8714
|
if (lookupInternalId) {
|
|
8721
|
-
rekeyLegacyRows(sqlite, db, lookupInternalId,
|
|
8715
|
+
rekeyLegacyRows(sqlite, db, lookupInternalId, ensureChat2);
|
|
8722
8716
|
}
|
|
8723
8717
|
return {
|
|
8724
8718
|
softDelete(internalId) {
|
|
8725
8719
|
const now = /* @__PURE__ */ new Date();
|
|
8726
|
-
db.insert(
|
|
8720
|
+
db.insert(chatsMeta).values({
|
|
8727
8721
|
id: internalId,
|
|
8728
8722
|
isDeleted: true,
|
|
8729
8723
|
createdAt: now,
|
|
8730
8724
|
updatedAt: now
|
|
8731
8725
|
}).onConflictDoUpdate({
|
|
8732
|
-
target:
|
|
8726
|
+
target: chatsMeta.id,
|
|
8733
8727
|
set: { isDeleted: true, updatedAt: now }
|
|
8734
8728
|
}).run();
|
|
8735
8729
|
},
|
|
8736
8730
|
restore(internalId) {
|
|
8737
8731
|
const now = /* @__PURE__ */ new Date();
|
|
8738
|
-
db.update(
|
|
8732
|
+
db.update(chatsMeta).set({ isDeleted: false, updatedAt: now }).where(eq(chatsMeta.id, internalId)).run();
|
|
8739
8733
|
},
|
|
8740
8734
|
isDeleted(internalId) {
|
|
8741
|
-
const row = db.select({ isDeleted:
|
|
8735
|
+
const row = db.select({ isDeleted: chatsMeta.isDeleted }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
8742
8736
|
return row?.isDeleted ?? false;
|
|
8743
8737
|
},
|
|
8744
8738
|
listDeletedIds() {
|
|
8745
|
-
const rows = db.select({ id:
|
|
8739
|
+
const rows = db.select({ id: chatsMeta.id }).from(chatsMeta).where(eq(chatsMeta.isDeleted, true)).all();
|
|
8746
8740
|
return rows.map((r) => r.id);
|
|
8747
8741
|
},
|
|
8748
8742
|
getCustomTitle(internalId) {
|
|
8749
|
-
const row = db.select({ customTitle:
|
|
8743
|
+
const row = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get();
|
|
8750
8744
|
return row?.customTitle ?? null;
|
|
8751
8745
|
},
|
|
8752
8746
|
setCustomTitle(internalId, title) {
|
|
8753
8747
|
const now = /* @__PURE__ */ new Date();
|
|
8754
|
-
db.insert(
|
|
8748
|
+
db.insert(chatsMeta).values({
|
|
8755
8749
|
id: internalId,
|
|
8756
8750
|
customTitle: title,
|
|
8757
8751
|
createdAt: now,
|
|
8758
8752
|
updatedAt: now
|
|
8759
8753
|
}).onConflictDoUpdate({
|
|
8760
|
-
target:
|
|
8754
|
+
target: chatsMeta.id,
|
|
8761
8755
|
set: { customTitle: title, updatedAt: now }
|
|
8762
8756
|
}).run();
|
|
8763
8757
|
}
|
|
8764
8758
|
};
|
|
8765
8759
|
}
|
|
8766
8760
|
var REKEY_USER_VERSION = 4;
|
|
8767
|
-
function rekeyLegacyRows(sqlite, db, lookupInternalId,
|
|
8761
|
+
function rekeyLegacyRows(sqlite, db, lookupInternalId, ensureChat2) {
|
|
8768
8762
|
const current = sqlite.pragma("user_version", { simple: true });
|
|
8769
8763
|
if (current >= REKEY_USER_VERSION) return;
|
|
8770
|
-
const rows = db.select({ id:
|
|
8764
|
+
const rows = db.select({ id: chatsMeta.id }).from(chatsMeta).all();
|
|
8771
8765
|
for (const row of rows) {
|
|
8772
8766
|
let target = lookupInternalId(CLAUDE_CODE_AGENT2, row.id);
|
|
8773
8767
|
if (target === null) {
|
|
8774
|
-
if (!
|
|
8775
|
-
target =
|
|
8768
|
+
if (!ensureChat2) continue;
|
|
8769
|
+
target = ensureChat2(CLAUDE_CODE_AGENT2, row.id);
|
|
8776
8770
|
}
|
|
8777
8771
|
if (target === row.id) continue;
|
|
8778
|
-
db.update(
|
|
8772
|
+
db.update(chatsMeta).set({ id: target, updatedAt: /* @__PURE__ */ new Date() }).where(eq(chatsMeta.id, row.id)).run();
|
|
8779
8773
|
}
|
|
8780
8774
|
sqlite.pragma(`user_version = ${REKEY_USER_VERSION}`);
|
|
8781
8775
|
}
|
|
@@ -8795,13 +8789,13 @@ async function runIngestion(opts) {
|
|
|
8795
8789
|
for await (const ref of plugin.discover(opts.env)) {
|
|
8796
8790
|
result.scanned += 1;
|
|
8797
8791
|
const stat4 = safeStat(ref.sourcePath);
|
|
8798
|
-
const prior = opts.archive.db.select().from(
|
|
8792
|
+
const prior = opts.archive.db.select().from(chatScanState).where(
|
|
8799
8793
|
and(
|
|
8800
|
-
eq(
|
|
8801
|
-
eq(
|
|
8794
|
+
eq(chatScanState.agent, plugin.id),
|
|
8795
|
+
eq(chatScanState.sourceId, ref.sourceId)
|
|
8802
8796
|
)
|
|
8803
8797
|
).get();
|
|
8804
|
-
|
|
8798
|
+
ensureChat(opts.archive, plugin.id, ref.sourceId, now(), ref.project);
|
|
8805
8799
|
if (stat4 && prior && prior.lastMtimeMs === stat4.mtimeMs && prior.lastSizeBytes === stat4.size) {
|
|
8806
8800
|
result.skippedByMtime += 1;
|
|
8807
8801
|
continue;
|
|
@@ -8812,7 +8806,7 @@ async function runIngestion(opts) {
|
|
|
8812
8806
|
const existing = opts.archive.db.select({ id: rawMessages.id }).from(rawMessages).where(
|
|
8813
8807
|
and(
|
|
8814
8808
|
eq(rawMessages.agent, plugin.id),
|
|
8815
|
-
eq(rawMessages.
|
|
8809
|
+
eq(rawMessages.sourceId, ref.sourceId),
|
|
8816
8810
|
eq(rawMessages.payloadHash, payloadHash)
|
|
8817
8811
|
)
|
|
8818
8812
|
).get();
|
|
@@ -8822,7 +8816,7 @@ async function runIngestion(opts) {
|
|
|
8822
8816
|
} else {
|
|
8823
8817
|
const inserted = opts.archive.db.insert(rawMessages).values({
|
|
8824
8818
|
agent: plugin.id,
|
|
8825
|
-
|
|
8819
|
+
sourceId: ref.sourceId,
|
|
8826
8820
|
sourcePath: raw2.sourcePath,
|
|
8827
8821
|
sourceLocator: raw2.sourceLocator,
|
|
8828
8822
|
rawPayload: payloadJson,
|
|
@@ -8837,7 +8831,7 @@ async function runIngestion(opts) {
|
|
|
8837
8831
|
const upserted = upsertCanonical(
|
|
8838
8832
|
opts.archive,
|
|
8839
8833
|
plugin.id,
|
|
8840
|
-
ref.
|
|
8834
|
+
ref.sourceId,
|
|
8841
8835
|
canonical,
|
|
8842
8836
|
rawId
|
|
8843
8837
|
);
|
|
@@ -8847,7 +8841,7 @@ async function runIngestion(opts) {
|
|
|
8847
8841
|
recordScanState(
|
|
8848
8842
|
opts.archive,
|
|
8849
8843
|
plugin.id,
|
|
8850
|
-
ref.
|
|
8844
|
+
ref.sourceId,
|
|
8851
8845
|
ref.sourcePath,
|
|
8852
8846
|
stat4,
|
|
8853
8847
|
now()
|
|
@@ -8864,24 +8858,21 @@ function safeStat(p) {
|
|
|
8864
8858
|
return null;
|
|
8865
8859
|
}
|
|
8866
8860
|
}
|
|
8867
|
-
function recordScanState(archive2, agent,
|
|
8868
|
-
const existing = archive2.db.select({ id:
|
|
8869
|
-
and(
|
|
8870
|
-
eq(sessionScanState.agent, agent),
|
|
8871
|
-
eq(sessionScanState.sessionId, sessionId)
|
|
8872
|
-
)
|
|
8861
|
+
function recordScanState(archive2, agent, sourceId, sourcePath, stat4, scannedAt) {
|
|
8862
|
+
const existing = archive2.db.select({ id: chatScanState.id }).from(chatScanState).where(
|
|
8863
|
+
and(eq(chatScanState.agent, agent), eq(chatScanState.sourceId, sourceId))
|
|
8873
8864
|
).get();
|
|
8874
8865
|
if (existing) {
|
|
8875
|
-
archive2.db.update(
|
|
8866
|
+
archive2.db.update(chatScanState).set({
|
|
8876
8867
|
sourcePath,
|
|
8877
8868
|
lastMtimeMs: stat4.mtimeMs,
|
|
8878
8869
|
lastSizeBytes: stat4.size,
|
|
8879
8870
|
lastScannedAt: scannedAt
|
|
8880
|
-
}).where(eq(
|
|
8871
|
+
}).where(eq(chatScanState.id, existing.id)).run();
|
|
8881
8872
|
} else {
|
|
8882
|
-
archive2.db.insert(
|
|
8873
|
+
archive2.db.insert(chatScanState).values({
|
|
8883
8874
|
agent,
|
|
8884
|
-
|
|
8875
|
+
sourceId,
|
|
8885
8876
|
sourcePath,
|
|
8886
8877
|
lastMtimeMs: stat4.mtimeMs,
|
|
8887
8878
|
lastSizeBytes: stat4.size,
|
|
@@ -8889,36 +8880,31 @@ function recordScanState(archive2, agent, sessionId, sourcePath, stat4, scannedA
|
|
|
8889
8880
|
}).run();
|
|
8890
8881
|
}
|
|
8891
8882
|
}
|
|
8892
|
-
function
|
|
8893
|
-
const existing = archive2.db.select().from(
|
|
8894
|
-
and(
|
|
8895
|
-
eq(sessions.agent, agent),
|
|
8896
|
-
eq(sessions.sourceSessionId, sourceSessionId)
|
|
8897
|
-
)
|
|
8898
|
-
).get();
|
|
8883
|
+
function ensureChat(archive2, agent, sourceId, firstSeenAt, project) {
|
|
8884
|
+
const existing = archive2.db.select().from(chats).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).get();
|
|
8899
8885
|
if (existing) {
|
|
8900
8886
|
if (project && existing.project !== project) {
|
|
8901
|
-
archive2.db.update(
|
|
8887
|
+
archive2.db.update(chats).set({ project }).where(eq(chats.id, existing.id)).run();
|
|
8902
8888
|
}
|
|
8903
8889
|
return existing.id;
|
|
8904
8890
|
}
|
|
8905
8891
|
const id = crypto6.randomUUID();
|
|
8906
|
-
const
|
|
8907
|
-
archive2.db.insert(
|
|
8892
|
+
const chatId = archive2.generateChatId();
|
|
8893
|
+
archive2.db.insert(chats).values({
|
|
8908
8894
|
id,
|
|
8909
|
-
|
|
8895
|
+
chatId,
|
|
8910
8896
|
agent,
|
|
8911
|
-
|
|
8897
|
+
sourceId,
|
|
8912
8898
|
firstSeenAt,
|
|
8913
8899
|
project: project ?? null
|
|
8914
8900
|
}).run();
|
|
8915
8901
|
return id;
|
|
8916
8902
|
}
|
|
8917
|
-
function upsertCanonical(archive2, agent,
|
|
8903
|
+
function upsertCanonical(archive2, agent, sourceId, msg, rawId) {
|
|
8918
8904
|
const existing = archive2.db.select().from(messages).where(
|
|
8919
8905
|
and(
|
|
8920
8906
|
eq(messages.agent, agent),
|
|
8921
|
-
eq(messages.
|
|
8907
|
+
eq(messages.sourceId, sourceId),
|
|
8922
8908
|
eq(messages.messageId, msg.messageId)
|
|
8923
8909
|
)
|
|
8924
8910
|
).get();
|
|
@@ -8926,7 +8912,7 @@ function upsertCanonical(archive2, agent, sessionId, msg, rawId) {
|
|
|
8926
8912
|
if (!existing) {
|
|
8927
8913
|
archive2.db.insert(messages).values({
|
|
8928
8914
|
agent,
|
|
8929
|
-
|
|
8915
|
+
sourceId,
|
|
8930
8916
|
messageId: msg.messageId,
|
|
8931
8917
|
role: msg.role,
|
|
8932
8918
|
ts,
|
|
@@ -10741,7 +10727,7 @@ function startWatcher(opts) {
|
|
|
10741
10727
|
try {
|
|
10742
10728
|
opts.archive.db.insert(ingestionEvents).values({
|
|
10743
10729
|
agent: binding.plugin.id,
|
|
10744
|
-
|
|
10730
|
+
sourceId: binding.ref.sourceId,
|
|
10745
10731
|
sourcePath: removedPath,
|
|
10746
10732
|
eventType: "unlink_observed",
|
|
10747
10733
|
detail: { path: removedPath },
|
|
@@ -10794,10 +10780,10 @@ var ClaudeCodePlugin = class {
|
|
|
10794
10780
|
for (const file of files) {
|
|
10795
10781
|
if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
|
|
10796
10782
|
const sourcePath = path3.join(projectPath, file.name);
|
|
10797
|
-
const
|
|
10783
|
+
const sourceId = file.name.replace(/\.jsonl$/, "");
|
|
10798
10784
|
const cwd = await readCwdFromJsonl(sourcePath);
|
|
10799
10785
|
yield {
|
|
10800
|
-
|
|
10786
|
+
sourceId,
|
|
10801
10787
|
sourcePath,
|
|
10802
10788
|
watchPaths: [sourcePath],
|
|
10803
10789
|
project: cwd ? path3.basename(cwd) : void 0
|
|
@@ -10814,7 +10800,7 @@ var ClaudeCodePlugin = class {
|
|
|
10814
10800
|
lineNo += 1;
|
|
10815
10801
|
if (!line) continue;
|
|
10816
10802
|
yield {
|
|
10817
|
-
|
|
10803
|
+
sourceId: ref.sourceId,
|
|
10818
10804
|
sourcePath: ref.sourcePath,
|
|
10819
10805
|
sourceLocator: `L${lineNo}`,
|
|
10820
10806
|
payload: JSON.parse(line)
|
|
@@ -10917,6 +10903,7 @@ function parseCliArgs(argv, env = {}) {
|
|
|
10917
10903
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
10918
10904
|
return { kind: "help" };
|
|
10919
10905
|
}
|
|
10906
|
+
const open2 = !env.CHAT_LOGBOOK_NO_OPEN;
|
|
10920
10907
|
const portIdx = argv.findIndex((a) => a === "--port" || a === "-p");
|
|
10921
10908
|
if (portIdx !== -1) {
|
|
10922
10909
|
const raw2 = argv[portIdx + 1];
|
|
@@ -10939,12 +10926,12 @@ function parseCliArgs(argv, env = {}) {
|
|
|
10939
10926
|
message: `Invalid port "${raw2}": must be between 1 and 65535`
|
|
10940
10927
|
};
|
|
10941
10928
|
}
|
|
10942
|
-
return { kind: "run", port: port2 };
|
|
10929
|
+
return { kind: "run", port: port2, open: open2 };
|
|
10943
10930
|
}
|
|
10944
10931
|
if (env.PORT) {
|
|
10945
|
-
return { kind: "run", port: Number(env.PORT) };
|
|
10932
|
+
return { kind: "run", port: Number(env.PORT), open: open2 };
|
|
10946
10933
|
}
|
|
10947
|
-
return { kind: "run", port: DEFAULT_PORT };
|
|
10934
|
+
return { kind: "run", port: DEFAULT_PORT, open: open2 };
|
|
10948
10935
|
}
|
|
10949
10936
|
|
|
10950
10937
|
// src/cli/help.ts
|
|
@@ -11011,7 +10998,7 @@ function openBrowser(url) {
|
|
|
11011
10998
|
var server = serve({ fetch: app.fetch, port }, (info) => {
|
|
11012
10999
|
const url = `http://localhost:${info.port}`;
|
|
11013
11000
|
console.log(`chat-logbook is running at \x1B[36m${url}\x1B[0m`);
|
|
11014
|
-
openBrowser(url);
|
|
11001
|
+
if (action.open) openBrowser(url);
|
|
11015
11002
|
});
|
|
11016
11003
|
server.on("error", (err) => {
|
|
11017
11004
|
if (err.code === "EADDRINUSE") {
|