chat-logbook 0.13.0 → 0.14.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 +3 -2
- package/api/dist/drizzle/0005_add_deleted_at_index.sql +1 -0
- package/api/dist/drizzle/0006_add_chat_sort_keys.sql +7 -0
- package/api/dist/drizzle/archive/0009_add_chats_updated_at.sql +21 -0
- package/api/dist/drizzle/archive/0010_add_chats_created_at.sql +26 -0
- package/api/dist/drizzle/archive/meta/_journal.json +14 -0
- package/api/dist/drizzle/meta/_journal.json +14 -0
- package/api/dist/index.js +1058 -346
- package/package.json +1 -1
- package/web/dist/assets/index-BQlthoCZ.css +2 -0
- package/web/dist/assets/index-zW6L5xEX.js +57 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BlF09kci.css +0 -2
- package/web/dist/assets/index-CHr9-eHr.js +0 -57
package/api/dist/index.js
CHANGED
|
@@ -5,9 +5,9 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import
|
|
8
|
+
import fs8 from "fs";
|
|
9
9
|
import os from "os";
|
|
10
|
-
import
|
|
10
|
+
import path7 from "path";
|
|
11
11
|
import { exec } from "child_process";
|
|
12
12
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13
13
|
|
|
@@ -304,13 +304,13 @@ function writeFromReadableStreamDefaultReader(reader, writable, currentReadPromi
|
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
306
|
}
|
|
307
|
-
function writeFromReadableStream(
|
|
308
|
-
if (
|
|
307
|
+
function writeFromReadableStream(stream2, writable) {
|
|
308
|
+
if (stream2.locked) {
|
|
309
309
|
throw new TypeError("ReadableStream is locked.");
|
|
310
310
|
} else if (writable.destroyed) {
|
|
311
311
|
return;
|
|
312
312
|
}
|
|
313
|
-
return writeFromReadableStreamDefaultReader(
|
|
313
|
+
return writeFromReadableStreamDefaultReader(stream2.getReader(), writable);
|
|
314
314
|
}
|
|
315
315
|
var buildOutgoingHttpHeaders = (headers) => {
|
|
316
316
|
const res = {};
|
|
@@ -705,26 +705,26 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
705
705
|
};
|
|
706
706
|
|
|
707
707
|
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/utils/url.js
|
|
708
|
-
var splitPath = (
|
|
709
|
-
const paths =
|
|
708
|
+
var splitPath = (path8) => {
|
|
709
|
+
const paths = path8.split("/");
|
|
710
710
|
if (paths[0] === "") {
|
|
711
711
|
paths.shift();
|
|
712
712
|
}
|
|
713
713
|
return paths;
|
|
714
714
|
};
|
|
715
715
|
var splitRoutingPath = (routePath) => {
|
|
716
|
-
const { groups, path:
|
|
717
|
-
const paths = splitPath(
|
|
716
|
+
const { groups, path: path8 } = extractGroupsFromPath(routePath);
|
|
717
|
+
const paths = splitPath(path8);
|
|
718
718
|
return replaceGroupMarks(paths, groups);
|
|
719
719
|
};
|
|
720
|
-
var extractGroupsFromPath = (
|
|
720
|
+
var extractGroupsFromPath = (path8) => {
|
|
721
721
|
const groups = [];
|
|
722
|
-
|
|
722
|
+
path8 = path8.replace(/\{[^}]+\}/g, (match2, index2) => {
|
|
723
723
|
const mark = `@${index2}`;
|
|
724
724
|
groups.push([mark, match2]);
|
|
725
725
|
return mark;
|
|
726
726
|
});
|
|
727
|
-
return { groups, path:
|
|
727
|
+
return { groups, path: path8 };
|
|
728
728
|
};
|
|
729
729
|
var replaceGroupMarks = (paths, groups) => {
|
|
730
730
|
for (let i = groups.length - 1; i >= 0; i--) {
|
|
@@ -781,8 +781,8 @@ var getPath = (request) => {
|
|
|
781
781
|
const queryIndex = url.indexOf("?", i);
|
|
782
782
|
const hashIndex = url.indexOf("#", i);
|
|
783
783
|
const end = queryIndex === -1 ? hashIndex === -1 ? void 0 : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
|
|
784
|
-
const
|
|
785
|
-
return tryDecodeURI(
|
|
784
|
+
const path8 = url.slice(start, end);
|
|
785
|
+
return tryDecodeURI(path8.includes("%25") ? path8.replace(/%25/g, "%2525") : path8);
|
|
786
786
|
} else if (charCode === 63 || charCode === 35) {
|
|
787
787
|
break;
|
|
788
788
|
}
|
|
@@ -799,11 +799,11 @@ var mergePath = (base, sub, ...rest) => {
|
|
|
799
799
|
}
|
|
800
800
|
return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
|
|
801
801
|
};
|
|
802
|
-
var checkOptionalParameter = (
|
|
803
|
-
if (
|
|
802
|
+
var checkOptionalParameter = (path8) => {
|
|
803
|
+
if (path8.charCodeAt(path8.length - 1) !== 63 || !path8.includes(":")) {
|
|
804
804
|
return null;
|
|
805
805
|
}
|
|
806
|
-
const segments =
|
|
806
|
+
const segments = path8.split("/");
|
|
807
807
|
const results = [];
|
|
808
808
|
let basePath = "";
|
|
809
809
|
segments.forEach((segment) => {
|
|
@@ -944,9 +944,9 @@ var HonoRequest = class {
|
|
|
944
944
|
*/
|
|
945
945
|
path;
|
|
946
946
|
bodyCache = {};
|
|
947
|
-
constructor(request,
|
|
947
|
+
constructor(request, path8 = "/", matchResult = [[]]) {
|
|
948
948
|
this.raw = request;
|
|
949
|
-
this.path =
|
|
949
|
+
this.path = path8;
|
|
950
950
|
this.#matchResult = matchResult;
|
|
951
951
|
this.#validatedData = {};
|
|
952
952
|
}
|
|
@@ -1683,8 +1683,8 @@ var Hono = class _Hono {
|
|
|
1683
1683
|
return this;
|
|
1684
1684
|
};
|
|
1685
1685
|
});
|
|
1686
|
-
this.on = (method,
|
|
1687
|
-
for (const p of [
|
|
1686
|
+
this.on = (method, path8, ...handlers) => {
|
|
1687
|
+
for (const p of [path8].flat()) {
|
|
1688
1688
|
this.#path = p;
|
|
1689
1689
|
for (const m of [method].flat()) {
|
|
1690
1690
|
handlers.map((handler) => {
|
|
@@ -1741,8 +1741,8 @@ var Hono = class _Hono {
|
|
|
1741
1741
|
* app.route("/api", app2) // GET /api/user
|
|
1742
1742
|
* ```
|
|
1743
1743
|
*/
|
|
1744
|
-
route(
|
|
1745
|
-
const subApp = this.basePath(
|
|
1744
|
+
route(path8, app2) {
|
|
1745
|
+
const subApp = this.basePath(path8);
|
|
1746
1746
|
app2.routes.map((r) => {
|
|
1747
1747
|
let handler;
|
|
1748
1748
|
if (app2.errorHandler === errorHandler) {
|
|
@@ -1768,9 +1768,9 @@ var Hono = class _Hono {
|
|
|
1768
1768
|
* const api = new Hono().basePath('/api')
|
|
1769
1769
|
* ```
|
|
1770
1770
|
*/
|
|
1771
|
-
basePath(
|
|
1771
|
+
basePath(path8) {
|
|
1772
1772
|
const subApp = this.#clone();
|
|
1773
|
-
subApp._basePath = mergePath(this._basePath,
|
|
1773
|
+
subApp._basePath = mergePath(this._basePath, path8);
|
|
1774
1774
|
return subApp;
|
|
1775
1775
|
}
|
|
1776
1776
|
/**
|
|
@@ -1844,7 +1844,7 @@ var Hono = class _Hono {
|
|
|
1844
1844
|
* })
|
|
1845
1845
|
* ```
|
|
1846
1846
|
*/
|
|
1847
|
-
mount(
|
|
1847
|
+
mount(path8, applicationHandler, options) {
|
|
1848
1848
|
let replaceRequest;
|
|
1849
1849
|
let optionHandler;
|
|
1850
1850
|
if (options) {
|
|
@@ -1871,7 +1871,7 @@ var Hono = class _Hono {
|
|
|
1871
1871
|
return [c.env, executionContext];
|
|
1872
1872
|
};
|
|
1873
1873
|
replaceRequest ||= (() => {
|
|
1874
|
-
const mergedPath = mergePath(this._basePath,
|
|
1874
|
+
const mergedPath = mergePath(this._basePath, path8);
|
|
1875
1875
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
1876
1876
|
return (request) => {
|
|
1877
1877
|
const url = new URL(request.url);
|
|
@@ -1886,14 +1886,14 @@ var Hono = class _Hono {
|
|
|
1886
1886
|
}
|
|
1887
1887
|
await next();
|
|
1888
1888
|
};
|
|
1889
|
-
this.#addRoute(METHOD_NAME_ALL, mergePath(
|
|
1889
|
+
this.#addRoute(METHOD_NAME_ALL, mergePath(path8, "*"), handler);
|
|
1890
1890
|
return this;
|
|
1891
1891
|
}
|
|
1892
|
-
#addRoute(method,
|
|
1892
|
+
#addRoute(method, path8, handler) {
|
|
1893
1893
|
method = method.toUpperCase();
|
|
1894
|
-
|
|
1895
|
-
const r = { basePath: this._basePath, path:
|
|
1896
|
-
this.router.add(method,
|
|
1894
|
+
path8 = mergePath(this._basePath, path8);
|
|
1895
|
+
const r = { basePath: this._basePath, path: path8, method, handler };
|
|
1896
|
+
this.router.add(method, path8, [handler, r]);
|
|
1897
1897
|
this.routes.push(r);
|
|
1898
1898
|
}
|
|
1899
1899
|
#handleError(err, c) {
|
|
@@ -1906,10 +1906,10 @@ var Hono = class _Hono {
|
|
|
1906
1906
|
if (method === "HEAD") {
|
|
1907
1907
|
return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
|
|
1908
1908
|
}
|
|
1909
|
-
const
|
|
1910
|
-
const matchResult = this.router.match(method,
|
|
1909
|
+
const path8 = this.getPath(request, { env });
|
|
1910
|
+
const matchResult = this.router.match(method, path8);
|
|
1911
1911
|
const c = new Context(request, {
|
|
1912
|
-
path:
|
|
1912
|
+
path: path8,
|
|
1913
1913
|
matchResult,
|
|
1914
1914
|
env,
|
|
1915
1915
|
executionCtx,
|
|
@@ -2009,7 +2009,7 @@ var Hono = class _Hono {
|
|
|
2009
2009
|
|
|
2010
2010
|
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
2011
2011
|
var emptyParam = [];
|
|
2012
|
-
function match(method,
|
|
2012
|
+
function match(method, path8) {
|
|
2013
2013
|
const matchers = this.buildAllMatchers();
|
|
2014
2014
|
const match2 = ((method2, path22) => {
|
|
2015
2015
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
|
@@ -2025,7 +2025,7 @@ function match(method, path5) {
|
|
|
2025
2025
|
return [matcher[1][index2], match3];
|
|
2026
2026
|
});
|
|
2027
2027
|
this.match = match2;
|
|
2028
|
-
return match2(method,
|
|
2028
|
+
return match2(method, path8);
|
|
2029
2029
|
}
|
|
2030
2030
|
|
|
2031
2031
|
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
@@ -2140,12 +2140,12 @@ var Node = class _Node {
|
|
|
2140
2140
|
var Trie = class {
|
|
2141
2141
|
#context = { varIndex: 0 };
|
|
2142
2142
|
#root = new Node();
|
|
2143
|
-
insert(
|
|
2143
|
+
insert(path8, index2, pathErrorCheckOnly) {
|
|
2144
2144
|
const paramAssoc = [];
|
|
2145
2145
|
const groups = [];
|
|
2146
2146
|
for (let i = 0; ; ) {
|
|
2147
2147
|
let replaced = false;
|
|
2148
|
-
|
|
2148
|
+
path8 = path8.replace(/\{[^}]+\}/g, (m) => {
|
|
2149
2149
|
const mark = `@\\${i}`;
|
|
2150
2150
|
groups[i] = [mark, m];
|
|
2151
2151
|
i++;
|
|
@@ -2156,7 +2156,7 @@ var Trie = class {
|
|
|
2156
2156
|
break;
|
|
2157
2157
|
}
|
|
2158
2158
|
}
|
|
2159
|
-
const tokens =
|
|
2159
|
+
const tokens = path8.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
|
|
2160
2160
|
for (let i = groups.length - 1; i >= 0; i--) {
|
|
2161
2161
|
const [mark] = groups[i];
|
|
2162
2162
|
for (let j = tokens.length - 1; j >= 0; j--) {
|
|
@@ -2195,9 +2195,9 @@ var Trie = class {
|
|
|
2195
2195
|
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
2196
2196
|
var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
|
|
2197
2197
|
var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
|
|
2198
|
-
function buildWildcardRegExp(
|
|
2199
|
-
return wildcardRegExpCache[
|
|
2200
|
-
|
|
2198
|
+
function buildWildcardRegExp(path8) {
|
|
2199
|
+
return wildcardRegExpCache[path8] ??= new RegExp(
|
|
2200
|
+
path8 === "*" ? "" : `^${path8.replace(
|
|
2201
2201
|
/\/\*$|([.\\+*[^\]$()])/g,
|
|
2202
2202
|
(_, metaChar) => metaChar ? `\\${metaChar}` : "(?:|/.*)"
|
|
2203
2203
|
)}$`
|
|
@@ -2219,17 +2219,17 @@ function buildMatcherFromPreprocessedRoutes(routes) {
|
|
|
2219
2219
|
);
|
|
2220
2220
|
const staticMap = /* @__PURE__ */ Object.create(null);
|
|
2221
2221
|
for (let i = 0, j = -1, len = routesWithStaticPathFlag.length; i < len; i++) {
|
|
2222
|
-
const [pathErrorCheckOnly,
|
|
2222
|
+
const [pathErrorCheckOnly, path8, handlers] = routesWithStaticPathFlag[i];
|
|
2223
2223
|
if (pathErrorCheckOnly) {
|
|
2224
|
-
staticMap[
|
|
2224
|
+
staticMap[path8] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
|
|
2225
2225
|
} else {
|
|
2226
2226
|
j++;
|
|
2227
2227
|
}
|
|
2228
2228
|
let paramAssoc;
|
|
2229
2229
|
try {
|
|
2230
|
-
paramAssoc = trie.insert(
|
|
2230
|
+
paramAssoc = trie.insert(path8, j, pathErrorCheckOnly);
|
|
2231
2231
|
} catch (e) {
|
|
2232
|
-
throw e === PATH_ERROR ? new UnsupportedPathError(
|
|
2232
|
+
throw e === PATH_ERROR ? new UnsupportedPathError(path8) : e;
|
|
2233
2233
|
}
|
|
2234
2234
|
if (pathErrorCheckOnly) {
|
|
2235
2235
|
continue;
|
|
@@ -2263,12 +2263,12 @@ function buildMatcherFromPreprocessedRoutes(routes) {
|
|
|
2263
2263
|
}
|
|
2264
2264
|
return [regexp, handlerMap, staticMap];
|
|
2265
2265
|
}
|
|
2266
|
-
function findMiddleware(middleware,
|
|
2266
|
+
function findMiddleware(middleware, path8) {
|
|
2267
2267
|
if (!middleware) {
|
|
2268
2268
|
return void 0;
|
|
2269
2269
|
}
|
|
2270
2270
|
for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
|
|
2271
|
-
if (buildWildcardRegExp(k).test(
|
|
2271
|
+
if (buildWildcardRegExp(k).test(path8)) {
|
|
2272
2272
|
return [...middleware[k]];
|
|
2273
2273
|
}
|
|
2274
2274
|
}
|
|
@@ -2282,7 +2282,7 @@ var RegExpRouter = class {
|
|
|
2282
2282
|
this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
|
|
2283
2283
|
this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
|
|
2284
2284
|
}
|
|
2285
|
-
add(method,
|
|
2285
|
+
add(method, path8, handler) {
|
|
2286
2286
|
const middleware = this.#middleware;
|
|
2287
2287
|
const routes = this.#routes;
|
|
2288
2288
|
if (!middleware || !routes) {
|
|
@@ -2297,18 +2297,18 @@ var RegExpRouter = class {
|
|
|
2297
2297
|
});
|
|
2298
2298
|
});
|
|
2299
2299
|
}
|
|
2300
|
-
if (
|
|
2301
|
-
|
|
2300
|
+
if (path8 === "/*") {
|
|
2301
|
+
path8 = "*";
|
|
2302
2302
|
}
|
|
2303
|
-
const paramCount = (
|
|
2304
|
-
if (/\*$/.test(
|
|
2305
|
-
const re = buildWildcardRegExp(
|
|
2303
|
+
const paramCount = (path8.match(/\/:/g) || []).length;
|
|
2304
|
+
if (/\*$/.test(path8)) {
|
|
2305
|
+
const re = buildWildcardRegExp(path8);
|
|
2306
2306
|
if (method === METHOD_NAME_ALL) {
|
|
2307
2307
|
Object.keys(middleware).forEach((m) => {
|
|
2308
|
-
middleware[m][
|
|
2308
|
+
middleware[m][path8] ||= findMiddleware(middleware[m], path8) || findMiddleware(middleware[METHOD_NAME_ALL], path8) || [];
|
|
2309
2309
|
});
|
|
2310
2310
|
} else {
|
|
2311
|
-
middleware[method][
|
|
2311
|
+
middleware[method][path8] ||= findMiddleware(middleware[method], path8) || findMiddleware(middleware[METHOD_NAME_ALL], path8) || [];
|
|
2312
2312
|
}
|
|
2313
2313
|
Object.keys(middleware).forEach((m) => {
|
|
2314
2314
|
if (method === METHOD_NAME_ALL || method === m) {
|
|
@@ -2326,7 +2326,7 @@ var RegExpRouter = class {
|
|
|
2326
2326
|
});
|
|
2327
2327
|
return;
|
|
2328
2328
|
}
|
|
2329
|
-
const paths = checkOptionalParameter(
|
|
2329
|
+
const paths = checkOptionalParameter(path8) || [path8];
|
|
2330
2330
|
for (let i = 0, len = paths.length; i < len; i++) {
|
|
2331
2331
|
const path22 = paths[i];
|
|
2332
2332
|
Object.keys(routes).forEach((m) => {
|
|
@@ -2353,13 +2353,13 @@ var RegExpRouter = class {
|
|
|
2353
2353
|
const routes = [];
|
|
2354
2354
|
let hasOwnRoute = method === METHOD_NAME_ALL;
|
|
2355
2355
|
[this.#middleware, this.#routes].forEach((r) => {
|
|
2356
|
-
const ownRoute = r[method] ? Object.keys(r[method]).map((
|
|
2356
|
+
const ownRoute = r[method] ? Object.keys(r[method]).map((path8) => [path8, r[method][path8]]) : [];
|
|
2357
2357
|
if (ownRoute.length !== 0) {
|
|
2358
2358
|
hasOwnRoute ||= true;
|
|
2359
2359
|
routes.push(...ownRoute);
|
|
2360
2360
|
} else if (method !== METHOD_NAME_ALL) {
|
|
2361
2361
|
routes.push(
|
|
2362
|
-
...Object.keys(r[METHOD_NAME_ALL]).map((
|
|
2362
|
+
...Object.keys(r[METHOD_NAME_ALL]).map((path8) => [path8, r[METHOD_NAME_ALL][path8]])
|
|
2363
2363
|
);
|
|
2364
2364
|
}
|
|
2365
2365
|
});
|
|
@@ -2379,13 +2379,13 @@ var SmartRouter = class {
|
|
|
2379
2379
|
constructor(init) {
|
|
2380
2380
|
this.#routers = init.routers;
|
|
2381
2381
|
}
|
|
2382
|
-
add(method,
|
|
2382
|
+
add(method, path8, handler) {
|
|
2383
2383
|
if (!this.#routes) {
|
|
2384
2384
|
throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
|
|
2385
2385
|
}
|
|
2386
|
-
this.#routes.push([method,
|
|
2386
|
+
this.#routes.push([method, path8, handler]);
|
|
2387
2387
|
}
|
|
2388
|
-
match(method,
|
|
2388
|
+
match(method, path8) {
|
|
2389
2389
|
if (!this.#routes) {
|
|
2390
2390
|
throw new Error("Fatal error");
|
|
2391
2391
|
}
|
|
@@ -2400,7 +2400,7 @@ var SmartRouter = class {
|
|
|
2400
2400
|
for (let i2 = 0, len2 = routes.length; i2 < len2; i2++) {
|
|
2401
2401
|
router.add(...routes[i2]);
|
|
2402
2402
|
}
|
|
2403
|
-
res = router.match(method,
|
|
2403
|
+
res = router.match(method, path8);
|
|
2404
2404
|
} catch (e) {
|
|
2405
2405
|
if (e instanceof UnsupportedPathError) {
|
|
2406
2406
|
continue;
|
|
@@ -2450,10 +2450,10 @@ var Node2 = class _Node2 {
|
|
|
2450
2450
|
}
|
|
2451
2451
|
this.#patterns = [];
|
|
2452
2452
|
}
|
|
2453
|
-
insert(method,
|
|
2453
|
+
insert(method, path8, handler) {
|
|
2454
2454
|
this.#order = ++this.#order;
|
|
2455
2455
|
let curNode = this;
|
|
2456
|
-
const parts = splitRoutingPath(
|
|
2456
|
+
const parts = splitRoutingPath(path8);
|
|
2457
2457
|
const possibleKeys = [];
|
|
2458
2458
|
for (let i = 0, len = parts.length; i < len; i++) {
|
|
2459
2459
|
const p = parts[i];
|
|
@@ -2502,12 +2502,12 @@ var Node2 = class _Node2 {
|
|
|
2502
2502
|
}
|
|
2503
2503
|
}
|
|
2504
2504
|
}
|
|
2505
|
-
search(method,
|
|
2505
|
+
search(method, path8) {
|
|
2506
2506
|
const handlerSets = [];
|
|
2507
2507
|
this.#params = emptyParams;
|
|
2508
2508
|
const curNode = this;
|
|
2509
2509
|
let curNodes = [curNode];
|
|
2510
|
-
const parts = splitPath(
|
|
2510
|
+
const parts = splitPath(path8);
|
|
2511
2511
|
const curNodesQueue = [];
|
|
2512
2512
|
const len = parts.length;
|
|
2513
2513
|
let partOffsets = null;
|
|
@@ -2549,13 +2549,13 @@ var Node2 = class _Node2 {
|
|
|
2549
2549
|
if (matcher instanceof RegExp) {
|
|
2550
2550
|
if (partOffsets === null) {
|
|
2551
2551
|
partOffsets = new Array(len);
|
|
2552
|
-
let offset =
|
|
2552
|
+
let offset = path8[0] === "/" ? 1 : 0;
|
|
2553
2553
|
for (let p = 0; p < len; p++) {
|
|
2554
2554
|
partOffsets[p] = offset;
|
|
2555
2555
|
offset += parts[p].length + 1;
|
|
2556
2556
|
}
|
|
2557
2557
|
}
|
|
2558
|
-
const restPathString =
|
|
2558
|
+
const restPathString = path8.substring(partOffsets[i]);
|
|
2559
2559
|
const m = matcher.exec(restPathString);
|
|
2560
2560
|
if (m) {
|
|
2561
2561
|
params[name] = m[0];
|
|
@@ -2608,18 +2608,18 @@ var TrieRouter = class {
|
|
|
2608
2608
|
constructor() {
|
|
2609
2609
|
this.#node = new Node2();
|
|
2610
2610
|
}
|
|
2611
|
-
add(method,
|
|
2612
|
-
const results = checkOptionalParameter(
|
|
2611
|
+
add(method, path8, handler) {
|
|
2612
|
+
const results = checkOptionalParameter(path8);
|
|
2613
2613
|
if (results) {
|
|
2614
2614
|
for (let i = 0, len = results.length; i < len; i++) {
|
|
2615
2615
|
this.#node.insert(method, results[i], handler);
|
|
2616
2616
|
}
|
|
2617
2617
|
return;
|
|
2618
2618
|
}
|
|
2619
|
-
this.#node.insert(method,
|
|
2619
|
+
this.#node.insert(method, path8, handler);
|
|
2620
2620
|
}
|
|
2621
|
-
match(method,
|
|
2622
|
-
return this.#node.search(method,
|
|
2621
|
+
match(method, path8) {
|
|
2622
|
+
return this.#node.search(method, path8);
|
|
2623
2623
|
}
|
|
2624
2624
|
};
|
|
2625
2625
|
|
|
@@ -2638,6 +2638,155 @@ var Hono2 = class extends Hono {
|
|
|
2638
2638
|
}
|
|
2639
2639
|
};
|
|
2640
2640
|
|
|
2641
|
+
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/utils/stream.js
|
|
2642
|
+
var StreamingApi = class {
|
|
2643
|
+
writer;
|
|
2644
|
+
encoder;
|
|
2645
|
+
writable;
|
|
2646
|
+
abortSubscribers = [];
|
|
2647
|
+
responseReadable;
|
|
2648
|
+
/**
|
|
2649
|
+
* Whether the stream has been aborted.
|
|
2650
|
+
*/
|
|
2651
|
+
aborted = false;
|
|
2652
|
+
/**
|
|
2653
|
+
* Whether the stream has been closed normally.
|
|
2654
|
+
*/
|
|
2655
|
+
closed = false;
|
|
2656
|
+
constructor(writable, _readable) {
|
|
2657
|
+
this.writable = writable;
|
|
2658
|
+
this.writer = writable.getWriter();
|
|
2659
|
+
this.encoder = new TextEncoder();
|
|
2660
|
+
const reader = _readable.getReader();
|
|
2661
|
+
this.abortSubscribers.push(async () => {
|
|
2662
|
+
await reader.cancel();
|
|
2663
|
+
});
|
|
2664
|
+
this.responseReadable = new ReadableStream({
|
|
2665
|
+
async pull(controller) {
|
|
2666
|
+
const { done, value } = await reader.read();
|
|
2667
|
+
done ? controller.close() : controller.enqueue(value);
|
|
2668
|
+
},
|
|
2669
|
+
cancel: () => {
|
|
2670
|
+
this.abort();
|
|
2671
|
+
}
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
async write(input) {
|
|
2675
|
+
try {
|
|
2676
|
+
if (typeof input === "string") {
|
|
2677
|
+
input = this.encoder.encode(input);
|
|
2678
|
+
}
|
|
2679
|
+
await this.writer.write(input);
|
|
2680
|
+
} catch {
|
|
2681
|
+
}
|
|
2682
|
+
return this;
|
|
2683
|
+
}
|
|
2684
|
+
async writeln(input) {
|
|
2685
|
+
await this.write(input + "\n");
|
|
2686
|
+
return this;
|
|
2687
|
+
}
|
|
2688
|
+
sleep(ms) {
|
|
2689
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
2690
|
+
}
|
|
2691
|
+
async close() {
|
|
2692
|
+
try {
|
|
2693
|
+
await this.writer.close();
|
|
2694
|
+
} catch {
|
|
2695
|
+
}
|
|
2696
|
+
this.closed = true;
|
|
2697
|
+
}
|
|
2698
|
+
async pipe(body) {
|
|
2699
|
+
this.writer.releaseLock();
|
|
2700
|
+
await body.pipeTo(this.writable, { preventClose: true });
|
|
2701
|
+
this.writer = this.writable.getWriter();
|
|
2702
|
+
}
|
|
2703
|
+
onAbort(listener) {
|
|
2704
|
+
this.abortSubscribers.push(listener);
|
|
2705
|
+
}
|
|
2706
|
+
/**
|
|
2707
|
+
* Abort the stream.
|
|
2708
|
+
* You can call this method when stream is aborted by external event.
|
|
2709
|
+
*/
|
|
2710
|
+
abort() {
|
|
2711
|
+
if (!this.aborted) {
|
|
2712
|
+
this.aborted = true;
|
|
2713
|
+
this.abortSubscribers.forEach((subscriber) => subscriber());
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
};
|
|
2717
|
+
|
|
2718
|
+
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/helper/streaming/utils.js
|
|
2719
|
+
var isOldBunVersion = () => {
|
|
2720
|
+
const version2 = typeof Bun !== "undefined" ? Bun.version : void 0;
|
|
2721
|
+
if (version2 === void 0) {
|
|
2722
|
+
return false;
|
|
2723
|
+
}
|
|
2724
|
+
const result = version2.startsWith("1.1") || version2.startsWith("1.0") || version2.startsWith("0.");
|
|
2725
|
+
isOldBunVersion = () => result;
|
|
2726
|
+
return result;
|
|
2727
|
+
};
|
|
2728
|
+
|
|
2729
|
+
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/helper/streaming/sse.js
|
|
2730
|
+
var SSEStreamingApi = class extends StreamingApi {
|
|
2731
|
+
constructor(writable, readable) {
|
|
2732
|
+
super(writable, readable);
|
|
2733
|
+
}
|
|
2734
|
+
async writeSSE(message) {
|
|
2735
|
+
const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
2736
|
+
const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
|
|
2737
|
+
return `data: ${line}`;
|
|
2738
|
+
}).join("\n");
|
|
2739
|
+
for (const key of ["event", "id", "retry"]) {
|
|
2740
|
+
if (message[key] && /[\r\n]/.test(message[key])) {
|
|
2741
|
+
throw new Error(`${key} must not contain "\\r" or "\\n"`);
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
const sseData = [
|
|
2745
|
+
message.event && `event: ${message.event}`,
|
|
2746
|
+
dataLines,
|
|
2747
|
+
message.id && `id: ${message.id}`,
|
|
2748
|
+
message.retry && `retry: ${message.retry}`
|
|
2749
|
+
].filter(Boolean).join("\n") + "\n\n";
|
|
2750
|
+
await this.write(sseData);
|
|
2751
|
+
}
|
|
2752
|
+
};
|
|
2753
|
+
var run = async (stream2, cb, onError) => {
|
|
2754
|
+
try {
|
|
2755
|
+
await cb(stream2);
|
|
2756
|
+
} catch (e) {
|
|
2757
|
+
if (e instanceof Error && onError) {
|
|
2758
|
+
await onError(e, stream2);
|
|
2759
|
+
await stream2.writeSSE({
|
|
2760
|
+
event: "error",
|
|
2761
|
+
data: e.message
|
|
2762
|
+
});
|
|
2763
|
+
} else {
|
|
2764
|
+
console.error(e);
|
|
2765
|
+
}
|
|
2766
|
+
} finally {
|
|
2767
|
+
stream2.close();
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
var contextStash = /* @__PURE__ */ new WeakMap();
|
|
2771
|
+
var streamSSE = (c, cb, onError) => {
|
|
2772
|
+
const { readable, writable } = new TransformStream();
|
|
2773
|
+
const stream2 = new SSEStreamingApi(writable, readable);
|
|
2774
|
+
if (isOldBunVersion()) {
|
|
2775
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
2776
|
+
if (!stream2.closed) {
|
|
2777
|
+
stream2.abort();
|
|
2778
|
+
}
|
|
2779
|
+
});
|
|
2780
|
+
}
|
|
2781
|
+
contextStash.set(stream2.responseReadable, c);
|
|
2782
|
+
c.header("Transfer-Encoding", "chunked");
|
|
2783
|
+
c.header("Content-Type", "text/event-stream");
|
|
2784
|
+
c.header("Cache-Control", "no-cache");
|
|
2785
|
+
c.header("Connection", "keep-alive");
|
|
2786
|
+
run(stream2, cb, onError);
|
|
2787
|
+
return c.newResponse(stream2.responseReadable);
|
|
2788
|
+
};
|
|
2789
|
+
|
|
2641
2790
|
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/utils/mime.js
|
|
2642
2791
|
var getMimeType = (filename, mimes = baseMimes) => {
|
|
2643
2792
|
const regexp = /\.([a-zA-Z0-9]+?)$/;
|
|
@@ -2728,32 +2877,32 @@ var pr54206Applied = () => {
|
|
|
2728
2877
|
return major >= 23 || major === 22 && minor >= 7 || major === 20 && minor >= 18;
|
|
2729
2878
|
};
|
|
2730
2879
|
var useReadableToWeb = pr54206Applied();
|
|
2731
|
-
var createStreamBody = (
|
|
2880
|
+
var createStreamBody = (stream2) => {
|
|
2732
2881
|
if (useReadableToWeb) {
|
|
2733
|
-
return Readable2.toWeb(
|
|
2882
|
+
return Readable2.toWeb(stream2);
|
|
2734
2883
|
}
|
|
2735
2884
|
const body = new ReadableStream({
|
|
2736
2885
|
start(controller) {
|
|
2737
|
-
|
|
2886
|
+
stream2.on("data", (chunk) => {
|
|
2738
2887
|
controller.enqueue(chunk);
|
|
2739
2888
|
});
|
|
2740
|
-
|
|
2889
|
+
stream2.on("error", (err) => {
|
|
2741
2890
|
controller.error(err);
|
|
2742
2891
|
});
|
|
2743
|
-
|
|
2892
|
+
stream2.on("end", () => {
|
|
2744
2893
|
controller.close();
|
|
2745
2894
|
});
|
|
2746
2895
|
},
|
|
2747
2896
|
cancel() {
|
|
2748
|
-
|
|
2897
|
+
stream2.destroy();
|
|
2749
2898
|
}
|
|
2750
2899
|
});
|
|
2751
2900
|
return body;
|
|
2752
2901
|
};
|
|
2753
|
-
var getStats = (
|
|
2902
|
+
var getStats = (path8) => {
|
|
2754
2903
|
let stats;
|
|
2755
2904
|
try {
|
|
2756
|
-
stats = statSync(
|
|
2905
|
+
stats = statSync(path8);
|
|
2757
2906
|
} catch {
|
|
2758
2907
|
}
|
|
2759
2908
|
return stats;
|
|
@@ -2796,21 +2945,21 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2796
2945
|
return next();
|
|
2797
2946
|
}
|
|
2798
2947
|
}
|
|
2799
|
-
let
|
|
2948
|
+
let path8 = join(
|
|
2800
2949
|
root,
|
|
2801
2950
|
!optionPath && options.rewriteRequestPath ? options.rewriteRequestPath(filename, c) : filename
|
|
2802
2951
|
);
|
|
2803
|
-
let stats = getStats(
|
|
2952
|
+
let stats = getStats(path8);
|
|
2804
2953
|
if (stats && stats.isDirectory()) {
|
|
2805
2954
|
const indexFile = options.index ?? "index.html";
|
|
2806
|
-
|
|
2807
|
-
stats = getStats(
|
|
2955
|
+
path8 = join(path8, indexFile);
|
|
2956
|
+
stats = getStats(path8);
|
|
2808
2957
|
}
|
|
2809
2958
|
if (!stats) {
|
|
2810
|
-
await options.onNotFound?.(
|
|
2959
|
+
await options.onNotFound?.(path8, c);
|
|
2811
2960
|
return next();
|
|
2812
2961
|
}
|
|
2813
|
-
const mimeType = getMimeType(
|
|
2962
|
+
const mimeType = getMimeType(path8);
|
|
2814
2963
|
c.header("Content-Type", mimeType || "application/octet-stream");
|
|
2815
2964
|
if (options.precompressed && (!mimeType || COMPRESSIBLE_CONTENT_TYPE_REGEX.test(mimeType))) {
|
|
2816
2965
|
const acceptEncodingSet = new Set(
|
|
@@ -2820,12 +2969,12 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2820
2969
|
if (!acceptEncodingSet.has(encoding)) {
|
|
2821
2970
|
continue;
|
|
2822
2971
|
}
|
|
2823
|
-
const precompressedStats = getStats(
|
|
2972
|
+
const precompressedStats = getStats(path8 + ENCODINGS[encoding]);
|
|
2824
2973
|
if (precompressedStats) {
|
|
2825
2974
|
c.header("Content-Encoding", encoding);
|
|
2826
2975
|
c.header("Vary", "Accept-Encoding", { append: true });
|
|
2827
2976
|
stats = precompressedStats;
|
|
2828
|
-
|
|
2977
|
+
path8 = path8 + ENCODINGS[encoding];
|
|
2829
2978
|
break;
|
|
2830
2979
|
}
|
|
2831
2980
|
}
|
|
@@ -2839,7 +2988,7 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2839
2988
|
result = c.body(null);
|
|
2840
2989
|
} else if (!range) {
|
|
2841
2990
|
c.header("Content-Length", size.toString());
|
|
2842
|
-
result = c.body(createStreamBody(createReadStream(
|
|
2991
|
+
result = c.body(createStreamBody(createReadStream(path8)), 200);
|
|
2843
2992
|
} else {
|
|
2844
2993
|
c.header("Accept-Ranges", "bytes");
|
|
2845
2994
|
c.header("Date", stats.birthtime.toUTCString());
|
|
@@ -2850,12 +2999,12 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
2850
2999
|
end = size - 1;
|
|
2851
3000
|
}
|
|
2852
3001
|
const chunksize = end - start + 1;
|
|
2853
|
-
const
|
|
3002
|
+
const stream2 = createReadStream(path8, { start, end });
|
|
2854
3003
|
c.header("Content-Length", chunksize.toString());
|
|
2855
3004
|
c.header("Content-Range", `bytes ${start}-${end}/${stats.size}`);
|
|
2856
|
-
result = c.body(createStreamBody(
|
|
3005
|
+
result = c.body(createStreamBody(stream2), 206);
|
|
2857
3006
|
}
|
|
2858
|
-
await options.onFound?.(
|
|
3007
|
+
await options.onFound?.(path8, c);
|
|
2859
3008
|
return result;
|
|
2860
3009
|
};
|
|
2861
3010
|
};
|
|
@@ -2924,6 +3073,154 @@ function loadChatVisibility(metadata2, opts) {
|
|
|
2924
3073
|
};
|
|
2925
3074
|
}
|
|
2926
3075
|
|
|
3076
|
+
// src/title.ts
|
|
3077
|
+
function deriveBaseTitle(firstUserText) {
|
|
3078
|
+
const text2 = firstUserText?.trim().split("\n")[0]?.trim();
|
|
3079
|
+
return text2 && text2.length > 0 ? text2 : "Untitled";
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
// src/list-pagination.ts
|
|
3083
|
+
import fs from "fs";
|
|
3084
|
+
import path from "path";
|
|
3085
|
+
import Database from "better-sqlite3";
|
|
3086
|
+
|
|
3087
|
+
// src/list-filter.ts
|
|
3088
|
+
function buildFilterClauses({
|
|
3089
|
+
projects,
|
|
3090
|
+
tags: tags3,
|
|
3091
|
+
hasMetadata
|
|
3092
|
+
}) {
|
|
3093
|
+
const clauses = [];
|
|
3094
|
+
const params = [];
|
|
3095
|
+
if (projects && projects.length > 0) {
|
|
3096
|
+
const placeholders = projects.map(() => "?").join(", ");
|
|
3097
|
+
clauses.push(`coalesce(c.project, '') IN (${placeholders})`);
|
|
3098
|
+
params.push(...projects);
|
|
3099
|
+
}
|
|
3100
|
+
if (tags3 && tags3.length > 0) {
|
|
3101
|
+
const realTagIds = tags3.filter((t) => t !== "");
|
|
3102
|
+
const wantUntagged = tags3.includes("");
|
|
3103
|
+
if (realTagIds.length > 0) {
|
|
3104
|
+
if (!hasMetadata) {
|
|
3105
|
+
clauses.push("0");
|
|
3106
|
+
} else {
|
|
3107
|
+
const placeholders = realTagIds.map(() => "?").join(", ");
|
|
3108
|
+
clauses.push(
|
|
3109
|
+
`c.id IN (SELECT chat_id FROM meta.chat_tags
|
|
3110
|
+
WHERE tag_id IN (${placeholders})
|
|
3111
|
+
GROUP BY chat_id HAVING count(*) = ?)`
|
|
3112
|
+
);
|
|
3113
|
+
params.push(...realTagIds, realTagIds.length);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
if (wantUntagged && hasMetadata) {
|
|
3117
|
+
clauses.push("c.id NOT IN (SELECT chat_id FROM meta.chat_tags)");
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
return { clauses, params };
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
// src/list-pagination.ts
|
|
3124
|
+
var ARCHIVE_DB = "archive.db";
|
|
3125
|
+
var METADATA_DB = "metadata.db";
|
|
3126
|
+
var SORT_EXPR = {
|
|
3127
|
+
createdAt: "c.created_at",
|
|
3128
|
+
updatedAt: "c.updated_at",
|
|
3129
|
+
deletedAt: "m.deleted_at",
|
|
3130
|
+
// The Title axis orders by the precomputed collation key on the ATTACHed
|
|
3131
|
+
// `meta.chat_sort_keys` (alias `k`), reached through the INNER JOIN added
|
|
3132
|
+
// below (ADR-0019). BINARY compare over `sort_key` is an index range scan.
|
|
3133
|
+
title: "k.sort_key"
|
|
3134
|
+
};
|
|
3135
|
+
function encodeCursor(cursor) {
|
|
3136
|
+
return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
|
|
3137
|
+
}
|
|
3138
|
+
function decodeCursor(token) {
|
|
3139
|
+
try {
|
|
3140
|
+
const parsed = JSON.parse(
|
|
3141
|
+
Buffer.from(token, "base64url").toString("utf8")
|
|
3142
|
+
);
|
|
3143
|
+
const sortKey = parsed?.sortKey;
|
|
3144
|
+
if (parsed && typeof parsed === "object" && (typeof sortKey === "number" || typeof sortKey === "string") && typeof parsed.id === "string") {
|
|
3145
|
+
return parsed;
|
|
3146
|
+
}
|
|
3147
|
+
return null;
|
|
3148
|
+
} catch {
|
|
3149
|
+
return null;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function createChatPageQuery({
|
|
3153
|
+
dataDir: dataDir2
|
|
3154
|
+
}) {
|
|
3155
|
+
const archive2 = new Database(path.join(dataDir2, ARCHIVE_DB), {
|
|
3156
|
+
readonly: true
|
|
3157
|
+
});
|
|
3158
|
+
const metadataPath = path.join(dataDir2, METADATA_DB);
|
|
3159
|
+
const hasMetadata = fs.existsSync(metadataPath);
|
|
3160
|
+
if (hasMetadata) {
|
|
3161
|
+
archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
|
|
3162
|
+
}
|
|
3163
|
+
function queryPage(query) {
|
|
3164
|
+
const sortExpr = SORT_EXPR[query.sort];
|
|
3165
|
+
const needsDeletedJoin = query.sort === "deletedAt";
|
|
3166
|
+
const needsSortKeyJoin = query.sort === "title";
|
|
3167
|
+
if ((needsDeletedJoin || needsSortKeyJoin) && !hasMetadata) {
|
|
3168
|
+
return { items: [], nextCursor: null };
|
|
3169
|
+
}
|
|
3170
|
+
let from = "chats c";
|
|
3171
|
+
if (needsDeletedJoin) from += " JOIN meta.chats_meta m ON m.id = c.id";
|
|
3172
|
+
if (needsSortKeyJoin) from += " JOIN meta.chat_sort_keys k ON k.id = c.id";
|
|
3173
|
+
const idCol = needsDeletedJoin ? "m.id" : needsSortKeyJoin ? "k.id" : "c.id";
|
|
3174
|
+
const clauses = [];
|
|
3175
|
+
const params = [];
|
|
3176
|
+
if (needsDeletedJoin) {
|
|
3177
|
+
clauses.push("m.deleted_at IS NOT NULL");
|
|
3178
|
+
} else if (query.trashedOnly) {
|
|
3179
|
+
if (!hasMetadata) return { items: [], nextCursor: null };
|
|
3180
|
+
clauses.push(
|
|
3181
|
+
"c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
3182
|
+
);
|
|
3183
|
+
} else if (!query.includeTrashed && hasMetadata) {
|
|
3184
|
+
clauses.push(
|
|
3185
|
+
"c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
const filter = buildFilterClauses({
|
|
3189
|
+
projects: query.projects,
|
|
3190
|
+
tags: query.tags,
|
|
3191
|
+
hasMetadata
|
|
3192
|
+
});
|
|
3193
|
+
clauses.push(...filter.clauses);
|
|
3194
|
+
params.push(...filter.params);
|
|
3195
|
+
const direction = query.direction ?? "desc";
|
|
3196
|
+
const cmp = direction === "asc" ? ">" : "<";
|
|
3197
|
+
const order = direction === "asc" ? "ASC" : "DESC";
|
|
3198
|
+
if (query.cursor) {
|
|
3199
|
+
clauses.push(
|
|
3200
|
+
`(${sortExpr} ${cmp} ? OR (${sortExpr} = ? AND ${idCol} ${cmp} ?))`
|
|
3201
|
+
);
|
|
3202
|
+
params.push(query.cursor.sortKey, query.cursor.sortKey, query.cursor.id);
|
|
3203
|
+
}
|
|
3204
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
3205
|
+
const rows = archive2.prepare(
|
|
3206
|
+
`SELECT c.id AS id, ${sortExpr} AS sortKey
|
|
3207
|
+
FROM ${from} ${where}
|
|
3208
|
+
ORDER BY ${sortExpr} ${order}, ${idCol} ${order}
|
|
3209
|
+
LIMIT ?`
|
|
3210
|
+
).all(...params, query.limit + 1);
|
|
3211
|
+
const items = rows.slice(0, query.limit);
|
|
3212
|
+
const last = items[items.length - 1];
|
|
3213
|
+
const nextCursor = rows.length > query.limit && last ? { sortKey: last.sortKey, id: last.id } : null;
|
|
3214
|
+
return { items, nextCursor };
|
|
3215
|
+
}
|
|
3216
|
+
return {
|
|
3217
|
+
queryPage,
|
|
3218
|
+
close() {
|
|
3219
|
+
archive2.close();
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
|
|
2927
3224
|
// src/chat-reader.ts
|
|
2928
3225
|
function toApiBlock(block) {
|
|
2929
3226
|
if (block.type === "tool_result") {
|
|
@@ -2938,7 +3235,9 @@ function toApiBlock(block) {
|
|
|
2938
3235
|
function createChatReader({
|
|
2939
3236
|
archive: archive2,
|
|
2940
3237
|
metadata: metadata2,
|
|
2941
|
-
tags: tags3
|
|
3238
|
+
tags: tags3,
|
|
3239
|
+
pageQuery: pageQuery2,
|
|
3240
|
+
countsQuery: countsQuery2
|
|
2942
3241
|
}) {
|
|
2943
3242
|
function findChat(id) {
|
|
2944
3243
|
const code = parseChatId(id);
|
|
@@ -2947,8 +3246,7 @@ function createChatReader({
|
|
|
2947
3246
|
}
|
|
2948
3247
|
function deriveTitle(customTitle, firstUserText) {
|
|
2949
3248
|
if (customTitle && customTitle.trim()) return customTitle;
|
|
2950
|
-
|
|
2951
|
-
return text2 && text2.length > 0 ? text2 : "Untitled";
|
|
3249
|
+
return deriveBaseTitle(firstUserText);
|
|
2952
3250
|
}
|
|
2953
3251
|
function key(agent, sourceId) {
|
|
2954
3252
|
return [agent, sourceId].join("\0");
|
|
@@ -2972,22 +3270,28 @@ function createChatReader({
|
|
|
2972
3270
|
return true;
|
|
2973
3271
|
};
|
|
2974
3272
|
}
|
|
3273
|
+
const toResponse = loadHydration();
|
|
3274
|
+
const chats2 = [];
|
|
3275
|
+
for (const row of rows) {
|
|
3276
|
+
if (!visibility.isVisible(row.id)) continue;
|
|
3277
|
+
if (passesTagFilter && !passesTagFilter(row.id)) continue;
|
|
3278
|
+
chats2.push(toResponse(row, visibility));
|
|
3279
|
+
}
|
|
3280
|
+
return chats2;
|
|
3281
|
+
}
|
|
3282
|
+
function loadHydration(scope) {
|
|
2975
3283
|
const tsRangeByKey = new Map(
|
|
2976
|
-
archive2.read.listChatTsRanges().map((r) => [key(r.agent, r.sourceId), r])
|
|
3284
|
+
archive2.read.listChatTsRanges(scope).map((r) => [key(r.agent, r.sourceId), r])
|
|
2977
3285
|
);
|
|
2978
3286
|
const sourcePathByKey = new Map(
|
|
2979
|
-
archive2.read.listLatestRawSourcePaths().map((r) => [key(r.agent, r.sourceId), r.sourcePath])
|
|
3287
|
+
archive2.read.listLatestRawSourcePaths(scope).map((r) => [key(r.agent, r.sourceId), r.sourcePath])
|
|
2980
3288
|
);
|
|
2981
3289
|
const firstUserTextByKey = new Map(
|
|
2982
|
-
archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
|
|
3290
|
+
archive2.read.listFirstUserTexts(scope).map((r) => [key(r.agent, r.sourceId), r.text])
|
|
2983
3291
|
);
|
|
2984
3292
|
const customTitleById = metadata2.listCustomTitles();
|
|
2985
3293
|
const tagsByChatId = tags3.listTagsByChat();
|
|
2986
|
-
|
|
2987
|
-
for (const row of rows) {
|
|
2988
|
-
if (!visibility.isVisible(row.id)) continue;
|
|
2989
|
-
if (passesTagFilter && !passesTagFilter(row.id)) continue;
|
|
2990
|
-
const isDeleted = visibility.isTrashed(row.id);
|
|
3294
|
+
return (row, visibility) => {
|
|
2991
3295
|
const rowKey = key(row.agent, row.sourceId);
|
|
2992
3296
|
const tsRange = tsRangeByKey.get(rowKey);
|
|
2993
3297
|
const firstSeenAtMs = row.firstSeenAt.getTime();
|
|
@@ -3007,10 +3311,70 @@ function createChatReader({
|
|
|
3007
3311
|
deletedAt: visibility.deletedAt(row.id),
|
|
3008
3312
|
tags: tagsByChatId.get(row.id) ?? []
|
|
3009
3313
|
};
|
|
3010
|
-
if (
|
|
3011
|
-
|
|
3314
|
+
if (visibility.isTrashed(row.id)) chat.isDeleted = true;
|
|
3315
|
+
return chat;
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
function listChatsPage({
|
|
3319
|
+
sort,
|
|
3320
|
+
direction,
|
|
3321
|
+
limit,
|
|
3322
|
+
cursor,
|
|
3323
|
+
includeTrashed = false,
|
|
3324
|
+
trashedOnly = false,
|
|
3325
|
+
projects,
|
|
3326
|
+
tags: tagSelection
|
|
3327
|
+
}) {
|
|
3328
|
+
if (!pageQuery2) {
|
|
3329
|
+
throw new Error("listChatsPage requires a pageQuery dependency");
|
|
3330
|
+
}
|
|
3331
|
+
const decoded = cursor ? decodeCursor(cursor) ?? void 0 : void 0;
|
|
3332
|
+
const page = pageQuery2.queryPage({
|
|
3333
|
+
sort,
|
|
3334
|
+
direction,
|
|
3335
|
+
limit,
|
|
3336
|
+
cursor: decoded,
|
|
3337
|
+
includeTrashed,
|
|
3338
|
+
trashedOnly,
|
|
3339
|
+
projects,
|
|
3340
|
+
tags: tagSelection
|
|
3341
|
+
});
|
|
3342
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
3343
|
+
includeTrashed: includeTrashed || trashedOnly
|
|
3344
|
+
});
|
|
3345
|
+
const pageIds = page.items.map((item) => item.id);
|
|
3346
|
+
const rows = archive2.read.listChatRowsByIds(pageIds);
|
|
3347
|
+
const rowById = new Map(rows.map((r) => [r.id, r]));
|
|
3348
|
+
const toResponse = loadHydration({
|
|
3349
|
+
sourceIds: rows.map((r) => r.sourceId)
|
|
3350
|
+
});
|
|
3351
|
+
const chats2 = [];
|
|
3352
|
+
for (const item of page.items) {
|
|
3353
|
+
const row = rowById.get(item.id);
|
|
3354
|
+
if (row) chats2.push(toResponse(row, visibility));
|
|
3012
3355
|
}
|
|
3013
|
-
return
|
|
3356
|
+
return {
|
|
3357
|
+
chats: chats2,
|
|
3358
|
+
nextCursor: page.nextCursor ? encodeCursor(page.nextCursor) : null
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
function listCounts({
|
|
3362
|
+
includeTrashed = false
|
|
3363
|
+
}) {
|
|
3364
|
+
if (!countsQuery2) {
|
|
3365
|
+
throw new Error("listCounts requires a countsQuery dependency");
|
|
3366
|
+
}
|
|
3367
|
+
return countsQuery2.queryCounts({ includeTrashed });
|
|
3368
|
+
}
|
|
3369
|
+
function listFilteredTotal({
|
|
3370
|
+
includeTrashed = false,
|
|
3371
|
+
projects,
|
|
3372
|
+
tags: tags4
|
|
3373
|
+
}) {
|
|
3374
|
+
if (!countsQuery2) {
|
|
3375
|
+
throw new Error("listFilteredTotal requires a countsQuery dependency");
|
|
3376
|
+
}
|
|
3377
|
+
return countsQuery2.queryFilteredTotal({ includeTrashed, projects, tags: tags4 });
|
|
3014
3378
|
}
|
|
3015
3379
|
function getMessages(id, { includeTrashed }) {
|
|
3016
3380
|
const row = findChat(id);
|
|
@@ -3024,24 +3388,122 @@ function createChatReader({
|
|
|
3024
3388
|
timestamp: m.ts.toISOString()
|
|
3025
3389
|
}));
|
|
3026
3390
|
}
|
|
3027
|
-
return {
|
|
3391
|
+
return {
|
|
3392
|
+
listChats,
|
|
3393
|
+
listChatsPage,
|
|
3394
|
+
listCounts,
|
|
3395
|
+
listFilteredTotal,
|
|
3396
|
+
getMessages,
|
|
3397
|
+
findChat
|
|
3398
|
+
};
|
|
3028
3399
|
}
|
|
3029
3400
|
|
|
3401
|
+
// src/list-contract.ts
|
|
3402
|
+
var MAX_PAGE_LIMIT = 200;
|
|
3403
|
+
|
|
3030
3404
|
// src/app.ts
|
|
3031
|
-
|
|
3405
|
+
var STREAM_HEARTBEAT_MS = 25e3;
|
|
3406
|
+
function createApp({
|
|
3407
|
+
archive: archive2,
|
|
3408
|
+
metadata: metadata2,
|
|
3409
|
+
tags: tags3,
|
|
3410
|
+
pageQuery: pageQuery2,
|
|
3411
|
+
countsQuery: countsQuery2,
|
|
3412
|
+
listEvents: listEvents2,
|
|
3413
|
+
webDistDir: webDistDir2
|
|
3414
|
+
}) {
|
|
3032
3415
|
const app2 = new Hono2();
|
|
3033
|
-
const reader = createChatReader({
|
|
3416
|
+
const reader = createChatReader({
|
|
3417
|
+
archive: archive2,
|
|
3418
|
+
metadata: metadata2,
|
|
3419
|
+
tags: tags3,
|
|
3420
|
+
pageQuery: pageQuery2,
|
|
3421
|
+
countsQuery: countsQuery2
|
|
3422
|
+
});
|
|
3034
3423
|
app2.get("/api/chats", (c) => {
|
|
3424
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3425
|
+
const limitParam = c.req.query("limit");
|
|
3426
|
+
if (limitParam !== void 0) {
|
|
3427
|
+
if (!pageQuery2) {
|
|
3428
|
+
return c.json({ error: "Pagination is not available" }, 501);
|
|
3429
|
+
}
|
|
3430
|
+
const limit = Number.parseInt(limitParam, 10);
|
|
3431
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_PAGE_LIMIT) {
|
|
3432
|
+
return c.json({ error: "Invalid limit" }, 400);
|
|
3433
|
+
}
|
|
3434
|
+
const sort = c.req.query("sort") ?? "updatedAt";
|
|
3435
|
+
if (sort !== "createdAt" && sort !== "updatedAt" && sort !== "deletedAt" && sort !== "title") {
|
|
3436
|
+
return c.json({ error: "Invalid sort" }, 400);
|
|
3437
|
+
}
|
|
3438
|
+
const direction = c.req.query("direction") ?? "desc";
|
|
3439
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
3440
|
+
return c.json({ error: "Invalid direction" }, 400);
|
|
3441
|
+
}
|
|
3442
|
+
const trashedOnly = c.req.query("trashedOnly") === "true";
|
|
3443
|
+
const projects2 = c.req.queries("project");
|
|
3444
|
+
const tagsParam2 = c.req.query("tags");
|
|
3445
|
+
const tagSelection = tagsParam2 === void 0 ? void 0 : tagsParam2.split(",");
|
|
3446
|
+
const { chats: chats3, nextCursor } = reader.listChatsPage({
|
|
3447
|
+
sort,
|
|
3448
|
+
direction,
|
|
3449
|
+
limit,
|
|
3450
|
+
cursor: c.req.query("cursor"),
|
|
3451
|
+
includeTrashed,
|
|
3452
|
+
trashedOnly,
|
|
3453
|
+
projects: projects2,
|
|
3454
|
+
tags: tagSelection
|
|
3455
|
+
});
|
|
3456
|
+
return c.json({ chats: chats3, nextCursor });
|
|
3457
|
+
}
|
|
3035
3458
|
const projects = c.req.queries("project");
|
|
3036
3459
|
const tagsParam = c.req.query("tags");
|
|
3037
3460
|
const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
|
|
3038
3461
|
const chats2 = reader.listChats({
|
|
3039
|
-
includeTrashed
|
|
3462
|
+
includeTrashed,
|
|
3040
3463
|
projects,
|
|
3041
3464
|
tags: tags4
|
|
3042
3465
|
});
|
|
3043
3466
|
return c.json({ chats: chats2 });
|
|
3044
3467
|
});
|
|
3468
|
+
app2.get("/api/chats/stream", (c) => {
|
|
3469
|
+
if (!listEvents2) {
|
|
3470
|
+
return c.json({ error: "Live updates are not available" }, 501);
|
|
3471
|
+
}
|
|
3472
|
+
const hub = listEvents2;
|
|
3473
|
+
return streamSSE(c, async (stream2) => {
|
|
3474
|
+
const unsubscribe = hub.subscribe((event) => {
|
|
3475
|
+
void stream2.writeSSE({ event: event.type, data: "" });
|
|
3476
|
+
});
|
|
3477
|
+
await new Promise((resolve3) => {
|
|
3478
|
+
const heartbeat = setInterval(() => {
|
|
3479
|
+
void stream2.writeSSE({ event: "ping", data: "" });
|
|
3480
|
+
}, STREAM_HEARTBEAT_MS);
|
|
3481
|
+
stream2.onAbort(() => {
|
|
3482
|
+
clearInterval(heartbeat);
|
|
3483
|
+
unsubscribe();
|
|
3484
|
+
resolve3();
|
|
3485
|
+
});
|
|
3486
|
+
});
|
|
3487
|
+
});
|
|
3488
|
+
});
|
|
3489
|
+
app2.get("/api/chats/counts", (c) => {
|
|
3490
|
+
if (!countsQuery2) {
|
|
3491
|
+
return c.json({ error: "Counts are not available" }, 501);
|
|
3492
|
+
}
|
|
3493
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3494
|
+
return c.json(reader.listCounts({ includeTrashed }));
|
|
3495
|
+
});
|
|
3496
|
+
app2.get("/api/chats/list-total", (c) => {
|
|
3497
|
+
if (!countsQuery2) {
|
|
3498
|
+
return c.json({ error: "Counts are not available" }, 501);
|
|
3499
|
+
}
|
|
3500
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3501
|
+
const projects = c.req.queries("project");
|
|
3502
|
+
const tagsParam = c.req.query("tags");
|
|
3503
|
+
const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
|
|
3504
|
+
const total = reader.listFilteredTotal({ includeTrashed, projects, tags: tags4 });
|
|
3505
|
+
return c.json({ total });
|
|
3506
|
+
});
|
|
3045
3507
|
app2.delete("/api/chats/:id", (c) => {
|
|
3046
3508
|
const row = reader.findChat(c.req.param("id"));
|
|
3047
3509
|
if (!row) {
|
|
@@ -4491,7 +4953,7 @@ var QueryPromise = class {
|
|
|
4491
4953
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4492
4954
|
const nullifyMap = {};
|
|
4493
4955
|
const result = columns.reduce(
|
|
4494
|
-
(result2, { path:
|
|
4956
|
+
(result2, { path: path8, field }, columnIndex) => {
|
|
4495
4957
|
let decoder;
|
|
4496
4958
|
if (is(field, Column)) {
|
|
4497
4959
|
decoder = field;
|
|
@@ -4503,8 +4965,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4503
4965
|
decoder = field.sql.decoder;
|
|
4504
4966
|
}
|
|
4505
4967
|
let node = result2;
|
|
4506
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4507
|
-
if (pathChunkIndex <
|
|
4968
|
+
for (const [pathChunkIndex, pathChunk] of path8.entries()) {
|
|
4969
|
+
if (pathChunkIndex < path8.length - 1) {
|
|
4508
4970
|
if (!(pathChunk in node)) {
|
|
4509
4971
|
node[pathChunk] = {};
|
|
4510
4972
|
}
|
|
@@ -4512,8 +4974,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4512
4974
|
} else {
|
|
4513
4975
|
const rawValue = row[columnIndex];
|
|
4514
4976
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4515
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4516
|
-
const objectName =
|
|
4977
|
+
if (joinsNotNullableMap && is(field, Column) && path8.length === 2) {
|
|
4978
|
+
const objectName = path8[0];
|
|
4517
4979
|
if (!(objectName in nullifyMap)) {
|
|
4518
4980
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4519
4981
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -5073,10 +5535,10 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
5073
5535
|
}
|
|
5074
5536
|
|
|
5075
5537
|
// src/storage/openStore.ts
|
|
5076
|
-
import
|
|
5077
|
-
import
|
|
5538
|
+
import fs3 from "fs";
|
|
5539
|
+
import path2 from "path";
|
|
5078
5540
|
import { fileURLToPath } from "url";
|
|
5079
|
-
import
|
|
5541
|
+
import Database2 from "better-sqlite3";
|
|
5080
5542
|
|
|
5081
5543
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/better-sqlite3/driver.js
|
|
5082
5544
|
import Client from "better-sqlite3";
|
|
@@ -8683,20 +9145,20 @@ function drizzle(...params) {
|
|
|
8683
9145
|
|
|
8684
9146
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/migrator.js
|
|
8685
9147
|
import crypto4 from "crypto";
|
|
8686
|
-
import
|
|
9148
|
+
import fs2 from "fs";
|
|
8687
9149
|
function readMigrationFiles(config) {
|
|
8688
9150
|
const migrationFolderTo = config.migrationsFolder;
|
|
8689
9151
|
const migrationQueries = [];
|
|
8690
9152
|
const journalPath = `${migrationFolderTo}/meta/_journal.json`;
|
|
8691
|
-
if (!
|
|
9153
|
+
if (!fs2.existsSync(journalPath)) {
|
|
8692
9154
|
throw new Error(`Can't find meta/_journal.json file`);
|
|
8693
9155
|
}
|
|
8694
|
-
const journalAsString =
|
|
9156
|
+
const journalAsString = fs2.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
|
|
8695
9157
|
const journal = JSON.parse(journalAsString);
|
|
8696
9158
|
for (const journalEntry of journal.entries) {
|
|
8697
9159
|
const migrationPath = `${migrationFolderTo}/${journalEntry.tag}.sql`;
|
|
8698
9160
|
try {
|
|
8699
|
-
const query =
|
|
9161
|
+
const query = fs2.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
|
|
8700
9162
|
const result = query.split("--> statement-breakpoint").map((it) => {
|
|
8701
9163
|
return it;
|
|
8702
9164
|
});
|
|
@@ -8721,12 +9183,12 @@ function migrate(db, config) {
|
|
|
8721
9183
|
|
|
8722
9184
|
// src/storage/openStore.ts
|
|
8723
9185
|
function resolveMigrationsFolder(callerUrl, migrationsSubdir) {
|
|
8724
|
-
const here =
|
|
9186
|
+
const here = path2.dirname(fileURLToPath(callerUrl));
|
|
8725
9187
|
const candidates = [
|
|
8726
|
-
|
|
8727
|
-
|
|
9188
|
+
path2.join(here, "../..", migrationsSubdir),
|
|
9189
|
+
path2.join(here, ".", migrationsSubdir)
|
|
8728
9190
|
];
|
|
8729
|
-
const found = candidates.find((p) =>
|
|
9191
|
+
const found = candidates.find((p) => fs3.existsSync(p));
|
|
8730
9192
|
if (!found) {
|
|
8731
9193
|
throw new Error(`Could not locate migrations folder "${migrationsSubdir}"`);
|
|
8732
9194
|
}
|
|
@@ -8740,8 +9202,8 @@ function openStore({
|
|
|
8740
9202
|
schema
|
|
8741
9203
|
}) {
|
|
8742
9204
|
const migrationsFolder = resolveMigrationsFolder(callerUrl, migrationsSubdir);
|
|
8743
|
-
|
|
8744
|
-
const sqlite = new
|
|
9205
|
+
fs3.mkdirSync(dataDir2, { recursive: true });
|
|
9206
|
+
const sqlite = new Database2(path2.join(dataDir2, dbFile));
|
|
8745
9207
|
const db = drizzle(sqlite, { schema });
|
|
8746
9208
|
migrate(db, { migrationsFolder });
|
|
8747
9209
|
return { db, sqlite };
|
|
@@ -8774,13 +9236,32 @@ var chats = sqliteTable(
|
|
|
8774
9236
|
agent: text("agent").notNull(),
|
|
8775
9237
|
sourceId: text("source_id").notNull(),
|
|
8776
9238
|
firstSeenAt: integer("first_seen_at", { mode: "timestamp_ms" }).notNull(),
|
|
9239
|
+
// Denormalized "conversation start" = min(messages.ts), kept current at
|
|
9240
|
+
// ingest and backfilled in migration 0010. The createdAt sort pages by this
|
|
9241
|
+
// (not first_seen_at, the ingest time) so the paged order matches the
|
|
9242
|
+
// reader's displayed createdAt (issue #143, reconciling ADR-0017's caveat).
|
|
9243
|
+
// Initialized to first_seen_at for a chat with no messages; since
|
|
9244
|
+
// first_seen_at is the ingest time and so >= every message ts, the running
|
|
9245
|
+
// min over (first_seen_at, message ts) equals min(messages.ts) once any
|
|
9246
|
+
// message exists, and first_seen_at when none — never NULL.
|
|
9247
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }),
|
|
9248
|
+
// Denormalized "most recent activity" = max(messages.ts), kept current at
|
|
9249
|
+
// ingest and backfilled in migration 0009. Lets the activity sort run as a
|
|
9250
|
+
// keyset index range scan instead of an aggregate that defeats the index
|
|
9251
|
+
// (ADR-0017). Initialized to first_seen_at for a chat with no messages, so
|
|
9252
|
+
// it is never NULL and always agrees with the reader's derived `updatedAt`.
|
|
9253
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }),
|
|
8777
9254
|
project: text("project"),
|
|
8778
9255
|
projectPath: text("project_path")
|
|
8779
9256
|
},
|
|
8780
9257
|
(t) => [
|
|
8781
9258
|
uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId),
|
|
8782
9259
|
// Backs the server-side Project filter (`WHERE coalesce(project,'') IN …`).
|
|
8783
|
-
index("chats_project_idx").on(t.project)
|
|
9260
|
+
index("chats_project_idx").on(t.project),
|
|
9261
|
+
// Covering keyset indexes for the two server-side list sorts (ADR-0017):
|
|
9262
|
+
// (sortKey, id) so the ORDER BY + LIMIT is an index range scan either way.
|
|
9263
|
+
index("chats_created_keyset_idx").on(t.createdAt, t.id),
|
|
9264
|
+
index("chats_updated_keyset_idx").on(t.updatedAt, t.id)
|
|
8784
9265
|
]
|
|
8785
9266
|
);
|
|
8786
9267
|
var rawMessages = sqliteTable(
|
|
@@ -8836,6 +9317,10 @@ function createArchiveReadSeam(db) {
|
|
|
8836
9317
|
}
|
|
8837
9318
|
return db.select().from(chats).all();
|
|
8838
9319
|
},
|
|
9320
|
+
listChatRowsByIds(ids) {
|
|
9321
|
+
if (ids.length === 0) return [];
|
|
9322
|
+
return db.select().from(chats).where(inArray(chats.id, ids)).all();
|
|
9323
|
+
},
|
|
8839
9324
|
findChatBySourceId(sourceId) {
|
|
8840
9325
|
return db.select().from(chats).where(eq(chats.sourceId, sourceId)).get() ?? null;
|
|
8841
9326
|
},
|
|
@@ -8845,15 +9330,17 @@ function createArchiveReadSeam(db) {
|
|
|
8845
9330
|
listMessagesByChat(agent, sourceId) {
|
|
8846
9331
|
return db.select().from(messages).where(and(eq(messages.agent, agent), eq(messages.sourceId, sourceId))).orderBy(asc(messages.ts)).all();
|
|
8847
9332
|
},
|
|
8848
|
-
listChatTsRanges() {
|
|
9333
|
+
listChatTsRanges(opts) {
|
|
9334
|
+
const scope = opts?.sourceIds !== void 0 ? inArray(messages.sourceId, opts.sourceIds) : sql`1 = 1`;
|
|
8849
9335
|
return db.select({
|
|
8850
9336
|
agent: messages.agent,
|
|
8851
9337
|
sourceId: messages.sourceId,
|
|
8852
9338
|
minTs: sql`min(${messages.ts})`,
|
|
8853
9339
|
maxTs: sql`max(${messages.ts})`
|
|
8854
|
-
}).from(messages).groupBy(messages.agent, messages.sourceId).all();
|
|
9340
|
+
}).from(messages).where(scope).groupBy(messages.agent, messages.sourceId).all();
|
|
8855
9341
|
},
|
|
8856
|
-
listLatestRawSourcePaths() {
|
|
9342
|
+
listLatestRawSourcePaths(opts) {
|
|
9343
|
+
const scope = opts?.sourceIds !== void 0 ? inArray(rawMessages.sourceId, opts.sourceIds) : sql`1 = 1`;
|
|
8857
9344
|
const ranked = db.select({
|
|
8858
9345
|
agent: rawMessages.agent,
|
|
8859
9346
|
sourceId: rawMessages.sourceId,
|
|
@@ -8861,14 +9348,18 @@ function createArchiveReadSeam(db) {
|
|
|
8861
9348
|
rn: sql`row_number() over (partition by ${rawMessages.agent}, ${rawMessages.sourceId} order by ${rawMessages.ingestedAt} desc)`.as(
|
|
8862
9349
|
"rn"
|
|
8863
9350
|
)
|
|
8864
|
-
}).from(rawMessages).as("ranked_raw");
|
|
9351
|
+
}).from(rawMessages).where(scope).as("ranked_raw");
|
|
8865
9352
|
return db.select({
|
|
8866
9353
|
agent: ranked.agent,
|
|
8867
9354
|
sourceId: ranked.sourceId,
|
|
8868
9355
|
sourcePath: ranked.sourcePath
|
|
8869
9356
|
}).from(ranked).where(eq(ranked.rn, 1)).all();
|
|
8870
9357
|
},
|
|
8871
|
-
listFirstUserTexts() {
|
|
9358
|
+
listFirstUserTexts(opts) {
|
|
9359
|
+
const scope = opts?.sourceIds !== void 0 ? and(
|
|
9360
|
+
eq(messages.role, "user"),
|
|
9361
|
+
inArray(messages.sourceId, opts.sourceIds)
|
|
9362
|
+
) : eq(messages.role, "user");
|
|
8872
9363
|
const ranked = db.select({
|
|
8873
9364
|
agent: messages.agent,
|
|
8874
9365
|
sourceId: messages.sourceId,
|
|
@@ -8876,7 +9367,7 @@ function createArchiveReadSeam(db) {
|
|
|
8876
9367
|
rn: sql`row_number() over (partition by ${messages.agent}, ${messages.sourceId} order by ${messages.ts} asc)`.as(
|
|
8877
9368
|
"rn"
|
|
8878
9369
|
)
|
|
8879
|
-
}).from(messages).where(
|
|
9370
|
+
}).from(messages).where(scope).as("ranked_user");
|
|
8880
9371
|
return db.select({
|
|
8881
9372
|
agent: ranked.agent,
|
|
8882
9373
|
sourceId: ranked.sourceId,
|
|
@@ -8959,6 +9450,12 @@ function createArchiveRepository({
|
|
|
8959
9450
|
agent,
|
|
8960
9451
|
sourceId,
|
|
8961
9452
|
firstSeenAt,
|
|
9453
|
+
// Seed both denormalized sort keys to first_seen_at; message upserts
|
|
9454
|
+
// pull createdAt down (min) and push updatedAt up (max) as ts arrive.
|
|
9455
|
+
// first_seen_at is the ingest time and so >= every message ts, making
|
|
9456
|
+
// it a safe upper bound for the running min.
|
|
9457
|
+
createdAt: firstSeenAt,
|
|
9458
|
+
updatedAt: firstSeenAt,
|
|
8962
9459
|
project: project ?? null,
|
|
8963
9460
|
projectPath: projectPath ?? null
|
|
8964
9461
|
}).run();
|
|
@@ -8995,6 +9492,10 @@ function createArchiveRepository({
|
|
|
8995
9492
|
)
|
|
8996
9493
|
).get();
|
|
8997
9494
|
const ts = parseTs(message.ts);
|
|
9495
|
+
db.update(chats).set({
|
|
9496
|
+
createdAt: sql`min(coalesce(${chats.createdAt}, ${ts.getTime()}), ${ts.getTime()})`,
|
|
9497
|
+
updatedAt: sql`max(coalesce(${chats.updatedAt}, 0), ${ts.getTime()})`
|
|
9498
|
+
}).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).run();
|
|
8998
9499
|
if (!existing2) {
|
|
8999
9500
|
db.insert(messages).values({
|
|
9000
9501
|
agent,
|
|
@@ -9115,26 +9616,53 @@ function createCheckpointRepository({
|
|
|
9115
9616
|
}
|
|
9116
9617
|
|
|
9117
9618
|
// src/metadata/repository.ts
|
|
9118
|
-
import
|
|
9119
|
-
import
|
|
9619
|
+
import fs4 from "fs";
|
|
9620
|
+
import path3 from "path";
|
|
9120
9621
|
|
|
9121
9622
|
// src/metadata/schema.ts
|
|
9122
9623
|
var schema_exports3 = {};
|
|
9123
9624
|
__export(schema_exports3, {
|
|
9625
|
+
chatSortKeys: () => chatSortKeys,
|
|
9124
9626
|
chatTags: () => chatTags,
|
|
9125
9627
|
chatsMeta: () => chatsMeta,
|
|
9126
9628
|
tags: () => tags
|
|
9127
9629
|
});
|
|
9128
|
-
var chatsMeta = sqliteTable(
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9630
|
+
var chatsMeta = sqliteTable(
|
|
9631
|
+
"chats_meta",
|
|
9632
|
+
{
|
|
9633
|
+
id: text("id").primaryKey(),
|
|
9634
|
+
isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
|
|
9635
|
+
customTitle: text("custom_title"),
|
|
9636
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
9637
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
|
|
9638
|
+
// Set when a chat is moved to Trash; null while active. Drives the Trash
|
|
9639
|
+
// view's independent "Deleted time" sort axis.
|
|
9640
|
+
deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
|
|
9641
|
+
},
|
|
9642
|
+
(table) => [
|
|
9643
|
+
// Covering keyset index for the Trash view's deleted-time axis (#145,
|
|
9644
|
+
// ADR-0017): `(deleted_at, id)` so the page's ORDER BY + LIMIT is an index
|
|
9645
|
+
// range scan, not a full-trash sort. Partial on `deleted_at IS NOT NULL`,
|
|
9646
|
+
// which holds exactly for trashed rows (restore nulls it), so the index
|
|
9647
|
+
// carries only the Trash set and the page query's matching predicate lets
|
|
9648
|
+
// the planner use it.
|
|
9649
|
+
index("chats_meta_deleted_at_idx").on(table.deletedAt, table.id).where(sql`${table.deletedAt} is not null`)
|
|
9650
|
+
]
|
|
9651
|
+
);
|
|
9652
|
+
var chatSortKeys = sqliteTable(
|
|
9653
|
+
"chat_sort_keys",
|
|
9654
|
+
{
|
|
9655
|
+
id: text("id").primaryKey(),
|
|
9656
|
+
textKey: text("text_key"),
|
|
9657
|
+
sortKey: text("sort_key")
|
|
9658
|
+
},
|
|
9659
|
+
(table) => [
|
|
9660
|
+
// Covering keyset index for the Title axis (ADR-0017/0019): `(sort_key, id)`
|
|
9661
|
+
// so `ORDER BY sort_key, id` + LIMIT is an index range scan either
|
|
9662
|
+
// direction, exactly like the time axes — never a full-table sort.
|
|
9663
|
+
index("chat_sort_keys_sort_key_idx").on(table.sortKey, table.id)
|
|
9664
|
+
]
|
|
9665
|
+
);
|
|
9138
9666
|
var tags = sqliteTable("tags", {
|
|
9139
9667
|
id: text("id").primaryKey(),
|
|
9140
9668
|
name: text("name").notNull(),
|
|
@@ -9154,15 +9682,26 @@ var chatTags = sqliteTable(
|
|
|
9154
9682
|
]
|
|
9155
9683
|
);
|
|
9156
9684
|
|
|
9685
|
+
// src/metadata/title-sort-key.ts
|
|
9686
|
+
function encodeNumericRun(run2) {
|
|
9687
|
+
const core = run2.replace(/^0+(?=\d)/, "");
|
|
9688
|
+
const len = String(core.length);
|
|
9689
|
+
return String(len.length) + len + core;
|
|
9690
|
+
}
|
|
9691
|
+
function computeSortKey(input) {
|
|
9692
|
+
const folded = input.normalize("NFKD").toLowerCase().replace(/\p{M}+/gu, "");
|
|
9693
|
+
return folded.replace(/\d+/g, encodeNumericRun);
|
|
9694
|
+
}
|
|
9695
|
+
|
|
9157
9696
|
// src/metadata/repository.ts
|
|
9158
9697
|
var DB_FILE = "metadata.db";
|
|
9159
9698
|
var LEGACY_DB_FILE = "data.db";
|
|
9160
9699
|
function migrateLegacyDbFile(dataDir2) {
|
|
9161
|
-
const legacy =
|
|
9162
|
-
const current =
|
|
9163
|
-
if (!
|
|
9164
|
-
if (
|
|
9165
|
-
|
|
9700
|
+
const legacy = path3.join(dataDir2, LEGACY_DB_FILE);
|
|
9701
|
+
const current = path3.join(dataDir2, DB_FILE);
|
|
9702
|
+
if (!fs4.existsSync(legacy)) return;
|
|
9703
|
+
if (fs4.existsSync(current)) return;
|
|
9704
|
+
fs4.renameSync(legacy, current);
|
|
9166
9705
|
}
|
|
9167
9706
|
var CLAUDE_CODE_AGENT = "claude-code";
|
|
9168
9707
|
function createMetadataRepository({
|
|
@@ -9234,6 +9773,27 @@ function createMetadataRepository({
|
|
|
9234
9773
|
target: chatsMeta.id,
|
|
9235
9774
|
set: { customTitle: title, updatedAt: now }
|
|
9236
9775
|
}).run();
|
|
9776
|
+
if (title !== null) {
|
|
9777
|
+
const sortKey = computeSortKey(title);
|
|
9778
|
+
db.insert(chatSortKeys).values({ id: internalId, sortKey }).onConflictDoUpdate({ target: chatSortKeys.id, set: { sortKey } }).run();
|
|
9779
|
+
} else {
|
|
9780
|
+
db.update(chatSortKeys).set({ sortKey: sql`${chatSortKeys.textKey}` }).where(eq(chatSortKeys.id, internalId)).run();
|
|
9781
|
+
}
|
|
9782
|
+
},
|
|
9783
|
+
getTitleSortKey(internalId) {
|
|
9784
|
+
const row = db.select({
|
|
9785
|
+
textKey: chatSortKeys.textKey,
|
|
9786
|
+
sortKey: chatSortKeys.sortKey
|
|
9787
|
+
}).from(chatSortKeys).where(eq(chatSortKeys.id, internalId)).get();
|
|
9788
|
+
return row ?? null;
|
|
9789
|
+
},
|
|
9790
|
+
setTitleTextKey(internalId, textKey) {
|
|
9791
|
+
const customTitle = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get()?.customTitle;
|
|
9792
|
+
const sortKey = customTitle && customTitle.trim() ? computeSortKey(customTitle) : textKey;
|
|
9793
|
+
db.insert(chatSortKeys).values({ id: internalId, textKey, sortKey }).onConflictDoUpdate({
|
|
9794
|
+
target: chatSortKeys.id,
|
|
9795
|
+
set: { textKey, sortKey }
|
|
9796
|
+
}).run();
|
|
9237
9797
|
}
|
|
9238
9798
|
};
|
|
9239
9799
|
}
|
|
@@ -9345,7 +9905,7 @@ function createTagRepository({
|
|
|
9345
9905
|
}
|
|
9346
9906
|
|
|
9347
9907
|
// src/ingestion/ingest.ts
|
|
9348
|
-
import
|
|
9908
|
+
import fs5 from "fs";
|
|
9349
9909
|
async function runIngestion(opts) {
|
|
9350
9910
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
9351
9911
|
const result = {
|
|
@@ -9405,7 +9965,7 @@ async function runIngestion(opts) {
|
|
|
9405
9965
|
}
|
|
9406
9966
|
function safeStat(p) {
|
|
9407
9967
|
try {
|
|
9408
|
-
return
|
|
9968
|
+
return fs5.statSync(p);
|
|
9409
9969
|
} catch {
|
|
9410
9970
|
return null;
|
|
9411
9971
|
}
|
|
@@ -9514,7 +10074,7 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9514
10074
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
9515
10075
|
const statMethod = opts.lstat ? lstat : stat;
|
|
9516
10076
|
if (wantBigintFsStats) {
|
|
9517
|
-
this._stat = (
|
|
10077
|
+
this._stat = (path8) => statMethod(path8, { bigint: true });
|
|
9518
10078
|
} else {
|
|
9519
10079
|
this._stat = statMethod;
|
|
9520
10080
|
}
|
|
@@ -9539,8 +10099,8 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9539
10099
|
const par = this.parent;
|
|
9540
10100
|
const fil = par && par.files;
|
|
9541
10101
|
if (fil && fil.length > 0) {
|
|
9542
|
-
const { path:
|
|
9543
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
10102
|
+
const { path: path8, depth } = par;
|
|
10103
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
|
|
9544
10104
|
const awaited = await Promise.all(slice);
|
|
9545
10105
|
for (const entry of awaited) {
|
|
9546
10106
|
if (!entry)
|
|
@@ -9580,20 +10140,20 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9580
10140
|
this.reading = false;
|
|
9581
10141
|
}
|
|
9582
10142
|
}
|
|
9583
|
-
async _exploreDir(
|
|
10143
|
+
async _exploreDir(path8, depth) {
|
|
9584
10144
|
let files;
|
|
9585
10145
|
try {
|
|
9586
|
-
files = await readdir(
|
|
10146
|
+
files = await readdir(path8, this._rdOptions);
|
|
9587
10147
|
} catch (error) {
|
|
9588
10148
|
this._onError(error);
|
|
9589
10149
|
}
|
|
9590
|
-
return { files, depth, path:
|
|
10150
|
+
return { files, depth, path: path8 };
|
|
9591
10151
|
}
|
|
9592
|
-
async _formatEntry(dirent,
|
|
10152
|
+
async _formatEntry(dirent, path8) {
|
|
9593
10153
|
let entry;
|
|
9594
10154
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
9595
10155
|
try {
|
|
9596
|
-
const fullPath = presolve(pjoin(
|
|
10156
|
+
const fullPath = presolve(pjoin(path8, basename3));
|
|
9597
10157
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
9598
10158
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
9599
10159
|
} catch (err) {
|
|
@@ -9993,16 +10553,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
9993
10553
|
};
|
|
9994
10554
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
9995
10555
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
9996
|
-
function createFsWatchInstance(
|
|
10556
|
+
function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
|
|
9997
10557
|
const handleEvent = (rawEvent, evPath) => {
|
|
9998
|
-
listener(
|
|
9999
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
10000
|
-
if (evPath &&
|
|
10001
|
-
fsWatchBroadcast(sp.resolve(
|
|
10558
|
+
listener(path8);
|
|
10559
|
+
emitRaw(rawEvent, evPath, { watchedPath: path8 });
|
|
10560
|
+
if (evPath && path8 !== evPath) {
|
|
10561
|
+
fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
|
|
10002
10562
|
}
|
|
10003
10563
|
};
|
|
10004
10564
|
try {
|
|
10005
|
-
return fs_watch(
|
|
10565
|
+
return fs_watch(path8, {
|
|
10006
10566
|
persistent: options.persistent
|
|
10007
10567
|
}, handleEvent);
|
|
10008
10568
|
} catch (error) {
|
|
@@ -10018,12 +10578,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
10018
10578
|
listener(val1, val2, val3);
|
|
10019
10579
|
});
|
|
10020
10580
|
};
|
|
10021
|
-
var setFsWatchListener = (
|
|
10581
|
+
var setFsWatchListener = (path8, fullPath, options, handlers) => {
|
|
10022
10582
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
10023
10583
|
let cont = FsWatchInstances.get(fullPath);
|
|
10024
10584
|
let watcher2;
|
|
10025
10585
|
if (!options.persistent) {
|
|
10026
|
-
watcher2 = createFsWatchInstance(
|
|
10586
|
+
watcher2 = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
|
|
10027
10587
|
if (!watcher2)
|
|
10028
10588
|
return;
|
|
10029
10589
|
return watcher2.close.bind(watcher2);
|
|
@@ -10034,7 +10594,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
10034
10594
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
10035
10595
|
} else {
|
|
10036
10596
|
watcher2 = createFsWatchInstance(
|
|
10037
|
-
|
|
10597
|
+
path8,
|
|
10038
10598
|
options,
|
|
10039
10599
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
10040
10600
|
errHandler,
|
|
@@ -10049,7 +10609,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
10049
10609
|
cont.watcherUnusable = true;
|
|
10050
10610
|
if (isWindows && error.code === "EPERM") {
|
|
10051
10611
|
try {
|
|
10052
|
-
const fd = await open(
|
|
10612
|
+
const fd = await open(path8, "r");
|
|
10053
10613
|
await fd.close();
|
|
10054
10614
|
broadcastErr(error);
|
|
10055
10615
|
} catch (err) {
|
|
@@ -10080,7 +10640,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
10080
10640
|
};
|
|
10081
10641
|
};
|
|
10082
10642
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
10083
|
-
var setFsWatchFileListener = (
|
|
10643
|
+
var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
|
|
10084
10644
|
const { listener, rawEmitter } = handlers;
|
|
10085
10645
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
10086
10646
|
const copts = cont && cont.options;
|
|
@@ -10102,7 +10662,7 @@ var setFsWatchFileListener = (path5, fullPath, options, handlers) => {
|
|
|
10102
10662
|
});
|
|
10103
10663
|
const currmtime = curr.mtimeMs;
|
|
10104
10664
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
10105
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
10665
|
+
foreach(cont.listeners, (listener2) => listener2(path8, curr));
|
|
10106
10666
|
}
|
|
10107
10667
|
})
|
|
10108
10668
|
};
|
|
@@ -10132,13 +10692,13 @@ var NodeFsHandler = class {
|
|
|
10132
10692
|
* @param listener on fs change
|
|
10133
10693
|
* @returns closer for the watcher instance
|
|
10134
10694
|
*/
|
|
10135
|
-
_watchWithNodeFs(
|
|
10695
|
+
_watchWithNodeFs(path8, listener) {
|
|
10136
10696
|
const opts = this.fsw.options;
|
|
10137
|
-
const directory = sp.dirname(
|
|
10138
|
-
const basename3 = sp.basename(
|
|
10697
|
+
const directory = sp.dirname(path8);
|
|
10698
|
+
const basename3 = sp.basename(path8);
|
|
10139
10699
|
const parent = this.fsw._getWatchedDir(directory);
|
|
10140
10700
|
parent.add(basename3);
|
|
10141
|
-
const absolutePath = sp.resolve(
|
|
10701
|
+
const absolutePath = sp.resolve(path8);
|
|
10142
10702
|
const options = {
|
|
10143
10703
|
persistent: opts.persistent
|
|
10144
10704
|
};
|
|
@@ -10148,12 +10708,12 @@ var NodeFsHandler = class {
|
|
|
10148
10708
|
if (opts.usePolling) {
|
|
10149
10709
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
10150
10710
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
10151
|
-
closer = setFsWatchFileListener(
|
|
10711
|
+
closer = setFsWatchFileListener(path8, absolutePath, options, {
|
|
10152
10712
|
listener,
|
|
10153
10713
|
rawEmitter: this.fsw._emitRaw
|
|
10154
10714
|
});
|
|
10155
10715
|
} else {
|
|
10156
|
-
closer = setFsWatchListener(
|
|
10716
|
+
closer = setFsWatchListener(path8, absolutePath, options, {
|
|
10157
10717
|
listener,
|
|
10158
10718
|
errHandler: this._boundHandleError,
|
|
10159
10719
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -10175,7 +10735,7 @@ var NodeFsHandler = class {
|
|
|
10175
10735
|
let prevStats = stats;
|
|
10176
10736
|
if (parent.has(basename3))
|
|
10177
10737
|
return;
|
|
10178
|
-
const listener = async (
|
|
10738
|
+
const listener = async (path8, newStats) => {
|
|
10179
10739
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
10180
10740
|
return;
|
|
10181
10741
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -10189,11 +10749,11 @@ var NodeFsHandler = class {
|
|
|
10189
10749
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
10190
10750
|
}
|
|
10191
10751
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
10192
|
-
this.fsw._closeFile(
|
|
10752
|
+
this.fsw._closeFile(path8);
|
|
10193
10753
|
prevStats = newStats2;
|
|
10194
10754
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
10195
10755
|
if (closer2)
|
|
10196
|
-
this.fsw._addPathCloser(
|
|
10756
|
+
this.fsw._addPathCloser(path8, closer2);
|
|
10197
10757
|
} else {
|
|
10198
10758
|
prevStats = newStats2;
|
|
10199
10759
|
}
|
|
@@ -10225,7 +10785,7 @@ var NodeFsHandler = class {
|
|
|
10225
10785
|
* @param item basename of this item
|
|
10226
10786
|
* @returns true if no more processing is needed for this entry.
|
|
10227
10787
|
*/
|
|
10228
|
-
async _handleSymlink(entry, directory,
|
|
10788
|
+
async _handleSymlink(entry, directory, path8, item) {
|
|
10229
10789
|
if (this.fsw.closed) {
|
|
10230
10790
|
return;
|
|
10231
10791
|
}
|
|
@@ -10235,7 +10795,7 @@ var NodeFsHandler = class {
|
|
|
10235
10795
|
this.fsw._incrReadyCount();
|
|
10236
10796
|
let linkPath;
|
|
10237
10797
|
try {
|
|
10238
|
-
linkPath = await fsrealpath(
|
|
10798
|
+
linkPath = await fsrealpath(path8);
|
|
10239
10799
|
} catch (e) {
|
|
10240
10800
|
this.fsw._emitReady();
|
|
10241
10801
|
return true;
|
|
@@ -10245,12 +10805,12 @@ var NodeFsHandler = class {
|
|
|
10245
10805
|
if (dir.has(item)) {
|
|
10246
10806
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
10247
10807
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
10248
|
-
this.fsw._emit(EV.CHANGE,
|
|
10808
|
+
this.fsw._emit(EV.CHANGE, path8, entry.stats);
|
|
10249
10809
|
}
|
|
10250
10810
|
} else {
|
|
10251
10811
|
dir.add(item);
|
|
10252
10812
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
10253
|
-
this.fsw._emit(EV.ADD,
|
|
10813
|
+
this.fsw._emit(EV.ADD, path8, entry.stats);
|
|
10254
10814
|
}
|
|
10255
10815
|
this.fsw._emitReady();
|
|
10256
10816
|
return true;
|
|
@@ -10268,39 +10828,39 @@ var NodeFsHandler = class {
|
|
|
10268
10828
|
return;
|
|
10269
10829
|
const previous = this.fsw._getWatchedDir(wh.path);
|
|
10270
10830
|
const current = /* @__PURE__ */ new Set();
|
|
10271
|
-
let
|
|
10831
|
+
let stream2 = this.fsw._readdirp(directory, {
|
|
10272
10832
|
fileFilter: (entry) => wh.filterPath(entry),
|
|
10273
10833
|
directoryFilter: (entry) => wh.filterDir(entry)
|
|
10274
10834
|
});
|
|
10275
|
-
if (!
|
|
10835
|
+
if (!stream2)
|
|
10276
10836
|
return;
|
|
10277
|
-
|
|
10837
|
+
stream2.on(STR_DATA, async (entry) => {
|
|
10278
10838
|
if (this.fsw.closed) {
|
|
10279
|
-
|
|
10839
|
+
stream2 = void 0;
|
|
10280
10840
|
return;
|
|
10281
10841
|
}
|
|
10282
10842
|
const item = entry.path;
|
|
10283
|
-
let
|
|
10843
|
+
let path8 = sp.join(directory, item);
|
|
10284
10844
|
current.add(item);
|
|
10285
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
10845
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
|
|
10286
10846
|
return;
|
|
10287
10847
|
}
|
|
10288
10848
|
if (this.fsw.closed) {
|
|
10289
|
-
|
|
10849
|
+
stream2 = void 0;
|
|
10290
10850
|
return;
|
|
10291
10851
|
}
|
|
10292
10852
|
if (item === target || !target && !previous.has(item)) {
|
|
10293
10853
|
this.fsw._incrReadyCount();
|
|
10294
|
-
|
|
10295
|
-
this._addToNodeFs(
|
|
10854
|
+
path8 = sp.join(dir, sp.relative(dir, path8));
|
|
10855
|
+
this._addToNodeFs(path8, initialAdd, wh, depth + 1);
|
|
10296
10856
|
}
|
|
10297
10857
|
}).on(EV.ERROR, this._boundHandleError);
|
|
10298
10858
|
return new Promise((resolve3, reject) => {
|
|
10299
|
-
if (!
|
|
10859
|
+
if (!stream2)
|
|
10300
10860
|
return reject();
|
|
10301
|
-
|
|
10861
|
+
stream2.once(STR_END, () => {
|
|
10302
10862
|
if (this.fsw.closed) {
|
|
10303
|
-
|
|
10863
|
+
stream2 = void 0;
|
|
10304
10864
|
return;
|
|
10305
10865
|
}
|
|
10306
10866
|
const wasThrottled = throttler ? throttler.clear() : false;
|
|
@@ -10310,7 +10870,7 @@ var NodeFsHandler = class {
|
|
|
10310
10870
|
}).forEach((item) => {
|
|
10311
10871
|
this.fsw._remove(directory, item);
|
|
10312
10872
|
});
|
|
10313
|
-
|
|
10873
|
+
stream2 = void 0;
|
|
10314
10874
|
if (wasThrottled)
|
|
10315
10875
|
this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
10316
10876
|
});
|
|
@@ -10361,13 +10921,13 @@ var NodeFsHandler = class {
|
|
|
10361
10921
|
* @param depth Child path actually targeted for watch
|
|
10362
10922
|
* @param target Child path actually targeted for watch
|
|
10363
10923
|
*/
|
|
10364
|
-
async _addToNodeFs(
|
|
10924
|
+
async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
|
|
10365
10925
|
const ready = this.fsw._emitReady;
|
|
10366
|
-
if (this.fsw._isIgnored(
|
|
10926
|
+
if (this.fsw._isIgnored(path8) || this.fsw.closed) {
|
|
10367
10927
|
ready();
|
|
10368
10928
|
return false;
|
|
10369
10929
|
}
|
|
10370
|
-
const wh = this.fsw._getWatchHelpers(
|
|
10930
|
+
const wh = this.fsw._getWatchHelpers(path8);
|
|
10371
10931
|
if (priorWh) {
|
|
10372
10932
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
10373
10933
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -10383,8 +10943,8 @@ var NodeFsHandler = class {
|
|
|
10383
10943
|
const follow = this.fsw.options.followSymlinks;
|
|
10384
10944
|
let closer;
|
|
10385
10945
|
if (stats.isDirectory()) {
|
|
10386
|
-
const absPath = sp.resolve(
|
|
10387
|
-
const targetPath = follow ? await fsrealpath(
|
|
10946
|
+
const absPath = sp.resolve(path8);
|
|
10947
|
+
const targetPath = follow ? await fsrealpath(path8) : path8;
|
|
10388
10948
|
if (this.fsw.closed)
|
|
10389
10949
|
return;
|
|
10390
10950
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -10394,29 +10954,29 @@ var NodeFsHandler = class {
|
|
|
10394
10954
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
10395
10955
|
}
|
|
10396
10956
|
} else if (stats.isSymbolicLink()) {
|
|
10397
|
-
const targetPath = follow ? await fsrealpath(
|
|
10957
|
+
const targetPath = follow ? await fsrealpath(path8) : path8;
|
|
10398
10958
|
if (this.fsw.closed)
|
|
10399
10959
|
return;
|
|
10400
10960
|
const parent = sp.dirname(wh.watchPath);
|
|
10401
10961
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
10402
10962
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
10403
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
10963
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
|
|
10404
10964
|
if (this.fsw.closed)
|
|
10405
10965
|
return;
|
|
10406
10966
|
if (targetPath !== void 0) {
|
|
10407
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
10967
|
+
this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
|
|
10408
10968
|
}
|
|
10409
10969
|
} else {
|
|
10410
10970
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
10411
10971
|
}
|
|
10412
10972
|
ready();
|
|
10413
10973
|
if (closer)
|
|
10414
|
-
this.fsw._addPathCloser(
|
|
10974
|
+
this.fsw._addPathCloser(path8, closer);
|
|
10415
10975
|
return false;
|
|
10416
10976
|
} catch (error) {
|
|
10417
10977
|
if (this.fsw._handleError(error)) {
|
|
10418
10978
|
ready();
|
|
10419
|
-
return
|
|
10979
|
+
return path8;
|
|
10420
10980
|
}
|
|
10421
10981
|
}
|
|
10422
10982
|
}
|
|
@@ -10459,24 +11019,24 @@ function createPattern(matcher) {
|
|
|
10459
11019
|
}
|
|
10460
11020
|
return () => false;
|
|
10461
11021
|
}
|
|
10462
|
-
function normalizePath(
|
|
10463
|
-
if (typeof
|
|
11022
|
+
function normalizePath(path8) {
|
|
11023
|
+
if (typeof path8 !== "string")
|
|
10464
11024
|
throw new Error("string expected");
|
|
10465
|
-
|
|
10466
|
-
|
|
11025
|
+
path8 = sp2.normalize(path8);
|
|
11026
|
+
path8 = path8.replace(/\\/g, "/");
|
|
10467
11027
|
let prepend = false;
|
|
10468
|
-
if (
|
|
11028
|
+
if (path8.startsWith("//"))
|
|
10469
11029
|
prepend = true;
|
|
10470
|
-
|
|
11030
|
+
path8 = path8.replace(DOUBLE_SLASH_RE, "/");
|
|
10471
11031
|
if (prepend)
|
|
10472
|
-
|
|
10473
|
-
return
|
|
11032
|
+
path8 = "/" + path8;
|
|
11033
|
+
return path8;
|
|
10474
11034
|
}
|
|
10475
11035
|
function matchPatterns(patterns, testString, stats) {
|
|
10476
|
-
const
|
|
11036
|
+
const path8 = normalizePath(testString);
|
|
10477
11037
|
for (let index2 = 0; index2 < patterns.length; index2++) {
|
|
10478
11038
|
const pattern = patterns[index2];
|
|
10479
|
-
if (pattern(
|
|
11039
|
+
if (pattern(path8, stats)) {
|
|
10480
11040
|
return true;
|
|
10481
11041
|
}
|
|
10482
11042
|
}
|
|
@@ -10514,19 +11074,19 @@ var toUnix = (string) => {
|
|
|
10514
11074
|
}
|
|
10515
11075
|
return str;
|
|
10516
11076
|
};
|
|
10517
|
-
var normalizePathToUnix = (
|
|
10518
|
-
var normalizeIgnored = (cwd = "") => (
|
|
10519
|
-
if (typeof
|
|
10520
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
11077
|
+
var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
|
|
11078
|
+
var normalizeIgnored = (cwd = "") => (path8) => {
|
|
11079
|
+
if (typeof path8 === "string") {
|
|
11080
|
+
return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
|
|
10521
11081
|
} else {
|
|
10522
|
-
return
|
|
11082
|
+
return path8;
|
|
10523
11083
|
}
|
|
10524
11084
|
};
|
|
10525
|
-
var getAbsolutePath = (
|
|
10526
|
-
if (sp2.isAbsolute(
|
|
10527
|
-
return
|
|
11085
|
+
var getAbsolutePath = (path8, cwd) => {
|
|
11086
|
+
if (sp2.isAbsolute(path8)) {
|
|
11087
|
+
return path8;
|
|
10528
11088
|
}
|
|
10529
|
-
return sp2.join(cwd,
|
|
11089
|
+
return sp2.join(cwd, path8);
|
|
10530
11090
|
};
|
|
10531
11091
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
10532
11092
|
var DirEntry = class {
|
|
@@ -10591,10 +11151,10 @@ var WatchHelper = class {
|
|
|
10591
11151
|
dirParts;
|
|
10592
11152
|
followSymlinks;
|
|
10593
11153
|
statMethod;
|
|
10594
|
-
constructor(
|
|
11154
|
+
constructor(path8, follow, fsw) {
|
|
10595
11155
|
this.fsw = fsw;
|
|
10596
|
-
const watchPath =
|
|
10597
|
-
this.path =
|
|
11156
|
+
const watchPath = path8;
|
|
11157
|
+
this.path = path8 = path8.replace(REPLACER_RE, "");
|
|
10598
11158
|
this.watchPath = watchPath;
|
|
10599
11159
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
10600
11160
|
this.dirParts = [];
|
|
@@ -10734,20 +11294,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10734
11294
|
this._closePromise = void 0;
|
|
10735
11295
|
let paths = unifyPaths(paths_);
|
|
10736
11296
|
if (cwd) {
|
|
10737
|
-
paths = paths.map((
|
|
10738
|
-
const absPath = getAbsolutePath(
|
|
11297
|
+
paths = paths.map((path8) => {
|
|
11298
|
+
const absPath = getAbsolutePath(path8, cwd);
|
|
10739
11299
|
return absPath;
|
|
10740
11300
|
});
|
|
10741
11301
|
}
|
|
10742
|
-
paths.forEach((
|
|
10743
|
-
this._removeIgnoredPath(
|
|
11302
|
+
paths.forEach((path8) => {
|
|
11303
|
+
this._removeIgnoredPath(path8);
|
|
10744
11304
|
});
|
|
10745
11305
|
this._userIgnored = void 0;
|
|
10746
11306
|
if (!this._readyCount)
|
|
10747
11307
|
this._readyCount = 0;
|
|
10748
11308
|
this._readyCount += paths.length;
|
|
10749
|
-
Promise.all(paths.map(async (
|
|
10750
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
11309
|
+
Promise.all(paths.map(async (path8) => {
|
|
11310
|
+
const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
|
|
10751
11311
|
if (res)
|
|
10752
11312
|
this._emitReady();
|
|
10753
11313
|
return res;
|
|
@@ -10769,17 +11329,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10769
11329
|
return this;
|
|
10770
11330
|
const paths = unifyPaths(paths_);
|
|
10771
11331
|
const { cwd } = this.options;
|
|
10772
|
-
paths.forEach((
|
|
10773
|
-
if (!sp2.isAbsolute(
|
|
11332
|
+
paths.forEach((path8) => {
|
|
11333
|
+
if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
|
|
10774
11334
|
if (cwd)
|
|
10775
|
-
|
|
10776
|
-
|
|
11335
|
+
path8 = sp2.join(cwd, path8);
|
|
11336
|
+
path8 = sp2.resolve(path8);
|
|
10777
11337
|
}
|
|
10778
|
-
this._closePath(
|
|
10779
|
-
this._addIgnoredPath(
|
|
10780
|
-
if (this._watched.has(
|
|
11338
|
+
this._closePath(path8);
|
|
11339
|
+
this._addIgnoredPath(path8);
|
|
11340
|
+
if (this._watched.has(path8)) {
|
|
10781
11341
|
this._addIgnoredPath({
|
|
10782
|
-
path:
|
|
11342
|
+
path: path8,
|
|
10783
11343
|
recursive: true
|
|
10784
11344
|
});
|
|
10785
11345
|
}
|
|
@@ -10802,7 +11362,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10802
11362
|
if (promise instanceof Promise)
|
|
10803
11363
|
closers.push(promise);
|
|
10804
11364
|
}));
|
|
10805
|
-
this._streams.forEach((
|
|
11365
|
+
this._streams.forEach((stream2) => stream2.destroy());
|
|
10806
11366
|
this._userIgnored = void 0;
|
|
10807
11367
|
this._readyCount = 0;
|
|
10808
11368
|
this._readyEmitted = false;
|
|
@@ -10843,38 +11403,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10843
11403
|
* @param stats arguments to be passed with event
|
|
10844
11404
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
10845
11405
|
*/
|
|
10846
|
-
async _emit(event,
|
|
11406
|
+
async _emit(event, path8, stats) {
|
|
10847
11407
|
if (this.closed)
|
|
10848
11408
|
return;
|
|
10849
11409
|
const opts = this.options;
|
|
10850
11410
|
if (isWindows)
|
|
10851
|
-
|
|
11411
|
+
path8 = sp2.normalize(path8);
|
|
10852
11412
|
if (opts.cwd)
|
|
10853
|
-
|
|
10854
|
-
const args = [
|
|
11413
|
+
path8 = sp2.relative(opts.cwd, path8);
|
|
11414
|
+
const args = [path8];
|
|
10855
11415
|
if (stats != null)
|
|
10856
11416
|
args.push(stats);
|
|
10857
11417
|
const awf = opts.awaitWriteFinish;
|
|
10858
11418
|
let pw;
|
|
10859
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
11419
|
+
if (awf && (pw = this._pendingWrites.get(path8))) {
|
|
10860
11420
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
10861
11421
|
return this;
|
|
10862
11422
|
}
|
|
10863
11423
|
if (opts.atomic) {
|
|
10864
11424
|
if (event === EVENTS.UNLINK) {
|
|
10865
|
-
this._pendingUnlinks.set(
|
|
11425
|
+
this._pendingUnlinks.set(path8, [event, ...args]);
|
|
10866
11426
|
setTimeout(() => {
|
|
10867
|
-
this._pendingUnlinks.forEach((entry,
|
|
11427
|
+
this._pendingUnlinks.forEach((entry, path9) => {
|
|
10868
11428
|
this.emit(...entry);
|
|
10869
11429
|
this.emit(EVENTS.ALL, ...entry);
|
|
10870
|
-
this._pendingUnlinks.delete(
|
|
11430
|
+
this._pendingUnlinks.delete(path9);
|
|
10871
11431
|
});
|
|
10872
11432
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
10873
11433
|
return this;
|
|
10874
11434
|
}
|
|
10875
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
11435
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
|
|
10876
11436
|
event = EVENTS.CHANGE;
|
|
10877
|
-
this._pendingUnlinks.delete(
|
|
11437
|
+
this._pendingUnlinks.delete(path8);
|
|
10878
11438
|
}
|
|
10879
11439
|
}
|
|
10880
11440
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -10892,16 +11452,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10892
11452
|
this.emitWithAll(event, args);
|
|
10893
11453
|
}
|
|
10894
11454
|
};
|
|
10895
|
-
this._awaitWriteFinish(
|
|
11455
|
+
this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
|
|
10896
11456
|
return this;
|
|
10897
11457
|
}
|
|
10898
11458
|
if (event === EVENTS.CHANGE) {
|
|
10899
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
11459
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
|
|
10900
11460
|
if (isThrottled)
|
|
10901
11461
|
return this;
|
|
10902
11462
|
}
|
|
10903
11463
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
10904
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
11464
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
|
|
10905
11465
|
let stats2;
|
|
10906
11466
|
try {
|
|
10907
11467
|
stats2 = await stat3(fullPath);
|
|
@@ -10932,23 +11492,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10932
11492
|
* @param timeout duration of time to suppress duplicate actions
|
|
10933
11493
|
* @returns tracking object or false if action should be suppressed
|
|
10934
11494
|
*/
|
|
10935
|
-
_throttle(actionType,
|
|
11495
|
+
_throttle(actionType, path8, timeout) {
|
|
10936
11496
|
if (!this._throttled.has(actionType)) {
|
|
10937
11497
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
10938
11498
|
}
|
|
10939
11499
|
const action2 = this._throttled.get(actionType);
|
|
10940
11500
|
if (!action2)
|
|
10941
11501
|
throw new Error("invalid throttle");
|
|
10942
|
-
const actionPath = action2.get(
|
|
11502
|
+
const actionPath = action2.get(path8);
|
|
10943
11503
|
if (actionPath) {
|
|
10944
11504
|
actionPath.count++;
|
|
10945
11505
|
return false;
|
|
10946
11506
|
}
|
|
10947
11507
|
let timeoutObject;
|
|
10948
11508
|
const clear = () => {
|
|
10949
|
-
const item = action2.get(
|
|
11509
|
+
const item = action2.get(path8);
|
|
10950
11510
|
const count = item ? item.count : 0;
|
|
10951
|
-
action2.delete(
|
|
11511
|
+
action2.delete(path8);
|
|
10952
11512
|
clearTimeout(timeoutObject);
|
|
10953
11513
|
if (item)
|
|
10954
11514
|
clearTimeout(item.timeoutObject);
|
|
@@ -10956,7 +11516,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10956
11516
|
};
|
|
10957
11517
|
timeoutObject = setTimeout(clear, timeout);
|
|
10958
11518
|
const thr = { timeoutObject, clear, count: 0 };
|
|
10959
|
-
action2.set(
|
|
11519
|
+
action2.set(path8, thr);
|
|
10960
11520
|
return thr;
|
|
10961
11521
|
}
|
|
10962
11522
|
_incrReadyCount() {
|
|
@@ -10970,44 +11530,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10970
11530
|
* @param event
|
|
10971
11531
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
10972
11532
|
*/
|
|
10973
|
-
_awaitWriteFinish(
|
|
11533
|
+
_awaitWriteFinish(path8, threshold, event, awfEmit) {
|
|
10974
11534
|
const awf = this.options.awaitWriteFinish;
|
|
10975
11535
|
if (typeof awf !== "object")
|
|
10976
11536
|
return;
|
|
10977
11537
|
const pollInterval = awf.pollInterval;
|
|
10978
11538
|
let timeoutHandler;
|
|
10979
|
-
let fullPath =
|
|
10980
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
10981
|
-
fullPath = sp2.join(this.options.cwd,
|
|
11539
|
+
let fullPath = path8;
|
|
11540
|
+
if (this.options.cwd && !sp2.isAbsolute(path8)) {
|
|
11541
|
+
fullPath = sp2.join(this.options.cwd, path8);
|
|
10982
11542
|
}
|
|
10983
11543
|
const now = /* @__PURE__ */ new Date();
|
|
10984
11544
|
const writes = this._pendingWrites;
|
|
10985
11545
|
function awaitWriteFinishFn(prevStat) {
|
|
10986
11546
|
statcb(fullPath, (err, curStat) => {
|
|
10987
|
-
if (err || !writes.has(
|
|
11547
|
+
if (err || !writes.has(path8)) {
|
|
10988
11548
|
if (err && err.code !== "ENOENT")
|
|
10989
11549
|
awfEmit(err);
|
|
10990
11550
|
return;
|
|
10991
11551
|
}
|
|
10992
11552
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
10993
11553
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
10994
|
-
writes.get(
|
|
11554
|
+
writes.get(path8).lastChange = now2;
|
|
10995
11555
|
}
|
|
10996
|
-
const pw = writes.get(
|
|
11556
|
+
const pw = writes.get(path8);
|
|
10997
11557
|
const df = now2 - pw.lastChange;
|
|
10998
11558
|
if (df >= threshold) {
|
|
10999
|
-
writes.delete(
|
|
11559
|
+
writes.delete(path8);
|
|
11000
11560
|
awfEmit(void 0, curStat);
|
|
11001
11561
|
} else {
|
|
11002
11562
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
11003
11563
|
}
|
|
11004
11564
|
});
|
|
11005
11565
|
}
|
|
11006
|
-
if (!writes.has(
|
|
11007
|
-
writes.set(
|
|
11566
|
+
if (!writes.has(path8)) {
|
|
11567
|
+
writes.set(path8, {
|
|
11008
11568
|
lastChange: now,
|
|
11009
11569
|
cancelWait: () => {
|
|
11010
|
-
writes.delete(
|
|
11570
|
+
writes.delete(path8);
|
|
11011
11571
|
clearTimeout(timeoutHandler);
|
|
11012
11572
|
return event;
|
|
11013
11573
|
}
|
|
@@ -11018,8 +11578,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11018
11578
|
/**
|
|
11019
11579
|
* Determines whether user has asked to ignore this path.
|
|
11020
11580
|
*/
|
|
11021
|
-
_isIgnored(
|
|
11022
|
-
if (this.options.atomic && DOT_RE.test(
|
|
11581
|
+
_isIgnored(path8, stats) {
|
|
11582
|
+
if (this.options.atomic && DOT_RE.test(path8))
|
|
11023
11583
|
return true;
|
|
11024
11584
|
if (!this._userIgnored) {
|
|
11025
11585
|
const { cwd } = this.options;
|
|
@@ -11029,17 +11589,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11029
11589
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
11030
11590
|
this._userIgnored = anymatch(list, void 0);
|
|
11031
11591
|
}
|
|
11032
|
-
return this._userIgnored(
|
|
11592
|
+
return this._userIgnored(path8, stats);
|
|
11033
11593
|
}
|
|
11034
|
-
_isntIgnored(
|
|
11035
|
-
return !this._isIgnored(
|
|
11594
|
+
_isntIgnored(path8, stat4) {
|
|
11595
|
+
return !this._isIgnored(path8, stat4);
|
|
11036
11596
|
}
|
|
11037
11597
|
/**
|
|
11038
11598
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
11039
11599
|
* @param path file or directory pattern being watched
|
|
11040
11600
|
*/
|
|
11041
|
-
_getWatchHelpers(
|
|
11042
|
-
return new WatchHelper(
|
|
11601
|
+
_getWatchHelpers(path8) {
|
|
11602
|
+
return new WatchHelper(path8, this.options.followSymlinks, this);
|
|
11043
11603
|
}
|
|
11044
11604
|
// Directory helpers
|
|
11045
11605
|
// -----------------
|
|
@@ -11071,63 +11631,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11071
11631
|
* @param item base path of item/directory
|
|
11072
11632
|
*/
|
|
11073
11633
|
_remove(directory, item, isDirectory) {
|
|
11074
|
-
const
|
|
11075
|
-
const fullPath = sp2.resolve(
|
|
11076
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
11077
|
-
if (!this._throttle("remove",
|
|
11634
|
+
const path8 = sp2.join(directory, item);
|
|
11635
|
+
const fullPath = sp2.resolve(path8);
|
|
11636
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
|
|
11637
|
+
if (!this._throttle("remove", path8, 100))
|
|
11078
11638
|
return;
|
|
11079
11639
|
if (!isDirectory && this._watched.size === 1) {
|
|
11080
11640
|
this.add(directory, item, true);
|
|
11081
11641
|
}
|
|
11082
|
-
const wp = this._getWatchedDir(
|
|
11642
|
+
const wp = this._getWatchedDir(path8);
|
|
11083
11643
|
const nestedDirectoryChildren = wp.getChildren();
|
|
11084
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
11644
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
|
|
11085
11645
|
const parent = this._getWatchedDir(directory);
|
|
11086
11646
|
const wasTracked = parent.has(item);
|
|
11087
11647
|
parent.remove(item);
|
|
11088
11648
|
if (this._symlinkPaths.has(fullPath)) {
|
|
11089
11649
|
this._symlinkPaths.delete(fullPath);
|
|
11090
11650
|
}
|
|
11091
|
-
let relPath =
|
|
11651
|
+
let relPath = path8;
|
|
11092
11652
|
if (this.options.cwd)
|
|
11093
|
-
relPath = sp2.relative(this.options.cwd,
|
|
11653
|
+
relPath = sp2.relative(this.options.cwd, path8);
|
|
11094
11654
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
11095
11655
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
11096
11656
|
if (event === EVENTS.ADD)
|
|
11097
11657
|
return;
|
|
11098
11658
|
}
|
|
11099
|
-
this._watched.delete(
|
|
11659
|
+
this._watched.delete(path8);
|
|
11100
11660
|
this._watched.delete(fullPath);
|
|
11101
11661
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
11102
|
-
if (wasTracked && !this._isIgnored(
|
|
11103
|
-
this._emit(eventName,
|
|
11104
|
-
this._closePath(
|
|
11662
|
+
if (wasTracked && !this._isIgnored(path8))
|
|
11663
|
+
this._emit(eventName, path8);
|
|
11664
|
+
this._closePath(path8);
|
|
11105
11665
|
}
|
|
11106
11666
|
/**
|
|
11107
11667
|
* Closes all watchers for a path
|
|
11108
11668
|
*/
|
|
11109
|
-
_closePath(
|
|
11110
|
-
this._closeFile(
|
|
11111
|
-
const dir = sp2.dirname(
|
|
11112
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
11669
|
+
_closePath(path8) {
|
|
11670
|
+
this._closeFile(path8);
|
|
11671
|
+
const dir = sp2.dirname(path8);
|
|
11672
|
+
this._getWatchedDir(dir).remove(sp2.basename(path8));
|
|
11113
11673
|
}
|
|
11114
11674
|
/**
|
|
11115
11675
|
* Closes only file-specific watchers
|
|
11116
11676
|
*/
|
|
11117
|
-
_closeFile(
|
|
11118
|
-
const closers = this._closers.get(
|
|
11677
|
+
_closeFile(path8) {
|
|
11678
|
+
const closers = this._closers.get(path8);
|
|
11119
11679
|
if (!closers)
|
|
11120
11680
|
return;
|
|
11121
11681
|
closers.forEach((closer) => closer());
|
|
11122
|
-
this._closers.delete(
|
|
11682
|
+
this._closers.delete(path8);
|
|
11123
11683
|
}
|
|
11124
|
-
_addPathCloser(
|
|
11684
|
+
_addPathCloser(path8, closer) {
|
|
11125
11685
|
if (!closer)
|
|
11126
11686
|
return;
|
|
11127
|
-
let list = this._closers.get(
|
|
11687
|
+
let list = this._closers.get(path8);
|
|
11128
11688
|
if (!list) {
|
|
11129
11689
|
list = [];
|
|
11130
|
-
this._closers.set(
|
|
11690
|
+
this._closers.set(path8, list);
|
|
11131
11691
|
}
|
|
11132
11692
|
list.push(closer);
|
|
11133
11693
|
}
|
|
@@ -11135,18 +11695,18 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11135
11695
|
if (this.closed)
|
|
11136
11696
|
return;
|
|
11137
11697
|
const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
|
|
11138
|
-
let
|
|
11139
|
-
this._streams.add(
|
|
11140
|
-
|
|
11141
|
-
|
|
11698
|
+
let stream2 = readdirp(root, options);
|
|
11699
|
+
this._streams.add(stream2);
|
|
11700
|
+
stream2.once(STR_CLOSE, () => {
|
|
11701
|
+
stream2 = void 0;
|
|
11142
11702
|
});
|
|
11143
|
-
|
|
11144
|
-
if (
|
|
11145
|
-
this._streams.delete(
|
|
11146
|
-
|
|
11703
|
+
stream2.once(STR_END, () => {
|
|
11704
|
+
if (stream2) {
|
|
11705
|
+
this._streams.delete(stream2);
|
|
11706
|
+
stream2 = void 0;
|
|
11147
11707
|
}
|
|
11148
11708
|
});
|
|
11149
|
-
return
|
|
11709
|
+
return stream2;
|
|
11150
11710
|
}
|
|
11151
11711
|
};
|
|
11152
11712
|
function watch(paths, options = {}) {
|
|
@@ -11225,13 +11785,13 @@ function startWatcher(opts) {
|
|
|
11225
11785
|
}
|
|
11226
11786
|
return {
|
|
11227
11787
|
ready,
|
|
11228
|
-
notifyChange(
|
|
11229
|
-
if (watcher2) watcher2.emit("change",
|
|
11230
|
-
else scheduleIngest(
|
|
11788
|
+
notifyChange(path8) {
|
|
11789
|
+
if (watcher2) watcher2.emit("change", path8);
|
|
11790
|
+
else scheduleIngest(path8);
|
|
11231
11791
|
},
|
|
11232
|
-
notifyUnlink(
|
|
11233
|
-
if (watcher2) watcher2.emit("unlink",
|
|
11234
|
-
else recordUnlink(
|
|
11792
|
+
notifyUnlink(path8) {
|
|
11793
|
+
if (watcher2) watcher2.emit("unlink", path8);
|
|
11794
|
+
else recordUnlink(path8);
|
|
11235
11795
|
},
|
|
11236
11796
|
async close() {
|
|
11237
11797
|
closed = true;
|
|
@@ -11243,39 +11803,39 @@ function startWatcher(opts) {
|
|
|
11243
11803
|
}
|
|
11244
11804
|
|
|
11245
11805
|
// src/plugins/claude-code/plugin.ts
|
|
11246
|
-
import
|
|
11247
|
-
import
|
|
11806
|
+
import fs6 from "fs";
|
|
11807
|
+
import path4 from "path";
|
|
11248
11808
|
import readline from "readline";
|
|
11249
11809
|
var ClaudeCodePlugin = class {
|
|
11250
11810
|
id = "claude-code";
|
|
11251
11811
|
displayName = "Claude Code";
|
|
11252
11812
|
async *discover(env) {
|
|
11253
|
-
const projectsDir =
|
|
11254
|
-
if (!
|
|
11255
|
-
const projects =
|
|
11813
|
+
const projectsDir = path4.join(env.homeDir, ".claude", "projects");
|
|
11814
|
+
if (!fs6.existsSync(projectsDir)) return;
|
|
11815
|
+
const projects = fs6.readdirSync(projectsDir, { withFileTypes: true });
|
|
11256
11816
|
for (const project of projects) {
|
|
11257
11817
|
if (!project.isDirectory()) continue;
|
|
11258
|
-
const projectPath =
|
|
11259
|
-
const files =
|
|
11818
|
+
const projectPath = path4.join(projectsDir, project.name);
|
|
11819
|
+
const files = fs6.readdirSync(projectPath, { withFileTypes: true });
|
|
11260
11820
|
for (const file of files) {
|
|
11261
11821
|
if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
|
|
11262
|
-
const sourcePath =
|
|
11822
|
+
const sourcePath = path4.join(projectPath, file.name);
|
|
11263
11823
|
const sourceId = file.name.replace(/\.jsonl$/, "");
|
|
11264
11824
|
const cwd = await readCwdFromJsonl(sourcePath);
|
|
11265
11825
|
yield {
|
|
11266
11826
|
sourceId,
|
|
11267
11827
|
sourcePath,
|
|
11268
11828
|
watchPaths: [sourcePath],
|
|
11269
|
-
project: cwd ?
|
|
11829
|
+
project: cwd ? path4.basename(cwd) : void 0,
|
|
11270
11830
|
projectPath: cwd ?? void 0
|
|
11271
11831
|
};
|
|
11272
11832
|
}
|
|
11273
11833
|
}
|
|
11274
11834
|
}
|
|
11275
11835
|
async *extractRaw(ref) {
|
|
11276
|
-
if (!
|
|
11277
|
-
const
|
|
11278
|
-
const rl = readline.createInterface({ input:
|
|
11836
|
+
if (!fs6.existsSync(ref.sourcePath)) return;
|
|
11837
|
+
const stream2 = fs6.createReadStream(ref.sourcePath, { encoding: "utf-8" });
|
|
11838
|
+
const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
|
|
11279
11839
|
let lineNo = 0;
|
|
11280
11840
|
for await (const line of rl) {
|
|
11281
11841
|
lineNo += 1;
|
|
@@ -11323,8 +11883,8 @@ var ClaudeCodePlugin = class {
|
|
|
11323
11883
|
};
|
|
11324
11884
|
async function readCwdFromJsonl(sourcePath) {
|
|
11325
11885
|
try {
|
|
11326
|
-
const
|
|
11327
|
-
const rl = readline.createInterface({ input:
|
|
11886
|
+
const stream2 = fs6.createReadStream(sourcePath, { encoding: "utf-8" });
|
|
11887
|
+
const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
|
|
11328
11888
|
try {
|
|
11329
11889
|
for await (const line of rl) {
|
|
11330
11890
|
if (!line) continue;
|
|
@@ -11340,7 +11900,7 @@ async function readCwdFromJsonl(sourcePath) {
|
|
|
11340
11900
|
}
|
|
11341
11901
|
} finally {
|
|
11342
11902
|
rl.close();
|
|
11343
|
-
|
|
11903
|
+
stream2.destroy();
|
|
11344
11904
|
}
|
|
11345
11905
|
} catch {
|
|
11346
11906
|
}
|
|
@@ -11429,10 +11989,131 @@ Environment:
|
|
|
11429
11989
|
PORT=<n> Set the HTTP port (overridden by --port)
|
|
11430
11990
|
`;
|
|
11431
11991
|
|
|
11992
|
+
// src/config/data-dir.ts
|
|
11993
|
+
import path5 from "path";
|
|
11994
|
+
function resolveDataDir(env, homeDir) {
|
|
11995
|
+
const override = env.CHAT_LOGBOOK_DATA_DIR?.trim();
|
|
11996
|
+
if (override) {
|
|
11997
|
+
return path5.resolve(override);
|
|
11998
|
+
}
|
|
11999
|
+
return path5.join(homeDir, ".chat-logbook");
|
|
12000
|
+
}
|
|
12001
|
+
|
|
12002
|
+
// src/list-counts.ts
|
|
12003
|
+
import fs7 from "fs";
|
|
12004
|
+
import path6 from "path";
|
|
12005
|
+
import Database3 from "better-sqlite3";
|
|
12006
|
+
var ARCHIVE_DB2 = "archive.db";
|
|
12007
|
+
var METADATA_DB2 = "metadata.db";
|
|
12008
|
+
function createChatCountsQuery({
|
|
12009
|
+
dataDir: dataDir2
|
|
12010
|
+
}) {
|
|
12011
|
+
const archive2 = new Database3(path6.join(dataDir2, ARCHIVE_DB2), {
|
|
12012
|
+
readonly: true
|
|
12013
|
+
});
|
|
12014
|
+
const metadataPath = path6.join(dataDir2, METADATA_DB2);
|
|
12015
|
+
const hasMetadata = fs7.existsSync(metadataPath);
|
|
12016
|
+
if (hasMetadata) {
|
|
12017
|
+
archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
|
|
12018
|
+
}
|
|
12019
|
+
function queryCounts({
|
|
12020
|
+
includeTrashed = false
|
|
12021
|
+
}) {
|
|
12022
|
+
const viewClause = viewPredicate(includeTrashed, hasMetadata);
|
|
12023
|
+
const total = archive2.prepare(`SELECT count(*) AS n FROM chats c ${viewClause}`).get().n;
|
|
12024
|
+
const projects = archive2.prepare(
|
|
12025
|
+
`SELECT coalesce(c.project, '') AS project,
|
|
12026
|
+
count(*) AS count,
|
|
12027
|
+
max(c.updated_at) AS lastActiveAt
|
|
12028
|
+
FROM chats c ${viewClause}
|
|
12029
|
+
GROUP BY coalesce(c.project, '')`
|
|
12030
|
+
).all();
|
|
12031
|
+
const tags3 = hasMetadata ? archive2.prepare(
|
|
12032
|
+
`SELECT ct.tag_id AS tagId, count(*) AS count
|
|
12033
|
+
FROM meta.chat_tags ct
|
|
12034
|
+
JOIN chats c ON c.id = ct.chat_id
|
|
12035
|
+
${viewClause}
|
|
12036
|
+
GROUP BY ct.tag_id`
|
|
12037
|
+
).all() : [];
|
|
12038
|
+
const untagged = hasMetadata ? archive2.prepare(
|
|
12039
|
+
`SELECT count(*) AS n
|
|
12040
|
+
FROM chats c ${viewClause}
|
|
12041
|
+
AND c.id NOT IN (SELECT chat_id FROM meta.chat_tags)`
|
|
12042
|
+
).get().n : total;
|
|
12043
|
+
return { total, projects, tags: tags3, untagged };
|
|
12044
|
+
}
|
|
12045
|
+
function queryFilteredTotal({
|
|
12046
|
+
includeTrashed = false,
|
|
12047
|
+
projects,
|
|
12048
|
+
tags: tags3
|
|
12049
|
+
}) {
|
|
12050
|
+
const clauses = [];
|
|
12051
|
+
const params = [];
|
|
12052
|
+
if (hasMetadata) {
|
|
12053
|
+
clauses.push(
|
|
12054
|
+
includeTrashed ? "c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)" : "c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
12055
|
+
);
|
|
12056
|
+
} else if (includeTrashed) {
|
|
12057
|
+
return 0;
|
|
12058
|
+
}
|
|
12059
|
+
const filter = buildFilterClauses({ projects, tags: tags3, hasMetadata });
|
|
12060
|
+
clauses.push(...filter.clauses);
|
|
12061
|
+
params.push(...filter.params);
|
|
12062
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
12063
|
+
return archive2.prepare(`SELECT count(*) AS n FROM chats c ${where}`).get(...params).n;
|
|
12064
|
+
}
|
|
12065
|
+
return {
|
|
12066
|
+
queryCounts,
|
|
12067
|
+
queryFilteredTotal,
|
|
12068
|
+
close() {
|
|
12069
|
+
archive2.close();
|
|
12070
|
+
}
|
|
12071
|
+
};
|
|
12072
|
+
}
|
|
12073
|
+
function viewPredicate(includeTrashed, hasMetadata) {
|
|
12074
|
+
if (!hasMetadata) {
|
|
12075
|
+
return includeTrashed ? "WHERE 0" : "";
|
|
12076
|
+
}
|
|
12077
|
+
return includeTrashed ? "WHERE c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)" : "WHERE c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)";
|
|
12078
|
+
}
|
|
12079
|
+
|
|
12080
|
+
// src/list-events.ts
|
|
12081
|
+
function createListEventHub() {
|
|
12082
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12083
|
+
return {
|
|
12084
|
+
publish(event) {
|
|
12085
|
+
for (const listener of listeners) listener(event);
|
|
12086
|
+
},
|
|
12087
|
+
subscribe(listener) {
|
|
12088
|
+
listeners.add(listener);
|
|
12089
|
+
return () => {
|
|
12090
|
+
listeners.delete(listener);
|
|
12091
|
+
};
|
|
12092
|
+
}
|
|
12093
|
+
};
|
|
12094
|
+
}
|
|
12095
|
+
|
|
12096
|
+
// src/metadata/reconcile-title-sort-keys.ts
|
|
12097
|
+
function reconcileTitleSortKeys({
|
|
12098
|
+
archive: archive2,
|
|
12099
|
+
metadata: metadata2
|
|
12100
|
+
}) {
|
|
12101
|
+
const key = (agent, sourceId) => [agent, sourceId].join("\0");
|
|
12102
|
+
const firstTextByKey = new Map(
|
|
12103
|
+
archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
|
|
12104
|
+
);
|
|
12105
|
+
for (const row of archive2.read.listChatRows()) {
|
|
12106
|
+
const baseTitle = deriveBaseTitle(
|
|
12107
|
+
firstTextByKey.get(key(row.agent, row.sourceId))
|
|
12108
|
+
);
|
|
12109
|
+
metadata2.setTitleTextKey(row.id, computeSortKey(baseTitle));
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
|
|
11432
12113
|
// src/index.ts
|
|
11433
|
-
var __dirname =
|
|
11434
|
-
var pkgPath =
|
|
11435
|
-
var pkg = JSON.parse(
|
|
12114
|
+
var __dirname = path7.dirname(fileURLToPath2(import.meta.url));
|
|
12115
|
+
var pkgPath = path7.join(__dirname, "../../package.json");
|
|
12116
|
+
var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
11436
12117
|
var action = parseCliArgs(process.argv.slice(2), {
|
|
11437
12118
|
PORT: process.env.PORT
|
|
11438
12119
|
});
|
|
@@ -11449,27 +12130,58 @@ if (action.kind === "error") {
|
|
|
11449
12130
|
process.exit(1);
|
|
11450
12131
|
}
|
|
11451
12132
|
updateNotifier({ pkg }).notify({ defer: false, isGlobal: true });
|
|
11452
|
-
var dataDir =
|
|
11453
|
-
|
|
12133
|
+
var dataDir = resolveDataDir(
|
|
12134
|
+
{ CHAT_LOGBOOK_DATA_DIR: process.env.CHAT_LOGBOOK_DATA_DIR },
|
|
12135
|
+
os.homedir()
|
|
12136
|
+
);
|
|
12137
|
+
var webDistDir = path7.join(__dirname, "../../web/dist");
|
|
11454
12138
|
var port = action.port;
|
|
11455
12139
|
var archive = createArchiveRepository({ dataDir });
|
|
11456
12140
|
var checkpoint = createCheckpointRepository({ dataDir });
|
|
11457
12141
|
var metadata = createMetadataRepository({ dataDir });
|
|
11458
12142
|
var tags2 = createTagRepository({ dataDir });
|
|
11459
|
-
var
|
|
12143
|
+
var pageQuery = createChatPageQuery({ dataDir });
|
|
12144
|
+
var countsQuery = createChatCountsQuery({ dataDir });
|
|
12145
|
+
var listEvents = createListEventHub();
|
|
12146
|
+
var app = createApp({
|
|
12147
|
+
archive,
|
|
12148
|
+
metadata,
|
|
12149
|
+
tags: tags2,
|
|
12150
|
+
pageQuery,
|
|
12151
|
+
countsQuery,
|
|
12152
|
+
listEvents,
|
|
12153
|
+
webDistDir
|
|
12154
|
+
});
|
|
11460
12155
|
var initialIngest = startIngestionInBackground({
|
|
11461
12156
|
plugins,
|
|
11462
12157
|
archive,
|
|
11463
12158
|
checkpoint,
|
|
11464
12159
|
env: { homeDir: os.homedir() }
|
|
11465
12160
|
});
|
|
12161
|
+
function reconcileTitles() {
|
|
12162
|
+
try {
|
|
12163
|
+
reconcileTitleSortKeys({ archive, metadata });
|
|
12164
|
+
} catch (err) {
|
|
12165
|
+
console.error("[title-sort-key] reconcile failed:", err);
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
11466
12168
|
var watcher = startWatcher({
|
|
11467
12169
|
plugins,
|
|
11468
12170
|
archive,
|
|
11469
12171
|
checkpoint,
|
|
11470
|
-
env: { homeDir: os.homedir() }
|
|
12172
|
+
env: { homeDir: os.homedir() },
|
|
12173
|
+
// Each watcher-driven ingest can add a chat or change a first user message;
|
|
12174
|
+
// refresh the Title keys so the Title axis stays current, then push a
|
|
12175
|
+
// `changed` event so connected clients reconcile their loaded window (#132).
|
|
12176
|
+
onIngest: () => {
|
|
12177
|
+
reconcileTitles();
|
|
12178
|
+
listEvents.publish({ type: "changed" });
|
|
12179
|
+
}
|
|
11471
12180
|
});
|
|
11472
|
-
void initialIngest.done.then(() =>
|
|
12181
|
+
void initialIngest.done.then(() => {
|
|
12182
|
+
reconcileTitles();
|
|
12183
|
+
return watcher.ready;
|
|
12184
|
+
}).catch(() => {
|
|
11473
12185
|
});
|
|
11474
12186
|
function shutdown() {
|
|
11475
12187
|
void watcher.close().finally(() => process.exit(0));
|