chat-logbook 0.13.0 → 0.15.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 +4 -3
- 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 +1077 -373
- package/package.json +1 -1
- package/web/dist/assets/index-CSvjgIUr.js +57 -0
- package/web/dist/assets/index-DcsvYh-v.css +2 -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,165 @@ 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
|
+
tagMode = "all",
|
|
3092
|
+
hasMetadata
|
|
3093
|
+
}) {
|
|
3094
|
+
const clauses = [];
|
|
3095
|
+
const params = [];
|
|
3096
|
+
if (projects && projects.length > 0) {
|
|
3097
|
+
const placeholders = projects.map(() => "?").join(", ");
|
|
3098
|
+
clauses.push(`coalesce(c.project, '') IN (${placeholders})`);
|
|
3099
|
+
params.push(...projects);
|
|
3100
|
+
}
|
|
3101
|
+
if (tags3 && tags3.length > 0) {
|
|
3102
|
+
const realTagIds = tags3.filter((t) => t !== "");
|
|
3103
|
+
const wantUntagged = tags3.includes("");
|
|
3104
|
+
let realFragment = null;
|
|
3105
|
+
if (realTagIds.length > 0) {
|
|
3106
|
+
if (!hasMetadata) {
|
|
3107
|
+
realFragment = "0";
|
|
3108
|
+
} else {
|
|
3109
|
+
const placeholders = realTagIds.map(() => "?").join(", ");
|
|
3110
|
+
if (tagMode === "any") {
|
|
3111
|
+
realFragment = `c.id IN (SELECT DISTINCT chat_id FROM meta.chat_tags
|
|
3112
|
+
WHERE tag_id IN (${placeholders}))`;
|
|
3113
|
+
params.push(...realTagIds);
|
|
3114
|
+
} else {
|
|
3115
|
+
realFragment = `c.id IN (SELECT chat_id FROM meta.chat_tags
|
|
3116
|
+
WHERE tag_id IN (${placeholders})
|
|
3117
|
+
GROUP BY chat_id HAVING count(*) = ?)`;
|
|
3118
|
+
params.push(...realTagIds, realTagIds.length);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
}
|
|
3122
|
+
const untaggedFragment = wantUntagged && hasMetadata ? "c.id NOT IN (SELECT chat_id FROM meta.chat_tags)" : null;
|
|
3123
|
+
if (tagMode === "any" && realFragment && untaggedFragment) {
|
|
3124
|
+
clauses.push(`(${realFragment} OR ${untaggedFragment})`);
|
|
3125
|
+
} else {
|
|
3126
|
+
if (realFragment) clauses.push(realFragment);
|
|
3127
|
+
if (untaggedFragment) clauses.push(untaggedFragment);
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return { clauses, params };
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
// src/list-pagination.ts
|
|
3134
|
+
var ARCHIVE_DB = "archive.db";
|
|
3135
|
+
var METADATA_DB = "metadata.db";
|
|
3136
|
+
var SORT_EXPR = {
|
|
3137
|
+
createdAt: "c.created_at",
|
|
3138
|
+
updatedAt: "c.updated_at",
|
|
3139
|
+
deletedAt: "m.deleted_at",
|
|
3140
|
+
// The Title axis orders by the precomputed collation key on the ATTACHed
|
|
3141
|
+
// `meta.chat_sort_keys` (alias `k`), reached through the INNER JOIN added
|
|
3142
|
+
// below (ADR-0019). BINARY compare over `sort_key` is an index range scan.
|
|
3143
|
+
title: "k.sort_key"
|
|
3144
|
+
};
|
|
3145
|
+
function encodeCursor(cursor) {
|
|
3146
|
+
return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
|
|
3147
|
+
}
|
|
3148
|
+
function decodeCursor(token) {
|
|
3149
|
+
try {
|
|
3150
|
+
const parsed = JSON.parse(
|
|
3151
|
+
Buffer.from(token, "base64url").toString("utf8")
|
|
3152
|
+
);
|
|
3153
|
+
const sortKey = parsed?.sortKey;
|
|
3154
|
+
if (parsed && typeof parsed === "object" && (typeof sortKey === "number" || typeof sortKey === "string") && typeof parsed.id === "string") {
|
|
3155
|
+
return parsed;
|
|
3156
|
+
}
|
|
3157
|
+
return null;
|
|
3158
|
+
} catch {
|
|
3159
|
+
return null;
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
function createChatPageQuery({
|
|
3163
|
+
dataDir: dataDir2
|
|
3164
|
+
}) {
|
|
3165
|
+
const archive2 = new Database(path.join(dataDir2, ARCHIVE_DB), {
|
|
3166
|
+
readonly: true
|
|
3167
|
+
});
|
|
3168
|
+
const metadataPath = path.join(dataDir2, METADATA_DB);
|
|
3169
|
+
const hasMetadata = fs.existsSync(metadataPath);
|
|
3170
|
+
if (hasMetadata) {
|
|
3171
|
+
archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
|
|
3172
|
+
}
|
|
3173
|
+
function queryPage(query) {
|
|
3174
|
+
const sortExpr = SORT_EXPR[query.sort];
|
|
3175
|
+
const needsDeletedJoin = query.sort === "deletedAt";
|
|
3176
|
+
const needsSortKeyJoin = query.sort === "title";
|
|
3177
|
+
if ((needsDeletedJoin || needsSortKeyJoin) && !hasMetadata) {
|
|
3178
|
+
return { items: [], nextCursor: null };
|
|
3179
|
+
}
|
|
3180
|
+
let from = "chats c";
|
|
3181
|
+
if (needsDeletedJoin) from += " JOIN meta.chats_meta m ON m.id = c.id";
|
|
3182
|
+
if (needsSortKeyJoin) from += " JOIN meta.chat_sort_keys k ON k.id = c.id";
|
|
3183
|
+
const idCol = needsDeletedJoin ? "m.id" : needsSortKeyJoin ? "k.id" : "c.id";
|
|
3184
|
+
const clauses = [];
|
|
3185
|
+
const params = [];
|
|
3186
|
+
if (needsDeletedJoin) {
|
|
3187
|
+
clauses.push("m.deleted_at IS NOT NULL");
|
|
3188
|
+
} else if (query.trashedOnly) {
|
|
3189
|
+
if (!hasMetadata) return { items: [], nextCursor: null };
|
|
3190
|
+
clauses.push(
|
|
3191
|
+
"c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
3192
|
+
);
|
|
3193
|
+
} else if (!query.includeTrashed && hasMetadata) {
|
|
3194
|
+
clauses.push(
|
|
3195
|
+
"c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
3196
|
+
);
|
|
3197
|
+
}
|
|
3198
|
+
const filter = buildFilterClauses({
|
|
3199
|
+
projects: query.projects,
|
|
3200
|
+
tags: query.tags,
|
|
3201
|
+
tagMode: query.tagMode,
|
|
3202
|
+
hasMetadata
|
|
3203
|
+
});
|
|
3204
|
+
clauses.push(...filter.clauses);
|
|
3205
|
+
params.push(...filter.params);
|
|
3206
|
+
const direction = query.direction ?? "desc";
|
|
3207
|
+
const cmp = direction === "asc" ? ">" : "<";
|
|
3208
|
+
const order = direction === "asc" ? "ASC" : "DESC";
|
|
3209
|
+
if (query.cursor) {
|
|
3210
|
+
clauses.push(
|
|
3211
|
+
`(${sortExpr} ${cmp} ? OR (${sortExpr} = ? AND ${idCol} ${cmp} ?))`
|
|
3212
|
+
);
|
|
3213
|
+
params.push(query.cursor.sortKey, query.cursor.sortKey, query.cursor.id);
|
|
3214
|
+
}
|
|
3215
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
3216
|
+
const rows = archive2.prepare(
|
|
3217
|
+
`SELECT c.id AS id, ${sortExpr} AS sortKey
|
|
3218
|
+
FROM ${from} ${where}
|
|
3219
|
+
ORDER BY ${sortExpr} ${order}, ${idCol} ${order}
|
|
3220
|
+
LIMIT ?`
|
|
3221
|
+
).all(...params, query.limit + 1);
|
|
3222
|
+
const items = rows.slice(0, query.limit);
|
|
3223
|
+
const last = items[items.length - 1];
|
|
3224
|
+
const nextCursor = rows.length > query.limit && last ? { sortKey: last.sortKey, id: last.id } : null;
|
|
3225
|
+
return { items, nextCursor };
|
|
3226
|
+
}
|
|
3227
|
+
return {
|
|
3228
|
+
queryPage,
|
|
3229
|
+
close() {
|
|
3230
|
+
archive2.close();
|
|
3231
|
+
}
|
|
3232
|
+
};
|
|
3233
|
+
}
|
|
3234
|
+
|
|
2927
3235
|
// src/chat-reader.ts
|
|
2928
3236
|
function toApiBlock(block) {
|
|
2929
3237
|
if (block.type === "tool_result") {
|
|
@@ -2938,7 +3246,9 @@ function toApiBlock(block) {
|
|
|
2938
3246
|
function createChatReader({
|
|
2939
3247
|
archive: archive2,
|
|
2940
3248
|
metadata: metadata2,
|
|
2941
|
-
tags: tags3
|
|
3249
|
+
tags: tags3,
|
|
3250
|
+
pageQuery: pageQuery2,
|
|
3251
|
+
countsQuery: countsQuery2
|
|
2942
3252
|
}) {
|
|
2943
3253
|
function findChat(id) {
|
|
2944
3254
|
const code = parseChatId(id);
|
|
@@ -2947,47 +3257,24 @@ function createChatReader({
|
|
|
2947
3257
|
}
|
|
2948
3258
|
function deriveTitle(customTitle, firstUserText) {
|
|
2949
3259
|
if (customTitle && customTitle.trim()) return customTitle;
|
|
2950
|
-
|
|
2951
|
-
return text2 && text2.length > 0 ? text2 : "Untitled";
|
|
3260
|
+
return deriveBaseTitle(firstUserText);
|
|
2952
3261
|
}
|
|
2953
3262
|
function key(agent, sourceId) {
|
|
2954
3263
|
return [agent, sourceId].join("\0");
|
|
2955
3264
|
}
|
|
2956
|
-
function
|
|
2957
|
-
includeTrashed,
|
|
2958
|
-
projects,
|
|
2959
|
-
tags: tagSelection
|
|
2960
|
-
}) {
|
|
2961
|
-
const visibility = loadChatVisibility(metadata2, { includeTrashed });
|
|
2962
|
-
const rows = archive2.read.listChatRows(projects ? { projects } : void 0);
|
|
2963
|
-
let passesTagFilter = null;
|
|
2964
|
-
if (tagSelection) {
|
|
2965
|
-
const realTagIds = tagSelection.filter((t) => t !== "");
|
|
2966
|
-
const wantUntagged = tagSelection.includes("");
|
|
2967
|
-
const allowedByTags = realTagIds.length > 0 ? new Set(tags3.listChatIdsWithAllTags(realTagIds)) : null;
|
|
2968
|
-
const taggedChatIds = wantUntagged ? new Set(tags3.listTagsByChat().keys()) : null;
|
|
2969
|
-
passesTagFilter = (chatInternalId) => {
|
|
2970
|
-
if (allowedByTags && !allowedByTags.has(chatInternalId)) return false;
|
|
2971
|
-
if (taggedChatIds && taggedChatIds.has(chatInternalId)) return false;
|
|
2972
|
-
return true;
|
|
2973
|
-
};
|
|
2974
|
-
}
|
|
3265
|
+
function loadHydration(scope) {
|
|
2975
3266
|
const tsRangeByKey = new Map(
|
|
2976
|
-
archive2.read.listChatTsRanges().map((r) => [key(r.agent, r.sourceId), r])
|
|
3267
|
+
archive2.read.listChatTsRanges(scope).map((r) => [key(r.agent, r.sourceId), r])
|
|
2977
3268
|
);
|
|
2978
3269
|
const sourcePathByKey = new Map(
|
|
2979
|
-
archive2.read.listLatestRawSourcePaths().map((r) => [key(r.agent, r.sourceId), r.sourcePath])
|
|
3270
|
+
archive2.read.listLatestRawSourcePaths(scope).map((r) => [key(r.agent, r.sourceId), r.sourcePath])
|
|
2980
3271
|
);
|
|
2981
3272
|
const firstUserTextByKey = new Map(
|
|
2982
|
-
archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
|
|
3273
|
+
archive2.read.listFirstUserTexts(scope).map((r) => [key(r.agent, r.sourceId), r.text])
|
|
2983
3274
|
);
|
|
2984
3275
|
const customTitleById = metadata2.listCustomTitles();
|
|
2985
3276
|
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);
|
|
3277
|
+
return (row, visibility) => {
|
|
2991
3278
|
const rowKey = key(row.agent, row.sourceId);
|
|
2992
3279
|
const tsRange = tsRangeByKey.get(rowKey);
|
|
2993
3280
|
const firstSeenAtMs = row.firstSeenAt.getTime();
|
|
@@ -3007,10 +3294,78 @@ function createChatReader({
|
|
|
3007
3294
|
deletedAt: visibility.deletedAt(row.id),
|
|
3008
3295
|
tags: tagsByChatId.get(row.id) ?? []
|
|
3009
3296
|
};
|
|
3010
|
-
if (
|
|
3011
|
-
|
|
3297
|
+
if (visibility.isTrashed(row.id)) chat.isDeleted = true;
|
|
3298
|
+
return chat;
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
function listChatsPage({
|
|
3302
|
+
sort,
|
|
3303
|
+
direction,
|
|
3304
|
+
limit,
|
|
3305
|
+
cursor,
|
|
3306
|
+
includeTrashed = false,
|
|
3307
|
+
trashedOnly = false,
|
|
3308
|
+
projects,
|
|
3309
|
+
tags: tagSelection,
|
|
3310
|
+
tagMode
|
|
3311
|
+
}) {
|
|
3312
|
+
if (!pageQuery2) {
|
|
3313
|
+
throw new Error("listChatsPage requires a pageQuery dependency");
|
|
3314
|
+
}
|
|
3315
|
+
const decoded = cursor ? decodeCursor(cursor) ?? void 0 : void 0;
|
|
3316
|
+
const page = pageQuery2.queryPage({
|
|
3317
|
+
sort,
|
|
3318
|
+
direction,
|
|
3319
|
+
limit,
|
|
3320
|
+
cursor: decoded,
|
|
3321
|
+
includeTrashed,
|
|
3322
|
+
trashedOnly,
|
|
3323
|
+
projects,
|
|
3324
|
+
tags: tagSelection,
|
|
3325
|
+
tagMode
|
|
3326
|
+
});
|
|
3327
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
3328
|
+
includeTrashed: includeTrashed || trashedOnly
|
|
3329
|
+
});
|
|
3330
|
+
const pageIds = page.items.map((item) => item.id);
|
|
3331
|
+
const rows = archive2.read.listChatRowsByIds(pageIds);
|
|
3332
|
+
const rowById = new Map(rows.map((r) => [r.id, r]));
|
|
3333
|
+
const toResponse = loadHydration({
|
|
3334
|
+
sourceIds: rows.map((r) => r.sourceId)
|
|
3335
|
+
});
|
|
3336
|
+
const chats2 = [];
|
|
3337
|
+
for (const item of page.items) {
|
|
3338
|
+
const row = rowById.get(item.id);
|
|
3339
|
+
if (row) chats2.push(toResponse(row, visibility));
|
|
3340
|
+
}
|
|
3341
|
+
return {
|
|
3342
|
+
chats: chats2,
|
|
3343
|
+
nextCursor: page.nextCursor ? encodeCursor(page.nextCursor) : null
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
function listCounts({
|
|
3347
|
+
includeTrashed = false
|
|
3348
|
+
}) {
|
|
3349
|
+
if (!countsQuery2) {
|
|
3350
|
+
throw new Error("listCounts requires a countsQuery dependency");
|
|
3351
|
+
}
|
|
3352
|
+
return countsQuery2.queryCounts({ includeTrashed });
|
|
3353
|
+
}
|
|
3354
|
+
function listFilteredTotal({
|
|
3355
|
+
includeTrashed = false,
|
|
3356
|
+
projects,
|
|
3357
|
+
tags: tags4,
|
|
3358
|
+
tagMode
|
|
3359
|
+
}) {
|
|
3360
|
+
if (!countsQuery2) {
|
|
3361
|
+
throw new Error("listFilteredTotal requires a countsQuery dependency");
|
|
3012
3362
|
}
|
|
3013
|
-
return
|
|
3363
|
+
return countsQuery2.queryFilteredTotal({
|
|
3364
|
+
includeTrashed,
|
|
3365
|
+
projects,
|
|
3366
|
+
tags: tags4,
|
|
3367
|
+
tagMode
|
|
3368
|
+
});
|
|
3014
3369
|
}
|
|
3015
3370
|
function getMessages(id, { includeTrashed }) {
|
|
3016
3371
|
const row = findChat(id);
|
|
@@ -3024,23 +3379,126 @@ function createChatReader({
|
|
|
3024
3379
|
timestamp: m.ts.toISOString()
|
|
3025
3380
|
}));
|
|
3026
3381
|
}
|
|
3027
|
-
return {
|
|
3382
|
+
return {
|
|
3383
|
+
listChatsPage,
|
|
3384
|
+
listCounts,
|
|
3385
|
+
listFilteredTotal,
|
|
3386
|
+
getMessages,
|
|
3387
|
+
findChat
|
|
3388
|
+
};
|
|
3028
3389
|
}
|
|
3029
3390
|
|
|
3391
|
+
// src/list-contract.ts
|
|
3392
|
+
var MAX_PAGE_LIMIT = 200;
|
|
3393
|
+
|
|
3030
3394
|
// src/app.ts
|
|
3031
|
-
|
|
3395
|
+
var STREAM_HEARTBEAT_MS = 25e3;
|
|
3396
|
+
function createApp({
|
|
3397
|
+
archive: archive2,
|
|
3398
|
+
metadata: metadata2,
|
|
3399
|
+
tags: tags3,
|
|
3400
|
+
pageQuery: pageQuery2,
|
|
3401
|
+
countsQuery: countsQuery2,
|
|
3402
|
+
listEvents: listEvents2,
|
|
3403
|
+
webDistDir: webDistDir2
|
|
3404
|
+
}) {
|
|
3032
3405
|
const app2 = new Hono2();
|
|
3033
|
-
const reader = createChatReader({
|
|
3406
|
+
const reader = createChatReader({
|
|
3407
|
+
archive: archive2,
|
|
3408
|
+
metadata: metadata2,
|
|
3409
|
+
tags: tags3,
|
|
3410
|
+
pageQuery: pageQuery2,
|
|
3411
|
+
countsQuery: countsQuery2
|
|
3412
|
+
});
|
|
3034
3413
|
app2.get("/api/chats", (c) => {
|
|
3414
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3415
|
+
const limitParam = c.req.query("limit");
|
|
3416
|
+
if (limitParam === void 0) {
|
|
3417
|
+
return c.json({ error: "Missing limit" }, 400);
|
|
3418
|
+
}
|
|
3419
|
+
if (!pageQuery2) {
|
|
3420
|
+
return c.json({ error: "Pagination is not available" }, 501);
|
|
3421
|
+
}
|
|
3422
|
+
const limit = Number.parseInt(limitParam, 10);
|
|
3423
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_PAGE_LIMIT) {
|
|
3424
|
+
return c.json({ error: "Invalid limit" }, 400);
|
|
3425
|
+
}
|
|
3426
|
+
const sort = c.req.query("sort") ?? "updatedAt";
|
|
3427
|
+
if (sort !== "createdAt" && sort !== "updatedAt" && sort !== "deletedAt" && sort !== "title") {
|
|
3428
|
+
return c.json({ error: "Invalid sort" }, 400);
|
|
3429
|
+
}
|
|
3430
|
+
const direction = c.req.query("direction") ?? "desc";
|
|
3431
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
3432
|
+
return c.json({ error: "Invalid direction" }, 400);
|
|
3433
|
+
}
|
|
3434
|
+
const trashedOnly = c.req.query("trashedOnly") === "true";
|
|
3435
|
+
const projects = c.req.queries("project");
|
|
3436
|
+
const tagsParam = c.req.query("tags");
|
|
3437
|
+
const tagSelection = tagsParam === void 0 ? void 0 : tagsParam.split(",");
|
|
3438
|
+
const tagMode = c.req.query("tagMode") ?? "all";
|
|
3439
|
+
if (tagMode !== "all" && tagMode !== "any") {
|
|
3440
|
+
return c.json({ error: "Invalid tagMode" }, 400);
|
|
3441
|
+
}
|
|
3442
|
+
const { chats: chats2, nextCursor } = reader.listChatsPage({
|
|
3443
|
+
sort,
|
|
3444
|
+
direction,
|
|
3445
|
+
limit,
|
|
3446
|
+
cursor: c.req.query("cursor"),
|
|
3447
|
+
includeTrashed,
|
|
3448
|
+
trashedOnly,
|
|
3449
|
+
projects,
|
|
3450
|
+
tags: tagSelection,
|
|
3451
|
+
tagMode
|
|
3452
|
+
});
|
|
3453
|
+
return c.json({ chats: chats2, nextCursor });
|
|
3454
|
+
});
|
|
3455
|
+
app2.get("/api/chats/stream", (c) => {
|
|
3456
|
+
if (!listEvents2) {
|
|
3457
|
+
return c.json({ error: "Live updates are not available" }, 501);
|
|
3458
|
+
}
|
|
3459
|
+
const hub = listEvents2;
|
|
3460
|
+
return streamSSE(c, async (stream2) => {
|
|
3461
|
+
const unsubscribe = hub.subscribe((event) => {
|
|
3462
|
+
void stream2.writeSSE({ event: event.type, data: "" });
|
|
3463
|
+
});
|
|
3464
|
+
await new Promise((resolve3) => {
|
|
3465
|
+
const heartbeat = setInterval(() => {
|
|
3466
|
+
void stream2.writeSSE({ event: "ping", data: "" });
|
|
3467
|
+
}, STREAM_HEARTBEAT_MS);
|
|
3468
|
+
stream2.onAbort(() => {
|
|
3469
|
+
clearInterval(heartbeat);
|
|
3470
|
+
unsubscribe();
|
|
3471
|
+
resolve3();
|
|
3472
|
+
});
|
|
3473
|
+
});
|
|
3474
|
+
});
|
|
3475
|
+
});
|
|
3476
|
+
app2.get("/api/chats/counts", (c) => {
|
|
3477
|
+
if (!countsQuery2) {
|
|
3478
|
+
return c.json({ error: "Counts are not available" }, 501);
|
|
3479
|
+
}
|
|
3480
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3481
|
+
return c.json(reader.listCounts({ includeTrashed }));
|
|
3482
|
+
});
|
|
3483
|
+
app2.get("/api/chats/list-total", (c) => {
|
|
3484
|
+
if (!countsQuery2) {
|
|
3485
|
+
return c.json({ error: "Counts are not available" }, 501);
|
|
3486
|
+
}
|
|
3487
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3035
3488
|
const projects = c.req.queries("project");
|
|
3036
3489
|
const tagsParam = c.req.query("tags");
|
|
3037
3490
|
const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
|
|
3038
|
-
const
|
|
3039
|
-
|
|
3491
|
+
const tagMode = c.req.query("tagMode") ?? "all";
|
|
3492
|
+
if (tagMode !== "all" && tagMode !== "any") {
|
|
3493
|
+
return c.json({ error: "Invalid tagMode" }, 400);
|
|
3494
|
+
}
|
|
3495
|
+
const total = reader.listFilteredTotal({
|
|
3496
|
+
includeTrashed,
|
|
3040
3497
|
projects,
|
|
3041
|
-
tags: tags4
|
|
3498
|
+
tags: tags4,
|
|
3499
|
+
tagMode
|
|
3042
3500
|
});
|
|
3043
|
-
return c.json({
|
|
3501
|
+
return c.json({ total });
|
|
3044
3502
|
});
|
|
3045
3503
|
app2.delete("/api/chats/:id", (c) => {
|
|
3046
3504
|
const row = reader.findChat(c.req.param("id"));
|
|
@@ -4491,7 +4949,7 @@ var QueryPromise = class {
|
|
|
4491
4949
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4492
4950
|
const nullifyMap = {};
|
|
4493
4951
|
const result = columns.reduce(
|
|
4494
|
-
(result2, { path:
|
|
4952
|
+
(result2, { path: path8, field }, columnIndex) => {
|
|
4495
4953
|
let decoder;
|
|
4496
4954
|
if (is(field, Column)) {
|
|
4497
4955
|
decoder = field;
|
|
@@ -4503,8 +4961,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4503
4961
|
decoder = field.sql.decoder;
|
|
4504
4962
|
}
|
|
4505
4963
|
let node = result2;
|
|
4506
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4507
|
-
if (pathChunkIndex <
|
|
4964
|
+
for (const [pathChunkIndex, pathChunk] of path8.entries()) {
|
|
4965
|
+
if (pathChunkIndex < path8.length - 1) {
|
|
4508
4966
|
if (!(pathChunk in node)) {
|
|
4509
4967
|
node[pathChunk] = {};
|
|
4510
4968
|
}
|
|
@@ -4512,8 +4970,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4512
4970
|
} else {
|
|
4513
4971
|
const rawValue = row[columnIndex];
|
|
4514
4972
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4515
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4516
|
-
const objectName =
|
|
4973
|
+
if (joinsNotNullableMap && is(field, Column) && path8.length === 2) {
|
|
4974
|
+
const objectName = path8[0];
|
|
4517
4975
|
if (!(objectName in nullifyMap)) {
|
|
4518
4976
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4519
4977
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -5073,10 +5531,10 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
5073
5531
|
}
|
|
5074
5532
|
|
|
5075
5533
|
// src/storage/openStore.ts
|
|
5076
|
-
import
|
|
5077
|
-
import
|
|
5534
|
+
import fs3 from "fs";
|
|
5535
|
+
import path2 from "path";
|
|
5078
5536
|
import { fileURLToPath } from "url";
|
|
5079
|
-
import
|
|
5537
|
+
import Database2 from "better-sqlite3";
|
|
5080
5538
|
|
|
5081
5539
|
// ../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
5540
|
import Client from "better-sqlite3";
|
|
@@ -8683,20 +9141,20 @@ function drizzle(...params) {
|
|
|
8683
9141
|
|
|
8684
9142
|
// ../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
9143
|
import crypto4 from "crypto";
|
|
8686
|
-
import
|
|
9144
|
+
import fs2 from "fs";
|
|
8687
9145
|
function readMigrationFiles(config) {
|
|
8688
9146
|
const migrationFolderTo = config.migrationsFolder;
|
|
8689
9147
|
const migrationQueries = [];
|
|
8690
9148
|
const journalPath = `${migrationFolderTo}/meta/_journal.json`;
|
|
8691
|
-
if (!
|
|
9149
|
+
if (!fs2.existsSync(journalPath)) {
|
|
8692
9150
|
throw new Error(`Can't find meta/_journal.json file`);
|
|
8693
9151
|
}
|
|
8694
|
-
const journalAsString =
|
|
9152
|
+
const journalAsString = fs2.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
|
|
8695
9153
|
const journal = JSON.parse(journalAsString);
|
|
8696
9154
|
for (const journalEntry of journal.entries) {
|
|
8697
9155
|
const migrationPath = `${migrationFolderTo}/${journalEntry.tag}.sql`;
|
|
8698
9156
|
try {
|
|
8699
|
-
const query =
|
|
9157
|
+
const query = fs2.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
|
|
8700
9158
|
const result = query.split("--> statement-breakpoint").map((it) => {
|
|
8701
9159
|
return it;
|
|
8702
9160
|
});
|
|
@@ -8721,12 +9179,12 @@ function migrate(db, config) {
|
|
|
8721
9179
|
|
|
8722
9180
|
// src/storage/openStore.ts
|
|
8723
9181
|
function resolveMigrationsFolder(callerUrl, migrationsSubdir) {
|
|
8724
|
-
const here =
|
|
9182
|
+
const here = path2.dirname(fileURLToPath(callerUrl));
|
|
8725
9183
|
const candidates = [
|
|
8726
|
-
|
|
8727
|
-
|
|
9184
|
+
path2.join(here, "../..", migrationsSubdir),
|
|
9185
|
+
path2.join(here, ".", migrationsSubdir)
|
|
8728
9186
|
];
|
|
8729
|
-
const found = candidates.find((p) =>
|
|
9187
|
+
const found = candidates.find((p) => fs3.existsSync(p));
|
|
8730
9188
|
if (!found) {
|
|
8731
9189
|
throw new Error(`Could not locate migrations folder "${migrationsSubdir}"`);
|
|
8732
9190
|
}
|
|
@@ -8740,8 +9198,8 @@ function openStore({
|
|
|
8740
9198
|
schema
|
|
8741
9199
|
}) {
|
|
8742
9200
|
const migrationsFolder = resolveMigrationsFolder(callerUrl, migrationsSubdir);
|
|
8743
|
-
|
|
8744
|
-
const sqlite = new
|
|
9201
|
+
fs3.mkdirSync(dataDir2, { recursive: true });
|
|
9202
|
+
const sqlite = new Database2(path2.join(dataDir2, dbFile));
|
|
8745
9203
|
const db = drizzle(sqlite, { schema });
|
|
8746
9204
|
migrate(db, { migrationsFolder });
|
|
8747
9205
|
return { db, sqlite };
|
|
@@ -8774,13 +9232,32 @@ var chats = sqliteTable(
|
|
|
8774
9232
|
agent: text("agent").notNull(),
|
|
8775
9233
|
sourceId: text("source_id").notNull(),
|
|
8776
9234
|
firstSeenAt: integer("first_seen_at", { mode: "timestamp_ms" }).notNull(),
|
|
9235
|
+
// Denormalized "conversation start" = min(messages.ts), kept current at
|
|
9236
|
+
// ingest and backfilled in migration 0010. The createdAt sort pages by this
|
|
9237
|
+
// (not first_seen_at, the ingest time) so the paged order matches the
|
|
9238
|
+
// reader's displayed createdAt (issue #143, reconciling ADR-0017's caveat).
|
|
9239
|
+
// Initialized to first_seen_at for a chat with no messages; since
|
|
9240
|
+
// first_seen_at is the ingest time and so >= every message ts, the running
|
|
9241
|
+
// min over (first_seen_at, message ts) equals min(messages.ts) once any
|
|
9242
|
+
// message exists, and first_seen_at when none — never NULL.
|
|
9243
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }),
|
|
9244
|
+
// Denormalized "most recent activity" = max(messages.ts), kept current at
|
|
9245
|
+
// ingest and backfilled in migration 0009. Lets the activity sort run as a
|
|
9246
|
+
// keyset index range scan instead of an aggregate that defeats the index
|
|
9247
|
+
// (ADR-0017). Initialized to first_seen_at for a chat with no messages, so
|
|
9248
|
+
// it is never NULL and always agrees with the reader's derived `updatedAt`.
|
|
9249
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }),
|
|
8777
9250
|
project: text("project"),
|
|
8778
9251
|
projectPath: text("project_path")
|
|
8779
9252
|
},
|
|
8780
9253
|
(t) => [
|
|
8781
9254
|
uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId),
|
|
8782
9255
|
// Backs the server-side Project filter (`WHERE coalesce(project,'') IN …`).
|
|
8783
|
-
index("chats_project_idx").on(t.project)
|
|
9256
|
+
index("chats_project_idx").on(t.project),
|
|
9257
|
+
// Covering keyset indexes for the two server-side list sorts (ADR-0017):
|
|
9258
|
+
// (sortKey, id) so the ORDER BY + LIMIT is an index range scan either way.
|
|
9259
|
+
index("chats_created_keyset_idx").on(t.createdAt, t.id),
|
|
9260
|
+
index("chats_updated_keyset_idx").on(t.updatedAt, t.id)
|
|
8784
9261
|
]
|
|
8785
9262
|
);
|
|
8786
9263
|
var rawMessages = sqliteTable(
|
|
@@ -8836,6 +9313,10 @@ function createArchiveReadSeam(db) {
|
|
|
8836
9313
|
}
|
|
8837
9314
|
return db.select().from(chats).all();
|
|
8838
9315
|
},
|
|
9316
|
+
listChatRowsByIds(ids) {
|
|
9317
|
+
if (ids.length === 0) return [];
|
|
9318
|
+
return db.select().from(chats).where(inArray(chats.id, ids)).all();
|
|
9319
|
+
},
|
|
8839
9320
|
findChatBySourceId(sourceId) {
|
|
8840
9321
|
return db.select().from(chats).where(eq(chats.sourceId, sourceId)).get() ?? null;
|
|
8841
9322
|
},
|
|
@@ -8845,15 +9326,17 @@ function createArchiveReadSeam(db) {
|
|
|
8845
9326
|
listMessagesByChat(agent, sourceId) {
|
|
8846
9327
|
return db.select().from(messages).where(and(eq(messages.agent, agent), eq(messages.sourceId, sourceId))).orderBy(asc(messages.ts)).all();
|
|
8847
9328
|
},
|
|
8848
|
-
listChatTsRanges() {
|
|
9329
|
+
listChatTsRanges(opts) {
|
|
9330
|
+
const scope = opts?.sourceIds !== void 0 ? inArray(messages.sourceId, opts.sourceIds) : sql`1 = 1`;
|
|
8849
9331
|
return db.select({
|
|
8850
9332
|
agent: messages.agent,
|
|
8851
9333
|
sourceId: messages.sourceId,
|
|
8852
9334
|
minTs: sql`min(${messages.ts})`,
|
|
8853
9335
|
maxTs: sql`max(${messages.ts})`
|
|
8854
|
-
}).from(messages).groupBy(messages.agent, messages.sourceId).all();
|
|
9336
|
+
}).from(messages).where(scope).groupBy(messages.agent, messages.sourceId).all();
|
|
8855
9337
|
},
|
|
8856
|
-
listLatestRawSourcePaths() {
|
|
9338
|
+
listLatestRawSourcePaths(opts) {
|
|
9339
|
+
const scope = opts?.sourceIds !== void 0 ? inArray(rawMessages.sourceId, opts.sourceIds) : sql`1 = 1`;
|
|
8857
9340
|
const ranked = db.select({
|
|
8858
9341
|
agent: rawMessages.agent,
|
|
8859
9342
|
sourceId: rawMessages.sourceId,
|
|
@@ -8861,14 +9344,18 @@ function createArchiveReadSeam(db) {
|
|
|
8861
9344
|
rn: sql`row_number() over (partition by ${rawMessages.agent}, ${rawMessages.sourceId} order by ${rawMessages.ingestedAt} desc)`.as(
|
|
8862
9345
|
"rn"
|
|
8863
9346
|
)
|
|
8864
|
-
}).from(rawMessages).as("ranked_raw");
|
|
9347
|
+
}).from(rawMessages).where(scope).as("ranked_raw");
|
|
8865
9348
|
return db.select({
|
|
8866
9349
|
agent: ranked.agent,
|
|
8867
9350
|
sourceId: ranked.sourceId,
|
|
8868
9351
|
sourcePath: ranked.sourcePath
|
|
8869
9352
|
}).from(ranked).where(eq(ranked.rn, 1)).all();
|
|
8870
9353
|
},
|
|
8871
|
-
listFirstUserTexts() {
|
|
9354
|
+
listFirstUserTexts(opts) {
|
|
9355
|
+
const scope = opts?.sourceIds !== void 0 ? and(
|
|
9356
|
+
eq(messages.role, "user"),
|
|
9357
|
+
inArray(messages.sourceId, opts.sourceIds)
|
|
9358
|
+
) : eq(messages.role, "user");
|
|
8872
9359
|
const ranked = db.select({
|
|
8873
9360
|
agent: messages.agent,
|
|
8874
9361
|
sourceId: messages.sourceId,
|
|
@@ -8876,7 +9363,7 @@ function createArchiveReadSeam(db) {
|
|
|
8876
9363
|
rn: sql`row_number() over (partition by ${messages.agent}, ${messages.sourceId} order by ${messages.ts} asc)`.as(
|
|
8877
9364
|
"rn"
|
|
8878
9365
|
)
|
|
8879
|
-
}).from(messages).where(
|
|
9366
|
+
}).from(messages).where(scope).as("ranked_user");
|
|
8880
9367
|
return db.select({
|
|
8881
9368
|
agent: ranked.agent,
|
|
8882
9369
|
sourceId: ranked.sourceId,
|
|
@@ -8959,6 +9446,12 @@ function createArchiveRepository({
|
|
|
8959
9446
|
agent,
|
|
8960
9447
|
sourceId,
|
|
8961
9448
|
firstSeenAt,
|
|
9449
|
+
// Seed both denormalized sort keys to first_seen_at; message upserts
|
|
9450
|
+
// pull createdAt down (min) and push updatedAt up (max) as ts arrive.
|
|
9451
|
+
// first_seen_at is the ingest time and so >= every message ts, making
|
|
9452
|
+
// it a safe upper bound for the running min.
|
|
9453
|
+
createdAt: firstSeenAt,
|
|
9454
|
+
updatedAt: firstSeenAt,
|
|
8962
9455
|
project: project ?? null,
|
|
8963
9456
|
projectPath: projectPath ?? null
|
|
8964
9457
|
}).run();
|
|
@@ -8995,6 +9488,10 @@ function createArchiveRepository({
|
|
|
8995
9488
|
)
|
|
8996
9489
|
).get();
|
|
8997
9490
|
const ts = parseTs(message.ts);
|
|
9491
|
+
db.update(chats).set({
|
|
9492
|
+
createdAt: sql`min(coalesce(${chats.createdAt}, ${ts.getTime()}), ${ts.getTime()})`,
|
|
9493
|
+
updatedAt: sql`max(coalesce(${chats.updatedAt}, 0), ${ts.getTime()})`
|
|
9494
|
+
}).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).run();
|
|
8998
9495
|
if (!existing2) {
|
|
8999
9496
|
db.insert(messages).values({
|
|
9000
9497
|
agent,
|
|
@@ -9115,26 +9612,53 @@ function createCheckpointRepository({
|
|
|
9115
9612
|
}
|
|
9116
9613
|
|
|
9117
9614
|
// src/metadata/repository.ts
|
|
9118
|
-
import
|
|
9119
|
-
import
|
|
9615
|
+
import fs4 from "fs";
|
|
9616
|
+
import path3 from "path";
|
|
9120
9617
|
|
|
9121
9618
|
// src/metadata/schema.ts
|
|
9122
9619
|
var schema_exports3 = {};
|
|
9123
9620
|
__export(schema_exports3, {
|
|
9621
|
+
chatSortKeys: () => chatSortKeys,
|
|
9124
9622
|
chatTags: () => chatTags,
|
|
9125
9623
|
chatsMeta: () => chatsMeta,
|
|
9126
9624
|
tags: () => tags
|
|
9127
9625
|
});
|
|
9128
|
-
var chatsMeta = sqliteTable(
|
|
9129
|
-
|
|
9130
|
-
|
|
9131
|
-
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
|
|
9135
|
-
|
|
9136
|
-
|
|
9137
|
-
|
|
9626
|
+
var chatsMeta = sqliteTable(
|
|
9627
|
+
"chats_meta",
|
|
9628
|
+
{
|
|
9629
|
+
id: text("id").primaryKey(),
|
|
9630
|
+
isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
|
|
9631
|
+
customTitle: text("custom_title"),
|
|
9632
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
9633
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
|
|
9634
|
+
// Set when a chat is moved to Trash; null while active. Drives the Trash
|
|
9635
|
+
// view's independent "Deleted time" sort axis.
|
|
9636
|
+
deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
|
|
9637
|
+
},
|
|
9638
|
+
(table) => [
|
|
9639
|
+
// Covering keyset index for the Trash view's deleted-time axis (#145,
|
|
9640
|
+
// ADR-0017): `(deleted_at, id)` so the page's ORDER BY + LIMIT is an index
|
|
9641
|
+
// range scan, not a full-trash sort. Partial on `deleted_at IS NOT NULL`,
|
|
9642
|
+
// which holds exactly for trashed rows (restore nulls it), so the index
|
|
9643
|
+
// carries only the Trash set and the page query's matching predicate lets
|
|
9644
|
+
// the planner use it.
|
|
9645
|
+
index("chats_meta_deleted_at_idx").on(table.deletedAt, table.id).where(sql`${table.deletedAt} is not null`)
|
|
9646
|
+
]
|
|
9647
|
+
);
|
|
9648
|
+
var chatSortKeys = sqliteTable(
|
|
9649
|
+
"chat_sort_keys",
|
|
9650
|
+
{
|
|
9651
|
+
id: text("id").primaryKey(),
|
|
9652
|
+
textKey: text("text_key"),
|
|
9653
|
+
sortKey: text("sort_key")
|
|
9654
|
+
},
|
|
9655
|
+
(table) => [
|
|
9656
|
+
// Covering keyset index for the Title axis (ADR-0017/0019): `(sort_key, id)`
|
|
9657
|
+
// so `ORDER BY sort_key, id` + LIMIT is an index range scan either
|
|
9658
|
+
// direction, exactly like the time axes — never a full-table sort.
|
|
9659
|
+
index("chat_sort_keys_sort_key_idx").on(table.sortKey, table.id)
|
|
9660
|
+
]
|
|
9661
|
+
);
|
|
9138
9662
|
var tags = sqliteTable("tags", {
|
|
9139
9663
|
id: text("id").primaryKey(),
|
|
9140
9664
|
name: text("name").notNull(),
|
|
@@ -9154,15 +9678,26 @@ var chatTags = sqliteTable(
|
|
|
9154
9678
|
]
|
|
9155
9679
|
);
|
|
9156
9680
|
|
|
9681
|
+
// src/metadata/title-sort-key.ts
|
|
9682
|
+
function encodeNumericRun(run2) {
|
|
9683
|
+
const core = run2.replace(/^0+(?=\d)/, "");
|
|
9684
|
+
const len = String(core.length);
|
|
9685
|
+
return String(len.length) + len + core;
|
|
9686
|
+
}
|
|
9687
|
+
function computeSortKey(input) {
|
|
9688
|
+
const folded = input.normalize("NFKD").toLowerCase().replace(/\p{M}+/gu, "");
|
|
9689
|
+
return folded.replace(/\d+/g, encodeNumericRun);
|
|
9690
|
+
}
|
|
9691
|
+
|
|
9157
9692
|
// src/metadata/repository.ts
|
|
9158
9693
|
var DB_FILE = "metadata.db";
|
|
9159
9694
|
var LEGACY_DB_FILE = "data.db";
|
|
9160
9695
|
function migrateLegacyDbFile(dataDir2) {
|
|
9161
|
-
const legacy =
|
|
9162
|
-
const current =
|
|
9163
|
-
if (!
|
|
9164
|
-
if (
|
|
9165
|
-
|
|
9696
|
+
const legacy = path3.join(dataDir2, LEGACY_DB_FILE);
|
|
9697
|
+
const current = path3.join(dataDir2, DB_FILE);
|
|
9698
|
+
if (!fs4.existsSync(legacy)) return;
|
|
9699
|
+
if (fs4.existsSync(current)) return;
|
|
9700
|
+
fs4.renameSync(legacy, current);
|
|
9166
9701
|
}
|
|
9167
9702
|
var CLAUDE_CODE_AGENT = "claude-code";
|
|
9168
9703
|
function createMetadataRepository({
|
|
@@ -9234,6 +9769,27 @@ function createMetadataRepository({
|
|
|
9234
9769
|
target: chatsMeta.id,
|
|
9235
9770
|
set: { customTitle: title, updatedAt: now }
|
|
9236
9771
|
}).run();
|
|
9772
|
+
if (title !== null) {
|
|
9773
|
+
const sortKey = computeSortKey(title);
|
|
9774
|
+
db.insert(chatSortKeys).values({ id: internalId, sortKey }).onConflictDoUpdate({ target: chatSortKeys.id, set: { sortKey } }).run();
|
|
9775
|
+
} else {
|
|
9776
|
+
db.update(chatSortKeys).set({ sortKey: sql`${chatSortKeys.textKey}` }).where(eq(chatSortKeys.id, internalId)).run();
|
|
9777
|
+
}
|
|
9778
|
+
},
|
|
9779
|
+
getTitleSortKey(internalId) {
|
|
9780
|
+
const row = db.select({
|
|
9781
|
+
textKey: chatSortKeys.textKey,
|
|
9782
|
+
sortKey: chatSortKeys.sortKey
|
|
9783
|
+
}).from(chatSortKeys).where(eq(chatSortKeys.id, internalId)).get();
|
|
9784
|
+
return row ?? null;
|
|
9785
|
+
},
|
|
9786
|
+
setTitleTextKey(internalId, textKey) {
|
|
9787
|
+
const customTitle = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get()?.customTitle;
|
|
9788
|
+
const sortKey = customTitle && customTitle.trim() ? computeSortKey(customTitle) : textKey;
|
|
9789
|
+
db.insert(chatSortKeys).values({ id: internalId, textKey, sortKey }).onConflictDoUpdate({
|
|
9790
|
+
target: chatSortKeys.id,
|
|
9791
|
+
set: { textKey, sortKey }
|
|
9792
|
+
}).run();
|
|
9237
9793
|
}
|
|
9238
9794
|
};
|
|
9239
9795
|
}
|
|
@@ -9335,17 +9891,12 @@ function createTagRepository({
|
|
|
9335
9891
|
byChat.set(row.chatId, list);
|
|
9336
9892
|
}
|
|
9337
9893
|
return byChat;
|
|
9338
|
-
},
|
|
9339
|
-
listChatIdsWithAllTags(tagIds) {
|
|
9340
|
-
const distinct = [...new Set(tagIds)];
|
|
9341
|
-
if (distinct.length === 0) return [];
|
|
9342
|
-
return db.select({ chatId: chatTags.chatId }).from(chatTags).where(inArray(chatTags.tagId, distinct)).groupBy(chatTags.chatId).having(sql`count(${chatTags.tagId}) = ${distinct.length}`).all().map((r) => r.chatId);
|
|
9343
9894
|
}
|
|
9344
9895
|
};
|
|
9345
9896
|
}
|
|
9346
9897
|
|
|
9347
9898
|
// src/ingestion/ingest.ts
|
|
9348
|
-
import
|
|
9899
|
+
import fs5 from "fs";
|
|
9349
9900
|
async function runIngestion(opts) {
|
|
9350
9901
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
9351
9902
|
const result = {
|
|
@@ -9405,7 +9956,7 @@ async function runIngestion(opts) {
|
|
|
9405
9956
|
}
|
|
9406
9957
|
function safeStat(p) {
|
|
9407
9958
|
try {
|
|
9408
|
-
return
|
|
9959
|
+
return fs5.statSync(p);
|
|
9409
9960
|
} catch {
|
|
9410
9961
|
return null;
|
|
9411
9962
|
}
|
|
@@ -9514,7 +10065,7 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9514
10065
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
9515
10066
|
const statMethod = opts.lstat ? lstat : stat;
|
|
9516
10067
|
if (wantBigintFsStats) {
|
|
9517
|
-
this._stat = (
|
|
10068
|
+
this._stat = (path8) => statMethod(path8, { bigint: true });
|
|
9518
10069
|
} else {
|
|
9519
10070
|
this._stat = statMethod;
|
|
9520
10071
|
}
|
|
@@ -9539,8 +10090,8 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9539
10090
|
const par = this.parent;
|
|
9540
10091
|
const fil = par && par.files;
|
|
9541
10092
|
if (fil && fil.length > 0) {
|
|
9542
|
-
const { path:
|
|
9543
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
10093
|
+
const { path: path8, depth } = par;
|
|
10094
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
|
|
9544
10095
|
const awaited = await Promise.all(slice);
|
|
9545
10096
|
for (const entry of awaited) {
|
|
9546
10097
|
if (!entry)
|
|
@@ -9580,20 +10131,20 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9580
10131
|
this.reading = false;
|
|
9581
10132
|
}
|
|
9582
10133
|
}
|
|
9583
|
-
async _exploreDir(
|
|
10134
|
+
async _exploreDir(path8, depth) {
|
|
9584
10135
|
let files;
|
|
9585
10136
|
try {
|
|
9586
|
-
files = await readdir(
|
|
10137
|
+
files = await readdir(path8, this._rdOptions);
|
|
9587
10138
|
} catch (error) {
|
|
9588
10139
|
this._onError(error);
|
|
9589
10140
|
}
|
|
9590
|
-
return { files, depth, path:
|
|
10141
|
+
return { files, depth, path: path8 };
|
|
9591
10142
|
}
|
|
9592
|
-
async _formatEntry(dirent,
|
|
10143
|
+
async _formatEntry(dirent, path8) {
|
|
9593
10144
|
let entry;
|
|
9594
10145
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
9595
10146
|
try {
|
|
9596
|
-
const fullPath = presolve(pjoin(
|
|
10147
|
+
const fullPath = presolve(pjoin(path8, basename3));
|
|
9597
10148
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
9598
10149
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
9599
10150
|
} catch (err) {
|
|
@@ -9993,16 +10544,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
9993
10544
|
};
|
|
9994
10545
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
9995
10546
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
9996
|
-
function createFsWatchInstance(
|
|
10547
|
+
function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
|
|
9997
10548
|
const handleEvent = (rawEvent, evPath) => {
|
|
9998
|
-
listener(
|
|
9999
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
10000
|
-
if (evPath &&
|
|
10001
|
-
fsWatchBroadcast(sp.resolve(
|
|
10549
|
+
listener(path8);
|
|
10550
|
+
emitRaw(rawEvent, evPath, { watchedPath: path8 });
|
|
10551
|
+
if (evPath && path8 !== evPath) {
|
|
10552
|
+
fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
|
|
10002
10553
|
}
|
|
10003
10554
|
};
|
|
10004
10555
|
try {
|
|
10005
|
-
return fs_watch(
|
|
10556
|
+
return fs_watch(path8, {
|
|
10006
10557
|
persistent: options.persistent
|
|
10007
10558
|
}, handleEvent);
|
|
10008
10559
|
} catch (error) {
|
|
@@ -10018,12 +10569,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
10018
10569
|
listener(val1, val2, val3);
|
|
10019
10570
|
});
|
|
10020
10571
|
};
|
|
10021
|
-
var setFsWatchListener = (
|
|
10572
|
+
var setFsWatchListener = (path8, fullPath, options, handlers) => {
|
|
10022
10573
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
10023
10574
|
let cont = FsWatchInstances.get(fullPath);
|
|
10024
10575
|
let watcher2;
|
|
10025
10576
|
if (!options.persistent) {
|
|
10026
|
-
watcher2 = createFsWatchInstance(
|
|
10577
|
+
watcher2 = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
|
|
10027
10578
|
if (!watcher2)
|
|
10028
10579
|
return;
|
|
10029
10580
|
return watcher2.close.bind(watcher2);
|
|
@@ -10034,7 +10585,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
10034
10585
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
10035
10586
|
} else {
|
|
10036
10587
|
watcher2 = createFsWatchInstance(
|
|
10037
|
-
|
|
10588
|
+
path8,
|
|
10038
10589
|
options,
|
|
10039
10590
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
10040
10591
|
errHandler,
|
|
@@ -10049,7 +10600,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
10049
10600
|
cont.watcherUnusable = true;
|
|
10050
10601
|
if (isWindows && error.code === "EPERM") {
|
|
10051
10602
|
try {
|
|
10052
|
-
const fd = await open(
|
|
10603
|
+
const fd = await open(path8, "r");
|
|
10053
10604
|
await fd.close();
|
|
10054
10605
|
broadcastErr(error);
|
|
10055
10606
|
} catch (err) {
|
|
@@ -10080,7 +10631,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
10080
10631
|
};
|
|
10081
10632
|
};
|
|
10082
10633
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
10083
|
-
var setFsWatchFileListener = (
|
|
10634
|
+
var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
|
|
10084
10635
|
const { listener, rawEmitter } = handlers;
|
|
10085
10636
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
10086
10637
|
const copts = cont && cont.options;
|
|
@@ -10102,7 +10653,7 @@ var setFsWatchFileListener = (path5, fullPath, options, handlers) => {
|
|
|
10102
10653
|
});
|
|
10103
10654
|
const currmtime = curr.mtimeMs;
|
|
10104
10655
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
10105
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
10656
|
+
foreach(cont.listeners, (listener2) => listener2(path8, curr));
|
|
10106
10657
|
}
|
|
10107
10658
|
})
|
|
10108
10659
|
};
|
|
@@ -10132,13 +10683,13 @@ var NodeFsHandler = class {
|
|
|
10132
10683
|
* @param listener on fs change
|
|
10133
10684
|
* @returns closer for the watcher instance
|
|
10134
10685
|
*/
|
|
10135
|
-
_watchWithNodeFs(
|
|
10686
|
+
_watchWithNodeFs(path8, listener) {
|
|
10136
10687
|
const opts = this.fsw.options;
|
|
10137
|
-
const directory = sp.dirname(
|
|
10138
|
-
const basename3 = sp.basename(
|
|
10688
|
+
const directory = sp.dirname(path8);
|
|
10689
|
+
const basename3 = sp.basename(path8);
|
|
10139
10690
|
const parent = this.fsw._getWatchedDir(directory);
|
|
10140
10691
|
parent.add(basename3);
|
|
10141
|
-
const absolutePath = sp.resolve(
|
|
10692
|
+
const absolutePath = sp.resolve(path8);
|
|
10142
10693
|
const options = {
|
|
10143
10694
|
persistent: opts.persistent
|
|
10144
10695
|
};
|
|
@@ -10148,12 +10699,12 @@ var NodeFsHandler = class {
|
|
|
10148
10699
|
if (opts.usePolling) {
|
|
10149
10700
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
10150
10701
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
10151
|
-
closer = setFsWatchFileListener(
|
|
10702
|
+
closer = setFsWatchFileListener(path8, absolutePath, options, {
|
|
10152
10703
|
listener,
|
|
10153
10704
|
rawEmitter: this.fsw._emitRaw
|
|
10154
10705
|
});
|
|
10155
10706
|
} else {
|
|
10156
|
-
closer = setFsWatchListener(
|
|
10707
|
+
closer = setFsWatchListener(path8, absolutePath, options, {
|
|
10157
10708
|
listener,
|
|
10158
10709
|
errHandler: this._boundHandleError,
|
|
10159
10710
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -10175,7 +10726,7 @@ var NodeFsHandler = class {
|
|
|
10175
10726
|
let prevStats = stats;
|
|
10176
10727
|
if (parent.has(basename3))
|
|
10177
10728
|
return;
|
|
10178
|
-
const listener = async (
|
|
10729
|
+
const listener = async (path8, newStats) => {
|
|
10179
10730
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
10180
10731
|
return;
|
|
10181
10732
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -10189,11 +10740,11 @@ var NodeFsHandler = class {
|
|
|
10189
10740
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
10190
10741
|
}
|
|
10191
10742
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
10192
|
-
this.fsw._closeFile(
|
|
10743
|
+
this.fsw._closeFile(path8);
|
|
10193
10744
|
prevStats = newStats2;
|
|
10194
10745
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
10195
10746
|
if (closer2)
|
|
10196
|
-
this.fsw._addPathCloser(
|
|
10747
|
+
this.fsw._addPathCloser(path8, closer2);
|
|
10197
10748
|
} else {
|
|
10198
10749
|
prevStats = newStats2;
|
|
10199
10750
|
}
|
|
@@ -10225,7 +10776,7 @@ var NodeFsHandler = class {
|
|
|
10225
10776
|
* @param item basename of this item
|
|
10226
10777
|
* @returns true if no more processing is needed for this entry.
|
|
10227
10778
|
*/
|
|
10228
|
-
async _handleSymlink(entry, directory,
|
|
10779
|
+
async _handleSymlink(entry, directory, path8, item) {
|
|
10229
10780
|
if (this.fsw.closed) {
|
|
10230
10781
|
return;
|
|
10231
10782
|
}
|
|
@@ -10235,7 +10786,7 @@ var NodeFsHandler = class {
|
|
|
10235
10786
|
this.fsw._incrReadyCount();
|
|
10236
10787
|
let linkPath;
|
|
10237
10788
|
try {
|
|
10238
|
-
linkPath = await fsrealpath(
|
|
10789
|
+
linkPath = await fsrealpath(path8);
|
|
10239
10790
|
} catch (e) {
|
|
10240
10791
|
this.fsw._emitReady();
|
|
10241
10792
|
return true;
|
|
@@ -10245,12 +10796,12 @@ var NodeFsHandler = class {
|
|
|
10245
10796
|
if (dir.has(item)) {
|
|
10246
10797
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
10247
10798
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
10248
|
-
this.fsw._emit(EV.CHANGE,
|
|
10799
|
+
this.fsw._emit(EV.CHANGE, path8, entry.stats);
|
|
10249
10800
|
}
|
|
10250
10801
|
} else {
|
|
10251
10802
|
dir.add(item);
|
|
10252
10803
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
10253
|
-
this.fsw._emit(EV.ADD,
|
|
10804
|
+
this.fsw._emit(EV.ADD, path8, entry.stats);
|
|
10254
10805
|
}
|
|
10255
10806
|
this.fsw._emitReady();
|
|
10256
10807
|
return true;
|
|
@@ -10268,39 +10819,39 @@ var NodeFsHandler = class {
|
|
|
10268
10819
|
return;
|
|
10269
10820
|
const previous = this.fsw._getWatchedDir(wh.path);
|
|
10270
10821
|
const current = /* @__PURE__ */ new Set();
|
|
10271
|
-
let
|
|
10822
|
+
let stream2 = this.fsw._readdirp(directory, {
|
|
10272
10823
|
fileFilter: (entry) => wh.filterPath(entry),
|
|
10273
10824
|
directoryFilter: (entry) => wh.filterDir(entry)
|
|
10274
10825
|
});
|
|
10275
|
-
if (!
|
|
10826
|
+
if (!stream2)
|
|
10276
10827
|
return;
|
|
10277
|
-
|
|
10828
|
+
stream2.on(STR_DATA, async (entry) => {
|
|
10278
10829
|
if (this.fsw.closed) {
|
|
10279
|
-
|
|
10830
|
+
stream2 = void 0;
|
|
10280
10831
|
return;
|
|
10281
10832
|
}
|
|
10282
10833
|
const item = entry.path;
|
|
10283
|
-
let
|
|
10834
|
+
let path8 = sp.join(directory, item);
|
|
10284
10835
|
current.add(item);
|
|
10285
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
10836
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
|
|
10286
10837
|
return;
|
|
10287
10838
|
}
|
|
10288
10839
|
if (this.fsw.closed) {
|
|
10289
|
-
|
|
10840
|
+
stream2 = void 0;
|
|
10290
10841
|
return;
|
|
10291
10842
|
}
|
|
10292
10843
|
if (item === target || !target && !previous.has(item)) {
|
|
10293
10844
|
this.fsw._incrReadyCount();
|
|
10294
|
-
|
|
10295
|
-
this._addToNodeFs(
|
|
10845
|
+
path8 = sp.join(dir, sp.relative(dir, path8));
|
|
10846
|
+
this._addToNodeFs(path8, initialAdd, wh, depth + 1);
|
|
10296
10847
|
}
|
|
10297
10848
|
}).on(EV.ERROR, this._boundHandleError);
|
|
10298
10849
|
return new Promise((resolve3, reject) => {
|
|
10299
|
-
if (!
|
|
10850
|
+
if (!stream2)
|
|
10300
10851
|
return reject();
|
|
10301
|
-
|
|
10852
|
+
stream2.once(STR_END, () => {
|
|
10302
10853
|
if (this.fsw.closed) {
|
|
10303
|
-
|
|
10854
|
+
stream2 = void 0;
|
|
10304
10855
|
return;
|
|
10305
10856
|
}
|
|
10306
10857
|
const wasThrottled = throttler ? throttler.clear() : false;
|
|
@@ -10310,7 +10861,7 @@ var NodeFsHandler = class {
|
|
|
10310
10861
|
}).forEach((item) => {
|
|
10311
10862
|
this.fsw._remove(directory, item);
|
|
10312
10863
|
});
|
|
10313
|
-
|
|
10864
|
+
stream2 = void 0;
|
|
10314
10865
|
if (wasThrottled)
|
|
10315
10866
|
this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
10316
10867
|
});
|
|
@@ -10361,13 +10912,13 @@ var NodeFsHandler = class {
|
|
|
10361
10912
|
* @param depth Child path actually targeted for watch
|
|
10362
10913
|
* @param target Child path actually targeted for watch
|
|
10363
10914
|
*/
|
|
10364
|
-
async _addToNodeFs(
|
|
10915
|
+
async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
|
|
10365
10916
|
const ready = this.fsw._emitReady;
|
|
10366
|
-
if (this.fsw._isIgnored(
|
|
10917
|
+
if (this.fsw._isIgnored(path8) || this.fsw.closed) {
|
|
10367
10918
|
ready();
|
|
10368
10919
|
return false;
|
|
10369
10920
|
}
|
|
10370
|
-
const wh = this.fsw._getWatchHelpers(
|
|
10921
|
+
const wh = this.fsw._getWatchHelpers(path8);
|
|
10371
10922
|
if (priorWh) {
|
|
10372
10923
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
10373
10924
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -10383,8 +10934,8 @@ var NodeFsHandler = class {
|
|
|
10383
10934
|
const follow = this.fsw.options.followSymlinks;
|
|
10384
10935
|
let closer;
|
|
10385
10936
|
if (stats.isDirectory()) {
|
|
10386
|
-
const absPath = sp.resolve(
|
|
10387
|
-
const targetPath = follow ? await fsrealpath(
|
|
10937
|
+
const absPath = sp.resolve(path8);
|
|
10938
|
+
const targetPath = follow ? await fsrealpath(path8) : path8;
|
|
10388
10939
|
if (this.fsw.closed)
|
|
10389
10940
|
return;
|
|
10390
10941
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -10394,29 +10945,29 @@ var NodeFsHandler = class {
|
|
|
10394
10945
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
10395
10946
|
}
|
|
10396
10947
|
} else if (stats.isSymbolicLink()) {
|
|
10397
|
-
const targetPath = follow ? await fsrealpath(
|
|
10948
|
+
const targetPath = follow ? await fsrealpath(path8) : path8;
|
|
10398
10949
|
if (this.fsw.closed)
|
|
10399
10950
|
return;
|
|
10400
10951
|
const parent = sp.dirname(wh.watchPath);
|
|
10401
10952
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
10402
10953
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
10403
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
10954
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
|
|
10404
10955
|
if (this.fsw.closed)
|
|
10405
10956
|
return;
|
|
10406
10957
|
if (targetPath !== void 0) {
|
|
10407
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
10958
|
+
this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
|
|
10408
10959
|
}
|
|
10409
10960
|
} else {
|
|
10410
10961
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
10411
10962
|
}
|
|
10412
10963
|
ready();
|
|
10413
10964
|
if (closer)
|
|
10414
|
-
this.fsw._addPathCloser(
|
|
10965
|
+
this.fsw._addPathCloser(path8, closer);
|
|
10415
10966
|
return false;
|
|
10416
10967
|
} catch (error) {
|
|
10417
10968
|
if (this.fsw._handleError(error)) {
|
|
10418
10969
|
ready();
|
|
10419
|
-
return
|
|
10970
|
+
return path8;
|
|
10420
10971
|
}
|
|
10421
10972
|
}
|
|
10422
10973
|
}
|
|
@@ -10459,24 +11010,24 @@ function createPattern(matcher) {
|
|
|
10459
11010
|
}
|
|
10460
11011
|
return () => false;
|
|
10461
11012
|
}
|
|
10462
|
-
function normalizePath(
|
|
10463
|
-
if (typeof
|
|
11013
|
+
function normalizePath(path8) {
|
|
11014
|
+
if (typeof path8 !== "string")
|
|
10464
11015
|
throw new Error("string expected");
|
|
10465
|
-
|
|
10466
|
-
|
|
11016
|
+
path8 = sp2.normalize(path8);
|
|
11017
|
+
path8 = path8.replace(/\\/g, "/");
|
|
10467
11018
|
let prepend = false;
|
|
10468
|
-
if (
|
|
11019
|
+
if (path8.startsWith("//"))
|
|
10469
11020
|
prepend = true;
|
|
10470
|
-
|
|
11021
|
+
path8 = path8.replace(DOUBLE_SLASH_RE, "/");
|
|
10471
11022
|
if (prepend)
|
|
10472
|
-
|
|
10473
|
-
return
|
|
11023
|
+
path8 = "/" + path8;
|
|
11024
|
+
return path8;
|
|
10474
11025
|
}
|
|
10475
11026
|
function matchPatterns(patterns, testString, stats) {
|
|
10476
|
-
const
|
|
11027
|
+
const path8 = normalizePath(testString);
|
|
10477
11028
|
for (let index2 = 0; index2 < patterns.length; index2++) {
|
|
10478
11029
|
const pattern = patterns[index2];
|
|
10479
|
-
if (pattern(
|
|
11030
|
+
if (pattern(path8, stats)) {
|
|
10480
11031
|
return true;
|
|
10481
11032
|
}
|
|
10482
11033
|
}
|
|
@@ -10514,19 +11065,19 @@ var toUnix = (string) => {
|
|
|
10514
11065
|
}
|
|
10515
11066
|
return str;
|
|
10516
11067
|
};
|
|
10517
|
-
var normalizePathToUnix = (
|
|
10518
|
-
var normalizeIgnored = (cwd = "") => (
|
|
10519
|
-
if (typeof
|
|
10520
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
11068
|
+
var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
|
|
11069
|
+
var normalizeIgnored = (cwd = "") => (path8) => {
|
|
11070
|
+
if (typeof path8 === "string") {
|
|
11071
|
+
return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
|
|
10521
11072
|
} else {
|
|
10522
|
-
return
|
|
11073
|
+
return path8;
|
|
10523
11074
|
}
|
|
10524
11075
|
};
|
|
10525
|
-
var getAbsolutePath = (
|
|
10526
|
-
if (sp2.isAbsolute(
|
|
10527
|
-
return
|
|
11076
|
+
var getAbsolutePath = (path8, cwd) => {
|
|
11077
|
+
if (sp2.isAbsolute(path8)) {
|
|
11078
|
+
return path8;
|
|
10528
11079
|
}
|
|
10529
|
-
return sp2.join(cwd,
|
|
11080
|
+
return sp2.join(cwd, path8);
|
|
10530
11081
|
};
|
|
10531
11082
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
10532
11083
|
var DirEntry = class {
|
|
@@ -10591,10 +11142,10 @@ var WatchHelper = class {
|
|
|
10591
11142
|
dirParts;
|
|
10592
11143
|
followSymlinks;
|
|
10593
11144
|
statMethod;
|
|
10594
|
-
constructor(
|
|
11145
|
+
constructor(path8, follow, fsw) {
|
|
10595
11146
|
this.fsw = fsw;
|
|
10596
|
-
const watchPath =
|
|
10597
|
-
this.path =
|
|
11147
|
+
const watchPath = path8;
|
|
11148
|
+
this.path = path8 = path8.replace(REPLACER_RE, "");
|
|
10598
11149
|
this.watchPath = watchPath;
|
|
10599
11150
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
10600
11151
|
this.dirParts = [];
|
|
@@ -10734,20 +11285,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10734
11285
|
this._closePromise = void 0;
|
|
10735
11286
|
let paths = unifyPaths(paths_);
|
|
10736
11287
|
if (cwd) {
|
|
10737
|
-
paths = paths.map((
|
|
10738
|
-
const absPath = getAbsolutePath(
|
|
11288
|
+
paths = paths.map((path8) => {
|
|
11289
|
+
const absPath = getAbsolutePath(path8, cwd);
|
|
10739
11290
|
return absPath;
|
|
10740
11291
|
});
|
|
10741
11292
|
}
|
|
10742
|
-
paths.forEach((
|
|
10743
|
-
this._removeIgnoredPath(
|
|
11293
|
+
paths.forEach((path8) => {
|
|
11294
|
+
this._removeIgnoredPath(path8);
|
|
10744
11295
|
});
|
|
10745
11296
|
this._userIgnored = void 0;
|
|
10746
11297
|
if (!this._readyCount)
|
|
10747
11298
|
this._readyCount = 0;
|
|
10748
11299
|
this._readyCount += paths.length;
|
|
10749
|
-
Promise.all(paths.map(async (
|
|
10750
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
11300
|
+
Promise.all(paths.map(async (path8) => {
|
|
11301
|
+
const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
|
|
10751
11302
|
if (res)
|
|
10752
11303
|
this._emitReady();
|
|
10753
11304
|
return res;
|
|
@@ -10769,17 +11320,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10769
11320
|
return this;
|
|
10770
11321
|
const paths = unifyPaths(paths_);
|
|
10771
11322
|
const { cwd } = this.options;
|
|
10772
|
-
paths.forEach((
|
|
10773
|
-
if (!sp2.isAbsolute(
|
|
11323
|
+
paths.forEach((path8) => {
|
|
11324
|
+
if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
|
|
10774
11325
|
if (cwd)
|
|
10775
|
-
|
|
10776
|
-
|
|
11326
|
+
path8 = sp2.join(cwd, path8);
|
|
11327
|
+
path8 = sp2.resolve(path8);
|
|
10777
11328
|
}
|
|
10778
|
-
this._closePath(
|
|
10779
|
-
this._addIgnoredPath(
|
|
10780
|
-
if (this._watched.has(
|
|
11329
|
+
this._closePath(path8);
|
|
11330
|
+
this._addIgnoredPath(path8);
|
|
11331
|
+
if (this._watched.has(path8)) {
|
|
10781
11332
|
this._addIgnoredPath({
|
|
10782
|
-
path:
|
|
11333
|
+
path: path8,
|
|
10783
11334
|
recursive: true
|
|
10784
11335
|
});
|
|
10785
11336
|
}
|
|
@@ -10802,7 +11353,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10802
11353
|
if (promise instanceof Promise)
|
|
10803
11354
|
closers.push(promise);
|
|
10804
11355
|
}));
|
|
10805
|
-
this._streams.forEach((
|
|
11356
|
+
this._streams.forEach((stream2) => stream2.destroy());
|
|
10806
11357
|
this._userIgnored = void 0;
|
|
10807
11358
|
this._readyCount = 0;
|
|
10808
11359
|
this._readyEmitted = false;
|
|
@@ -10843,38 +11394,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10843
11394
|
* @param stats arguments to be passed with event
|
|
10844
11395
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
10845
11396
|
*/
|
|
10846
|
-
async _emit(event,
|
|
11397
|
+
async _emit(event, path8, stats) {
|
|
10847
11398
|
if (this.closed)
|
|
10848
11399
|
return;
|
|
10849
11400
|
const opts = this.options;
|
|
10850
11401
|
if (isWindows)
|
|
10851
|
-
|
|
11402
|
+
path8 = sp2.normalize(path8);
|
|
10852
11403
|
if (opts.cwd)
|
|
10853
|
-
|
|
10854
|
-
const args = [
|
|
11404
|
+
path8 = sp2.relative(opts.cwd, path8);
|
|
11405
|
+
const args = [path8];
|
|
10855
11406
|
if (stats != null)
|
|
10856
11407
|
args.push(stats);
|
|
10857
11408
|
const awf = opts.awaitWriteFinish;
|
|
10858
11409
|
let pw;
|
|
10859
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
11410
|
+
if (awf && (pw = this._pendingWrites.get(path8))) {
|
|
10860
11411
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
10861
11412
|
return this;
|
|
10862
11413
|
}
|
|
10863
11414
|
if (opts.atomic) {
|
|
10864
11415
|
if (event === EVENTS.UNLINK) {
|
|
10865
|
-
this._pendingUnlinks.set(
|
|
11416
|
+
this._pendingUnlinks.set(path8, [event, ...args]);
|
|
10866
11417
|
setTimeout(() => {
|
|
10867
|
-
this._pendingUnlinks.forEach((entry,
|
|
11418
|
+
this._pendingUnlinks.forEach((entry, path9) => {
|
|
10868
11419
|
this.emit(...entry);
|
|
10869
11420
|
this.emit(EVENTS.ALL, ...entry);
|
|
10870
|
-
this._pendingUnlinks.delete(
|
|
11421
|
+
this._pendingUnlinks.delete(path9);
|
|
10871
11422
|
});
|
|
10872
11423
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
10873
11424
|
return this;
|
|
10874
11425
|
}
|
|
10875
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
11426
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
|
|
10876
11427
|
event = EVENTS.CHANGE;
|
|
10877
|
-
this._pendingUnlinks.delete(
|
|
11428
|
+
this._pendingUnlinks.delete(path8);
|
|
10878
11429
|
}
|
|
10879
11430
|
}
|
|
10880
11431
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -10892,16 +11443,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10892
11443
|
this.emitWithAll(event, args);
|
|
10893
11444
|
}
|
|
10894
11445
|
};
|
|
10895
|
-
this._awaitWriteFinish(
|
|
11446
|
+
this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
|
|
10896
11447
|
return this;
|
|
10897
11448
|
}
|
|
10898
11449
|
if (event === EVENTS.CHANGE) {
|
|
10899
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
11450
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
|
|
10900
11451
|
if (isThrottled)
|
|
10901
11452
|
return this;
|
|
10902
11453
|
}
|
|
10903
11454
|
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,
|
|
11455
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
|
|
10905
11456
|
let stats2;
|
|
10906
11457
|
try {
|
|
10907
11458
|
stats2 = await stat3(fullPath);
|
|
@@ -10932,23 +11483,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10932
11483
|
* @param timeout duration of time to suppress duplicate actions
|
|
10933
11484
|
* @returns tracking object or false if action should be suppressed
|
|
10934
11485
|
*/
|
|
10935
|
-
_throttle(actionType,
|
|
11486
|
+
_throttle(actionType, path8, timeout) {
|
|
10936
11487
|
if (!this._throttled.has(actionType)) {
|
|
10937
11488
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
10938
11489
|
}
|
|
10939
11490
|
const action2 = this._throttled.get(actionType);
|
|
10940
11491
|
if (!action2)
|
|
10941
11492
|
throw new Error("invalid throttle");
|
|
10942
|
-
const actionPath = action2.get(
|
|
11493
|
+
const actionPath = action2.get(path8);
|
|
10943
11494
|
if (actionPath) {
|
|
10944
11495
|
actionPath.count++;
|
|
10945
11496
|
return false;
|
|
10946
11497
|
}
|
|
10947
11498
|
let timeoutObject;
|
|
10948
11499
|
const clear = () => {
|
|
10949
|
-
const item = action2.get(
|
|
11500
|
+
const item = action2.get(path8);
|
|
10950
11501
|
const count = item ? item.count : 0;
|
|
10951
|
-
action2.delete(
|
|
11502
|
+
action2.delete(path8);
|
|
10952
11503
|
clearTimeout(timeoutObject);
|
|
10953
11504
|
if (item)
|
|
10954
11505
|
clearTimeout(item.timeoutObject);
|
|
@@ -10956,7 +11507,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10956
11507
|
};
|
|
10957
11508
|
timeoutObject = setTimeout(clear, timeout);
|
|
10958
11509
|
const thr = { timeoutObject, clear, count: 0 };
|
|
10959
|
-
action2.set(
|
|
11510
|
+
action2.set(path8, thr);
|
|
10960
11511
|
return thr;
|
|
10961
11512
|
}
|
|
10962
11513
|
_incrReadyCount() {
|
|
@@ -10970,44 +11521,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10970
11521
|
* @param event
|
|
10971
11522
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
10972
11523
|
*/
|
|
10973
|
-
_awaitWriteFinish(
|
|
11524
|
+
_awaitWriteFinish(path8, threshold, event, awfEmit) {
|
|
10974
11525
|
const awf = this.options.awaitWriteFinish;
|
|
10975
11526
|
if (typeof awf !== "object")
|
|
10976
11527
|
return;
|
|
10977
11528
|
const pollInterval = awf.pollInterval;
|
|
10978
11529
|
let timeoutHandler;
|
|
10979
|
-
let fullPath =
|
|
10980
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
10981
|
-
fullPath = sp2.join(this.options.cwd,
|
|
11530
|
+
let fullPath = path8;
|
|
11531
|
+
if (this.options.cwd && !sp2.isAbsolute(path8)) {
|
|
11532
|
+
fullPath = sp2.join(this.options.cwd, path8);
|
|
10982
11533
|
}
|
|
10983
11534
|
const now = /* @__PURE__ */ new Date();
|
|
10984
11535
|
const writes = this._pendingWrites;
|
|
10985
11536
|
function awaitWriteFinishFn(prevStat) {
|
|
10986
11537
|
statcb(fullPath, (err, curStat) => {
|
|
10987
|
-
if (err || !writes.has(
|
|
11538
|
+
if (err || !writes.has(path8)) {
|
|
10988
11539
|
if (err && err.code !== "ENOENT")
|
|
10989
11540
|
awfEmit(err);
|
|
10990
11541
|
return;
|
|
10991
11542
|
}
|
|
10992
11543
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
10993
11544
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
10994
|
-
writes.get(
|
|
11545
|
+
writes.get(path8).lastChange = now2;
|
|
10995
11546
|
}
|
|
10996
|
-
const pw = writes.get(
|
|
11547
|
+
const pw = writes.get(path8);
|
|
10997
11548
|
const df = now2 - pw.lastChange;
|
|
10998
11549
|
if (df >= threshold) {
|
|
10999
|
-
writes.delete(
|
|
11550
|
+
writes.delete(path8);
|
|
11000
11551
|
awfEmit(void 0, curStat);
|
|
11001
11552
|
} else {
|
|
11002
11553
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
11003
11554
|
}
|
|
11004
11555
|
});
|
|
11005
11556
|
}
|
|
11006
|
-
if (!writes.has(
|
|
11007
|
-
writes.set(
|
|
11557
|
+
if (!writes.has(path8)) {
|
|
11558
|
+
writes.set(path8, {
|
|
11008
11559
|
lastChange: now,
|
|
11009
11560
|
cancelWait: () => {
|
|
11010
|
-
writes.delete(
|
|
11561
|
+
writes.delete(path8);
|
|
11011
11562
|
clearTimeout(timeoutHandler);
|
|
11012
11563
|
return event;
|
|
11013
11564
|
}
|
|
@@ -11018,8 +11569,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11018
11569
|
/**
|
|
11019
11570
|
* Determines whether user has asked to ignore this path.
|
|
11020
11571
|
*/
|
|
11021
|
-
_isIgnored(
|
|
11022
|
-
if (this.options.atomic && DOT_RE.test(
|
|
11572
|
+
_isIgnored(path8, stats) {
|
|
11573
|
+
if (this.options.atomic && DOT_RE.test(path8))
|
|
11023
11574
|
return true;
|
|
11024
11575
|
if (!this._userIgnored) {
|
|
11025
11576
|
const { cwd } = this.options;
|
|
@@ -11029,17 +11580,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11029
11580
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
11030
11581
|
this._userIgnored = anymatch(list, void 0);
|
|
11031
11582
|
}
|
|
11032
|
-
return this._userIgnored(
|
|
11583
|
+
return this._userIgnored(path8, stats);
|
|
11033
11584
|
}
|
|
11034
|
-
_isntIgnored(
|
|
11035
|
-
return !this._isIgnored(
|
|
11585
|
+
_isntIgnored(path8, stat4) {
|
|
11586
|
+
return !this._isIgnored(path8, stat4);
|
|
11036
11587
|
}
|
|
11037
11588
|
/**
|
|
11038
11589
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
11039
11590
|
* @param path file or directory pattern being watched
|
|
11040
11591
|
*/
|
|
11041
|
-
_getWatchHelpers(
|
|
11042
|
-
return new WatchHelper(
|
|
11592
|
+
_getWatchHelpers(path8) {
|
|
11593
|
+
return new WatchHelper(path8, this.options.followSymlinks, this);
|
|
11043
11594
|
}
|
|
11044
11595
|
// Directory helpers
|
|
11045
11596
|
// -----------------
|
|
@@ -11071,63 +11622,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11071
11622
|
* @param item base path of item/directory
|
|
11072
11623
|
*/
|
|
11073
11624
|
_remove(directory, item, isDirectory) {
|
|
11074
|
-
const
|
|
11075
|
-
const fullPath = sp2.resolve(
|
|
11076
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
11077
|
-
if (!this._throttle("remove",
|
|
11625
|
+
const path8 = sp2.join(directory, item);
|
|
11626
|
+
const fullPath = sp2.resolve(path8);
|
|
11627
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
|
|
11628
|
+
if (!this._throttle("remove", path8, 100))
|
|
11078
11629
|
return;
|
|
11079
11630
|
if (!isDirectory && this._watched.size === 1) {
|
|
11080
11631
|
this.add(directory, item, true);
|
|
11081
11632
|
}
|
|
11082
|
-
const wp = this._getWatchedDir(
|
|
11633
|
+
const wp = this._getWatchedDir(path8);
|
|
11083
11634
|
const nestedDirectoryChildren = wp.getChildren();
|
|
11084
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
11635
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
|
|
11085
11636
|
const parent = this._getWatchedDir(directory);
|
|
11086
11637
|
const wasTracked = parent.has(item);
|
|
11087
11638
|
parent.remove(item);
|
|
11088
11639
|
if (this._symlinkPaths.has(fullPath)) {
|
|
11089
11640
|
this._symlinkPaths.delete(fullPath);
|
|
11090
11641
|
}
|
|
11091
|
-
let relPath =
|
|
11642
|
+
let relPath = path8;
|
|
11092
11643
|
if (this.options.cwd)
|
|
11093
|
-
relPath = sp2.relative(this.options.cwd,
|
|
11644
|
+
relPath = sp2.relative(this.options.cwd, path8);
|
|
11094
11645
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
11095
11646
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
11096
11647
|
if (event === EVENTS.ADD)
|
|
11097
11648
|
return;
|
|
11098
11649
|
}
|
|
11099
|
-
this._watched.delete(
|
|
11650
|
+
this._watched.delete(path8);
|
|
11100
11651
|
this._watched.delete(fullPath);
|
|
11101
11652
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
11102
|
-
if (wasTracked && !this._isIgnored(
|
|
11103
|
-
this._emit(eventName,
|
|
11104
|
-
this._closePath(
|
|
11653
|
+
if (wasTracked && !this._isIgnored(path8))
|
|
11654
|
+
this._emit(eventName, path8);
|
|
11655
|
+
this._closePath(path8);
|
|
11105
11656
|
}
|
|
11106
11657
|
/**
|
|
11107
11658
|
* Closes all watchers for a path
|
|
11108
11659
|
*/
|
|
11109
|
-
_closePath(
|
|
11110
|
-
this._closeFile(
|
|
11111
|
-
const dir = sp2.dirname(
|
|
11112
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
11660
|
+
_closePath(path8) {
|
|
11661
|
+
this._closeFile(path8);
|
|
11662
|
+
const dir = sp2.dirname(path8);
|
|
11663
|
+
this._getWatchedDir(dir).remove(sp2.basename(path8));
|
|
11113
11664
|
}
|
|
11114
11665
|
/**
|
|
11115
11666
|
* Closes only file-specific watchers
|
|
11116
11667
|
*/
|
|
11117
|
-
_closeFile(
|
|
11118
|
-
const closers = this._closers.get(
|
|
11668
|
+
_closeFile(path8) {
|
|
11669
|
+
const closers = this._closers.get(path8);
|
|
11119
11670
|
if (!closers)
|
|
11120
11671
|
return;
|
|
11121
11672
|
closers.forEach((closer) => closer());
|
|
11122
|
-
this._closers.delete(
|
|
11673
|
+
this._closers.delete(path8);
|
|
11123
11674
|
}
|
|
11124
|
-
_addPathCloser(
|
|
11675
|
+
_addPathCloser(path8, closer) {
|
|
11125
11676
|
if (!closer)
|
|
11126
11677
|
return;
|
|
11127
|
-
let list = this._closers.get(
|
|
11678
|
+
let list = this._closers.get(path8);
|
|
11128
11679
|
if (!list) {
|
|
11129
11680
|
list = [];
|
|
11130
|
-
this._closers.set(
|
|
11681
|
+
this._closers.set(path8, list);
|
|
11131
11682
|
}
|
|
11132
11683
|
list.push(closer);
|
|
11133
11684
|
}
|
|
@@ -11135,18 +11686,18 @@ var FSWatcher = class extends EventEmitter {
|
|
|
11135
11686
|
if (this.closed)
|
|
11136
11687
|
return;
|
|
11137
11688
|
const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
|
|
11138
|
-
let
|
|
11139
|
-
this._streams.add(
|
|
11140
|
-
|
|
11141
|
-
|
|
11689
|
+
let stream2 = readdirp(root, options);
|
|
11690
|
+
this._streams.add(stream2);
|
|
11691
|
+
stream2.once(STR_CLOSE, () => {
|
|
11692
|
+
stream2 = void 0;
|
|
11142
11693
|
});
|
|
11143
|
-
|
|
11144
|
-
if (
|
|
11145
|
-
this._streams.delete(
|
|
11146
|
-
|
|
11694
|
+
stream2.once(STR_END, () => {
|
|
11695
|
+
if (stream2) {
|
|
11696
|
+
this._streams.delete(stream2);
|
|
11697
|
+
stream2 = void 0;
|
|
11147
11698
|
}
|
|
11148
11699
|
});
|
|
11149
|
-
return
|
|
11700
|
+
return stream2;
|
|
11150
11701
|
}
|
|
11151
11702
|
};
|
|
11152
11703
|
function watch(paths, options = {}) {
|
|
@@ -11225,13 +11776,13 @@ function startWatcher(opts) {
|
|
|
11225
11776
|
}
|
|
11226
11777
|
return {
|
|
11227
11778
|
ready,
|
|
11228
|
-
notifyChange(
|
|
11229
|
-
if (watcher2) watcher2.emit("change",
|
|
11230
|
-
else scheduleIngest(
|
|
11779
|
+
notifyChange(path8) {
|
|
11780
|
+
if (watcher2) watcher2.emit("change", path8);
|
|
11781
|
+
else scheduleIngest(path8);
|
|
11231
11782
|
},
|
|
11232
|
-
notifyUnlink(
|
|
11233
|
-
if (watcher2) watcher2.emit("unlink",
|
|
11234
|
-
else recordUnlink(
|
|
11783
|
+
notifyUnlink(path8) {
|
|
11784
|
+
if (watcher2) watcher2.emit("unlink", path8);
|
|
11785
|
+
else recordUnlink(path8);
|
|
11235
11786
|
},
|
|
11236
11787
|
async close() {
|
|
11237
11788
|
closed = true;
|
|
@@ -11243,39 +11794,39 @@ function startWatcher(opts) {
|
|
|
11243
11794
|
}
|
|
11244
11795
|
|
|
11245
11796
|
// src/plugins/claude-code/plugin.ts
|
|
11246
|
-
import
|
|
11247
|
-
import
|
|
11797
|
+
import fs6 from "fs";
|
|
11798
|
+
import path4 from "path";
|
|
11248
11799
|
import readline from "readline";
|
|
11249
11800
|
var ClaudeCodePlugin = class {
|
|
11250
11801
|
id = "claude-code";
|
|
11251
11802
|
displayName = "Claude Code";
|
|
11252
11803
|
async *discover(env) {
|
|
11253
|
-
const projectsDir =
|
|
11254
|
-
if (!
|
|
11255
|
-
const projects =
|
|
11804
|
+
const projectsDir = path4.join(env.homeDir, ".claude", "projects");
|
|
11805
|
+
if (!fs6.existsSync(projectsDir)) return;
|
|
11806
|
+
const projects = fs6.readdirSync(projectsDir, { withFileTypes: true });
|
|
11256
11807
|
for (const project of projects) {
|
|
11257
11808
|
if (!project.isDirectory()) continue;
|
|
11258
|
-
const projectPath =
|
|
11259
|
-
const files =
|
|
11809
|
+
const projectPath = path4.join(projectsDir, project.name);
|
|
11810
|
+
const files = fs6.readdirSync(projectPath, { withFileTypes: true });
|
|
11260
11811
|
for (const file of files) {
|
|
11261
11812
|
if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
|
|
11262
|
-
const sourcePath =
|
|
11813
|
+
const sourcePath = path4.join(projectPath, file.name);
|
|
11263
11814
|
const sourceId = file.name.replace(/\.jsonl$/, "");
|
|
11264
11815
|
const cwd = await readCwdFromJsonl(sourcePath);
|
|
11265
11816
|
yield {
|
|
11266
11817
|
sourceId,
|
|
11267
11818
|
sourcePath,
|
|
11268
11819
|
watchPaths: [sourcePath],
|
|
11269
|
-
project: cwd ?
|
|
11820
|
+
project: cwd ? path4.basename(cwd) : void 0,
|
|
11270
11821
|
projectPath: cwd ?? void 0
|
|
11271
11822
|
};
|
|
11272
11823
|
}
|
|
11273
11824
|
}
|
|
11274
11825
|
}
|
|
11275
11826
|
async *extractRaw(ref) {
|
|
11276
|
-
if (!
|
|
11277
|
-
const
|
|
11278
|
-
const rl = readline.createInterface({ input:
|
|
11827
|
+
if (!fs6.existsSync(ref.sourcePath)) return;
|
|
11828
|
+
const stream2 = fs6.createReadStream(ref.sourcePath, { encoding: "utf-8" });
|
|
11829
|
+
const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
|
|
11279
11830
|
let lineNo = 0;
|
|
11280
11831
|
for await (const line of rl) {
|
|
11281
11832
|
lineNo += 1;
|
|
@@ -11323,8 +11874,8 @@ var ClaudeCodePlugin = class {
|
|
|
11323
11874
|
};
|
|
11324
11875
|
async function readCwdFromJsonl(sourcePath) {
|
|
11325
11876
|
try {
|
|
11326
|
-
const
|
|
11327
|
-
const rl = readline.createInterface({ input:
|
|
11877
|
+
const stream2 = fs6.createReadStream(sourcePath, { encoding: "utf-8" });
|
|
11878
|
+
const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
|
|
11328
11879
|
try {
|
|
11329
11880
|
for await (const line of rl) {
|
|
11330
11881
|
if (!line) continue;
|
|
@@ -11340,7 +11891,7 @@ async function readCwdFromJsonl(sourcePath) {
|
|
|
11340
11891
|
}
|
|
11341
11892
|
} finally {
|
|
11342
11893
|
rl.close();
|
|
11343
|
-
|
|
11894
|
+
stream2.destroy();
|
|
11344
11895
|
}
|
|
11345
11896
|
} catch {
|
|
11346
11897
|
}
|
|
@@ -11429,10 +11980,132 @@ Environment:
|
|
|
11429
11980
|
PORT=<n> Set the HTTP port (overridden by --port)
|
|
11430
11981
|
`;
|
|
11431
11982
|
|
|
11983
|
+
// src/config/data-dir.ts
|
|
11984
|
+
import path5 from "path";
|
|
11985
|
+
function resolveDataDir(env, homeDir) {
|
|
11986
|
+
const override = env.CHAT_LOGBOOK_DATA_DIR?.trim();
|
|
11987
|
+
if (override) {
|
|
11988
|
+
return path5.resolve(override);
|
|
11989
|
+
}
|
|
11990
|
+
return path5.join(homeDir, ".chat-logbook");
|
|
11991
|
+
}
|
|
11992
|
+
|
|
11993
|
+
// src/list-counts.ts
|
|
11994
|
+
import fs7 from "fs";
|
|
11995
|
+
import path6 from "path";
|
|
11996
|
+
import Database3 from "better-sqlite3";
|
|
11997
|
+
var ARCHIVE_DB2 = "archive.db";
|
|
11998
|
+
var METADATA_DB2 = "metadata.db";
|
|
11999
|
+
function createChatCountsQuery({
|
|
12000
|
+
dataDir: dataDir2
|
|
12001
|
+
}) {
|
|
12002
|
+
const archive2 = new Database3(path6.join(dataDir2, ARCHIVE_DB2), {
|
|
12003
|
+
readonly: true
|
|
12004
|
+
});
|
|
12005
|
+
const metadataPath = path6.join(dataDir2, METADATA_DB2);
|
|
12006
|
+
const hasMetadata = fs7.existsSync(metadataPath);
|
|
12007
|
+
if (hasMetadata) {
|
|
12008
|
+
archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
|
|
12009
|
+
}
|
|
12010
|
+
function queryCounts({
|
|
12011
|
+
includeTrashed = false
|
|
12012
|
+
}) {
|
|
12013
|
+
const viewClause = viewPredicate(includeTrashed, hasMetadata);
|
|
12014
|
+
const total = archive2.prepare(`SELECT count(*) AS n FROM chats c ${viewClause}`).get().n;
|
|
12015
|
+
const projects = archive2.prepare(
|
|
12016
|
+
`SELECT coalesce(c.project, '') AS project,
|
|
12017
|
+
count(*) AS count,
|
|
12018
|
+
max(c.updated_at) AS lastActiveAt
|
|
12019
|
+
FROM chats c ${viewClause}
|
|
12020
|
+
GROUP BY coalesce(c.project, '')`
|
|
12021
|
+
).all();
|
|
12022
|
+
const tags3 = hasMetadata ? archive2.prepare(
|
|
12023
|
+
`SELECT ct.tag_id AS tagId, count(*) AS count
|
|
12024
|
+
FROM meta.chat_tags ct
|
|
12025
|
+
JOIN chats c ON c.id = ct.chat_id
|
|
12026
|
+
${viewClause}
|
|
12027
|
+
GROUP BY ct.tag_id`
|
|
12028
|
+
).all() : [];
|
|
12029
|
+
const untagged = hasMetadata ? archive2.prepare(
|
|
12030
|
+
`SELECT count(*) AS n
|
|
12031
|
+
FROM chats c ${viewClause}
|
|
12032
|
+
AND c.id NOT IN (SELECT chat_id FROM meta.chat_tags)`
|
|
12033
|
+
).get().n : total;
|
|
12034
|
+
return { total, projects, tags: tags3, untagged };
|
|
12035
|
+
}
|
|
12036
|
+
function queryFilteredTotal({
|
|
12037
|
+
includeTrashed = false,
|
|
12038
|
+
projects,
|
|
12039
|
+
tags: tags3,
|
|
12040
|
+
tagMode = "all"
|
|
12041
|
+
}) {
|
|
12042
|
+
const clauses = [];
|
|
12043
|
+
const params = [];
|
|
12044
|
+
if (hasMetadata) {
|
|
12045
|
+
clauses.push(
|
|
12046
|
+
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)"
|
|
12047
|
+
);
|
|
12048
|
+
} else if (includeTrashed) {
|
|
12049
|
+
return 0;
|
|
12050
|
+
}
|
|
12051
|
+
const filter = buildFilterClauses({ projects, tags: tags3, tagMode, hasMetadata });
|
|
12052
|
+
clauses.push(...filter.clauses);
|
|
12053
|
+
params.push(...filter.params);
|
|
12054
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
12055
|
+
return archive2.prepare(`SELECT count(*) AS n FROM chats c ${where}`).get(...params).n;
|
|
12056
|
+
}
|
|
12057
|
+
return {
|
|
12058
|
+
queryCounts,
|
|
12059
|
+
queryFilteredTotal,
|
|
12060
|
+
close() {
|
|
12061
|
+
archive2.close();
|
|
12062
|
+
}
|
|
12063
|
+
};
|
|
12064
|
+
}
|
|
12065
|
+
function viewPredicate(includeTrashed, hasMetadata) {
|
|
12066
|
+
if (!hasMetadata) {
|
|
12067
|
+
return includeTrashed ? "WHERE 0" : "";
|
|
12068
|
+
}
|
|
12069
|
+
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)";
|
|
12070
|
+
}
|
|
12071
|
+
|
|
12072
|
+
// src/list-events.ts
|
|
12073
|
+
function createListEventHub() {
|
|
12074
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12075
|
+
return {
|
|
12076
|
+
publish(event) {
|
|
12077
|
+
for (const listener of listeners) listener(event);
|
|
12078
|
+
},
|
|
12079
|
+
subscribe(listener) {
|
|
12080
|
+
listeners.add(listener);
|
|
12081
|
+
return () => {
|
|
12082
|
+
listeners.delete(listener);
|
|
12083
|
+
};
|
|
12084
|
+
}
|
|
12085
|
+
};
|
|
12086
|
+
}
|
|
12087
|
+
|
|
12088
|
+
// src/metadata/reconcile-title-sort-keys.ts
|
|
12089
|
+
function reconcileTitleSortKeys({
|
|
12090
|
+
archive: archive2,
|
|
12091
|
+
metadata: metadata2
|
|
12092
|
+
}) {
|
|
12093
|
+
const key = (agent, sourceId) => [agent, sourceId].join("\0");
|
|
12094
|
+
const firstTextByKey = new Map(
|
|
12095
|
+
archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
|
|
12096
|
+
);
|
|
12097
|
+
for (const row of archive2.read.listChatRows()) {
|
|
12098
|
+
const baseTitle = deriveBaseTitle(
|
|
12099
|
+
firstTextByKey.get(key(row.agent, row.sourceId))
|
|
12100
|
+
);
|
|
12101
|
+
metadata2.setTitleTextKey(row.id, computeSortKey(baseTitle));
|
|
12102
|
+
}
|
|
12103
|
+
}
|
|
12104
|
+
|
|
11432
12105
|
// src/index.ts
|
|
11433
|
-
var __dirname =
|
|
11434
|
-
var pkgPath =
|
|
11435
|
-
var pkg = JSON.parse(
|
|
12106
|
+
var __dirname = path7.dirname(fileURLToPath2(import.meta.url));
|
|
12107
|
+
var pkgPath = path7.join(__dirname, "../../package.json");
|
|
12108
|
+
var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
11436
12109
|
var action = parseCliArgs(process.argv.slice(2), {
|
|
11437
12110
|
PORT: process.env.PORT
|
|
11438
12111
|
});
|
|
@@ -11449,27 +12122,58 @@ if (action.kind === "error") {
|
|
|
11449
12122
|
process.exit(1);
|
|
11450
12123
|
}
|
|
11451
12124
|
updateNotifier({ pkg }).notify({ defer: false, isGlobal: true });
|
|
11452
|
-
var dataDir =
|
|
11453
|
-
|
|
12125
|
+
var dataDir = resolveDataDir(
|
|
12126
|
+
{ CHAT_LOGBOOK_DATA_DIR: process.env.CHAT_LOGBOOK_DATA_DIR },
|
|
12127
|
+
os.homedir()
|
|
12128
|
+
);
|
|
12129
|
+
var webDistDir = path7.join(__dirname, "../../web/dist");
|
|
11454
12130
|
var port = action.port;
|
|
11455
12131
|
var archive = createArchiveRepository({ dataDir });
|
|
11456
12132
|
var checkpoint = createCheckpointRepository({ dataDir });
|
|
11457
12133
|
var metadata = createMetadataRepository({ dataDir });
|
|
11458
12134
|
var tags2 = createTagRepository({ dataDir });
|
|
11459
|
-
var
|
|
12135
|
+
var pageQuery = createChatPageQuery({ dataDir });
|
|
12136
|
+
var countsQuery = createChatCountsQuery({ dataDir });
|
|
12137
|
+
var listEvents = createListEventHub();
|
|
12138
|
+
var app = createApp({
|
|
12139
|
+
archive,
|
|
12140
|
+
metadata,
|
|
12141
|
+
tags: tags2,
|
|
12142
|
+
pageQuery,
|
|
12143
|
+
countsQuery,
|
|
12144
|
+
listEvents,
|
|
12145
|
+
webDistDir
|
|
12146
|
+
});
|
|
11460
12147
|
var initialIngest = startIngestionInBackground({
|
|
11461
12148
|
plugins,
|
|
11462
12149
|
archive,
|
|
11463
12150
|
checkpoint,
|
|
11464
12151
|
env: { homeDir: os.homedir() }
|
|
11465
12152
|
});
|
|
12153
|
+
function reconcileTitles() {
|
|
12154
|
+
try {
|
|
12155
|
+
reconcileTitleSortKeys({ archive, metadata });
|
|
12156
|
+
} catch (err) {
|
|
12157
|
+
console.error("[title-sort-key] reconcile failed:", err);
|
|
12158
|
+
}
|
|
12159
|
+
}
|
|
11466
12160
|
var watcher = startWatcher({
|
|
11467
12161
|
plugins,
|
|
11468
12162
|
archive,
|
|
11469
12163
|
checkpoint,
|
|
11470
|
-
env: { homeDir: os.homedir() }
|
|
12164
|
+
env: { homeDir: os.homedir() },
|
|
12165
|
+
// Each watcher-driven ingest can add a chat or change a first user message;
|
|
12166
|
+
// refresh the Title keys so the Title axis stays current, then push a
|
|
12167
|
+
// `changed` event so connected clients reconcile their loaded window (#132).
|
|
12168
|
+
onIngest: () => {
|
|
12169
|
+
reconcileTitles();
|
|
12170
|
+
listEvents.publish({ type: "changed" });
|
|
12171
|
+
}
|
|
11471
12172
|
});
|
|
11472
|
-
void initialIngest.done.then(() =>
|
|
12173
|
+
void initialIngest.done.then(() => {
|
|
12174
|
+
reconcileTitles();
|
|
12175
|
+
return watcher.ready;
|
|
12176
|
+
}).catch(() => {
|
|
11473
12177
|
});
|
|
11474
12178
|
function shutdown() {
|
|
11475
12179
|
void watcher.close().finally(() => process.exit(0));
|