chat-logbook 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -7
- package/api/dist/drizzle/0004_add_tags.sql +16 -0
- 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/0008_add_chats_project_idx.sql +1 -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 +21 -0
- package/api/dist/drizzle/meta/_journal.json +21 -0
- package/api/dist/index.js +1363 -376
- package/package.json +1 -1
- package/web/dist/assets/index-BQlthoCZ.css +2 -0
- package/web/dist/assets/index-zW6L5xEX.js +57 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BJlGZyK5.js +0 -57
- package/web/dist/assets/index-DSNHfjXl.css +0 -2
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 = {};
|
|
@@ -588,13 +588,13 @@ import updateNotifier from "update-notifier";
|
|
|
588
588
|
// ../node_modules/.pnpm/hono@4.12.9/node_modules/hono/dist/compose.js
|
|
589
589
|
var compose = (middleware, onError, onNotFound) => {
|
|
590
590
|
return (context, next) => {
|
|
591
|
-
let
|
|
591
|
+
let index2 = -1;
|
|
592
592
|
return dispatch(0);
|
|
593
593
|
async function dispatch(i) {
|
|
594
|
-
if (i <=
|
|
594
|
+
if (i <= index2) {
|
|
595
595
|
throw new Error("next() called multiple times");
|
|
596
596
|
}
|
|
597
|
-
|
|
597
|
+
index2 = i;
|
|
598
598
|
let res;
|
|
599
599
|
let isError = false;
|
|
600
600
|
let handler;
|
|
@@ -692,8 +692,8 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
692
692
|
}
|
|
693
693
|
let nestedForm = form;
|
|
694
694
|
const keys = key.split(".");
|
|
695
|
-
keys.forEach((key2,
|
|
696
|
-
if (
|
|
695
|
+
keys.forEach((key2, index2) => {
|
|
696
|
+
if (index2 === keys.length - 1) {
|
|
697
697
|
nestedForm[key2] = value;
|
|
698
698
|
} else {
|
|
699
699
|
if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
|
|
@@ -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
|
-
|
|
723
|
-
const mark = `@${
|
|
722
|
+
path8 = path8.replace(/\{[^}]+\}/g, (match2, index2) => {
|
|
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];
|
|
@@ -2021,11 +2021,11 @@ function match(method, path5) {
|
|
|
2021
2021
|
if (!match3) {
|
|
2022
2022
|
return [[], emptyParam];
|
|
2023
2023
|
}
|
|
2024
|
-
const
|
|
2025
|
-
return [matcher[1][
|
|
2024
|
+
const index2 = match3.indexOf("", 1);
|
|
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
|
|
@@ -2057,7 +2057,7 @@ var Node = class _Node {
|
|
|
2057
2057
|
#index;
|
|
2058
2058
|
#varIndex;
|
|
2059
2059
|
#children = /* @__PURE__ */ Object.create(null);
|
|
2060
|
-
insert(tokens,
|
|
2060
|
+
insert(tokens, index2, paramMap, context, pathErrorCheckOnly) {
|
|
2061
2061
|
if (tokens.length === 0) {
|
|
2062
2062
|
if (this.#index !== void 0) {
|
|
2063
2063
|
throw PATH_ERROR;
|
|
@@ -2065,7 +2065,7 @@ var Node = class _Node {
|
|
|
2065
2065
|
if (pathErrorCheckOnly) {
|
|
2066
2066
|
return;
|
|
2067
2067
|
}
|
|
2068
|
-
this.#index =
|
|
2068
|
+
this.#index = index2;
|
|
2069
2069
|
return;
|
|
2070
2070
|
}
|
|
2071
2071
|
const [token, ...restTokens] = tokens;
|
|
@@ -2115,7 +2115,7 @@ var Node = class _Node {
|
|
|
2115
2115
|
node = this.#children[token] = new _Node();
|
|
2116
2116
|
}
|
|
2117
2117
|
}
|
|
2118
|
-
node.insert(restTokens,
|
|
2118
|
+
node.insert(restTokens, index2, paramMap, context, pathErrorCheckOnly);
|
|
2119
2119
|
}
|
|
2120
2120
|
buildRegExpStr() {
|
|
2121
2121
|
const childKeys = Object.keys(this.#children).sort(compareKey);
|
|
@@ -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--) {
|
|
@@ -2166,7 +2166,7 @@ var Trie = class {
|
|
|
2166
2166
|
}
|
|
2167
2167
|
}
|
|
2168
2168
|
}
|
|
2169
|
-
this.#root.insert(tokens,
|
|
2169
|
+
this.#root.insert(tokens, index2, paramAssoc, this.#context, pathErrorCheckOnly);
|
|
2170
2170
|
return paramAssoc;
|
|
2171
2171
|
}
|
|
2172
2172
|
buildRegExp() {
|
|
@@ -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,16 +2999,31 @@ 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
|
};
|
|
2862
3011
|
|
|
3012
|
+
// src/metadata/tag-colors.ts
|
|
3013
|
+
var TAG_COLORS = [
|
|
3014
|
+
"yellow",
|
|
3015
|
+
"orange",
|
|
3016
|
+
"red",
|
|
3017
|
+
"magenta",
|
|
3018
|
+
"violet",
|
|
3019
|
+
"blue",
|
|
3020
|
+
"cyan",
|
|
3021
|
+
"green"
|
|
3022
|
+
];
|
|
3023
|
+
function isColorToken(value) {
|
|
3024
|
+
return typeof value === "string" && TAG_COLORS.includes(value);
|
|
3025
|
+
}
|
|
3026
|
+
|
|
2863
3027
|
// src/archive/chat-id.ts
|
|
2864
3028
|
import crypto3 from "crypto";
|
|
2865
3029
|
var CROCKFORD_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
@@ -2909,6 +3073,154 @@ function loadChatVisibility(metadata2, opts) {
|
|
|
2909
3073
|
};
|
|
2910
3074
|
}
|
|
2911
3075
|
|
|
3076
|
+
// src/title.ts
|
|
3077
|
+
function deriveBaseTitle(firstUserText) {
|
|
3078
|
+
const text2 = firstUserText?.trim().split("\n")[0]?.trim();
|
|
3079
|
+
return text2 && text2.length > 0 ? text2 : "Untitled";
|
|
3080
|
+
}
|
|
3081
|
+
|
|
3082
|
+
// src/list-pagination.ts
|
|
3083
|
+
import fs from "fs";
|
|
3084
|
+
import path from "path";
|
|
3085
|
+
import Database from "better-sqlite3";
|
|
3086
|
+
|
|
3087
|
+
// src/list-filter.ts
|
|
3088
|
+
function buildFilterClauses({
|
|
3089
|
+
projects,
|
|
3090
|
+
tags: tags3,
|
|
3091
|
+
hasMetadata
|
|
3092
|
+
}) {
|
|
3093
|
+
const clauses = [];
|
|
3094
|
+
const params = [];
|
|
3095
|
+
if (projects && projects.length > 0) {
|
|
3096
|
+
const placeholders = projects.map(() => "?").join(", ");
|
|
3097
|
+
clauses.push(`coalesce(c.project, '') IN (${placeholders})`);
|
|
3098
|
+
params.push(...projects);
|
|
3099
|
+
}
|
|
3100
|
+
if (tags3 && tags3.length > 0) {
|
|
3101
|
+
const realTagIds = tags3.filter((t) => t !== "");
|
|
3102
|
+
const wantUntagged = tags3.includes("");
|
|
3103
|
+
if (realTagIds.length > 0) {
|
|
3104
|
+
if (!hasMetadata) {
|
|
3105
|
+
clauses.push("0");
|
|
3106
|
+
} else {
|
|
3107
|
+
const placeholders = realTagIds.map(() => "?").join(", ");
|
|
3108
|
+
clauses.push(
|
|
3109
|
+
`c.id IN (SELECT chat_id FROM meta.chat_tags
|
|
3110
|
+
WHERE tag_id IN (${placeholders})
|
|
3111
|
+
GROUP BY chat_id HAVING count(*) = ?)`
|
|
3112
|
+
);
|
|
3113
|
+
params.push(...realTagIds, realTagIds.length);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
if (wantUntagged && hasMetadata) {
|
|
3117
|
+
clauses.push("c.id NOT IN (SELECT chat_id FROM meta.chat_tags)");
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
return { clauses, params };
|
|
3121
|
+
}
|
|
3122
|
+
|
|
3123
|
+
// src/list-pagination.ts
|
|
3124
|
+
var ARCHIVE_DB = "archive.db";
|
|
3125
|
+
var METADATA_DB = "metadata.db";
|
|
3126
|
+
var SORT_EXPR = {
|
|
3127
|
+
createdAt: "c.created_at",
|
|
3128
|
+
updatedAt: "c.updated_at",
|
|
3129
|
+
deletedAt: "m.deleted_at",
|
|
3130
|
+
// The Title axis orders by the precomputed collation key on the ATTACHed
|
|
3131
|
+
// `meta.chat_sort_keys` (alias `k`), reached through the INNER JOIN added
|
|
3132
|
+
// below (ADR-0019). BINARY compare over `sort_key` is an index range scan.
|
|
3133
|
+
title: "k.sort_key"
|
|
3134
|
+
};
|
|
3135
|
+
function encodeCursor(cursor) {
|
|
3136
|
+
return Buffer.from(JSON.stringify(cursor), "utf8").toString("base64url");
|
|
3137
|
+
}
|
|
3138
|
+
function decodeCursor(token) {
|
|
3139
|
+
try {
|
|
3140
|
+
const parsed = JSON.parse(
|
|
3141
|
+
Buffer.from(token, "base64url").toString("utf8")
|
|
3142
|
+
);
|
|
3143
|
+
const sortKey = parsed?.sortKey;
|
|
3144
|
+
if (parsed && typeof parsed === "object" && (typeof sortKey === "number" || typeof sortKey === "string") && typeof parsed.id === "string") {
|
|
3145
|
+
return parsed;
|
|
3146
|
+
}
|
|
3147
|
+
return null;
|
|
3148
|
+
} catch {
|
|
3149
|
+
return null;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function createChatPageQuery({
|
|
3153
|
+
dataDir: dataDir2
|
|
3154
|
+
}) {
|
|
3155
|
+
const archive2 = new Database(path.join(dataDir2, ARCHIVE_DB), {
|
|
3156
|
+
readonly: true
|
|
3157
|
+
});
|
|
3158
|
+
const metadataPath = path.join(dataDir2, METADATA_DB);
|
|
3159
|
+
const hasMetadata = fs.existsSync(metadataPath);
|
|
3160
|
+
if (hasMetadata) {
|
|
3161
|
+
archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
|
|
3162
|
+
}
|
|
3163
|
+
function queryPage(query) {
|
|
3164
|
+
const sortExpr = SORT_EXPR[query.sort];
|
|
3165
|
+
const needsDeletedJoin = query.sort === "deletedAt";
|
|
3166
|
+
const needsSortKeyJoin = query.sort === "title";
|
|
3167
|
+
if ((needsDeletedJoin || needsSortKeyJoin) && !hasMetadata) {
|
|
3168
|
+
return { items: [], nextCursor: null };
|
|
3169
|
+
}
|
|
3170
|
+
let from = "chats c";
|
|
3171
|
+
if (needsDeletedJoin) from += " JOIN meta.chats_meta m ON m.id = c.id";
|
|
3172
|
+
if (needsSortKeyJoin) from += " JOIN meta.chat_sort_keys k ON k.id = c.id";
|
|
3173
|
+
const idCol = needsDeletedJoin ? "m.id" : needsSortKeyJoin ? "k.id" : "c.id";
|
|
3174
|
+
const clauses = [];
|
|
3175
|
+
const params = [];
|
|
3176
|
+
if (needsDeletedJoin) {
|
|
3177
|
+
clauses.push("m.deleted_at IS NOT NULL");
|
|
3178
|
+
} else if (query.trashedOnly) {
|
|
3179
|
+
if (!hasMetadata) return { items: [], nextCursor: null };
|
|
3180
|
+
clauses.push(
|
|
3181
|
+
"c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
3182
|
+
);
|
|
3183
|
+
} else if (!query.includeTrashed && hasMetadata) {
|
|
3184
|
+
clauses.push(
|
|
3185
|
+
"c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
3186
|
+
);
|
|
3187
|
+
}
|
|
3188
|
+
const filter = buildFilterClauses({
|
|
3189
|
+
projects: query.projects,
|
|
3190
|
+
tags: query.tags,
|
|
3191
|
+
hasMetadata
|
|
3192
|
+
});
|
|
3193
|
+
clauses.push(...filter.clauses);
|
|
3194
|
+
params.push(...filter.params);
|
|
3195
|
+
const direction = query.direction ?? "desc";
|
|
3196
|
+
const cmp = direction === "asc" ? ">" : "<";
|
|
3197
|
+
const order = direction === "asc" ? "ASC" : "DESC";
|
|
3198
|
+
if (query.cursor) {
|
|
3199
|
+
clauses.push(
|
|
3200
|
+
`(${sortExpr} ${cmp} ? OR (${sortExpr} = ? AND ${idCol} ${cmp} ?))`
|
|
3201
|
+
);
|
|
3202
|
+
params.push(query.cursor.sortKey, query.cursor.sortKey, query.cursor.id);
|
|
3203
|
+
}
|
|
3204
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
3205
|
+
const rows = archive2.prepare(
|
|
3206
|
+
`SELECT c.id AS id, ${sortExpr} AS sortKey
|
|
3207
|
+
FROM ${from} ${where}
|
|
3208
|
+
ORDER BY ${sortExpr} ${order}, ${idCol} ${order}
|
|
3209
|
+
LIMIT ?`
|
|
3210
|
+
).all(...params, query.limit + 1);
|
|
3211
|
+
const items = rows.slice(0, query.limit);
|
|
3212
|
+
const last = items[items.length - 1];
|
|
3213
|
+
const nextCursor = rows.length > query.limit && last ? { sortKey: last.sortKey, id: last.id } : null;
|
|
3214
|
+
return { items, nextCursor };
|
|
3215
|
+
}
|
|
3216
|
+
return {
|
|
3217
|
+
queryPage,
|
|
3218
|
+
close() {
|
|
3219
|
+
archive2.close();
|
|
3220
|
+
}
|
|
3221
|
+
};
|
|
3222
|
+
}
|
|
3223
|
+
|
|
2912
3224
|
// src/chat-reader.ts
|
|
2913
3225
|
function toApiBlock(block) {
|
|
2914
3226
|
if (block.type === "tool_result") {
|
|
@@ -2922,7 +3234,10 @@ function toApiBlock(block) {
|
|
|
2922
3234
|
}
|
|
2923
3235
|
function createChatReader({
|
|
2924
3236
|
archive: archive2,
|
|
2925
|
-
metadata: metadata2
|
|
3237
|
+
metadata: metadata2,
|
|
3238
|
+
tags: tags3,
|
|
3239
|
+
pageQuery: pageQuery2,
|
|
3240
|
+
countsQuery: countsQuery2
|
|
2926
3241
|
}) {
|
|
2927
3242
|
function findChat(id) {
|
|
2928
3243
|
const code = parseChatId(id);
|
|
@@ -2931,31 +3246,52 @@ function createChatReader({
|
|
|
2931
3246
|
}
|
|
2932
3247
|
function deriveTitle(customTitle, firstUserText) {
|
|
2933
3248
|
if (customTitle && customTitle.trim()) return customTitle;
|
|
2934
|
-
|
|
2935
|
-
return text2 && text2.length > 0 ? text2 : "Untitled";
|
|
3249
|
+
return deriveBaseTitle(firstUserText);
|
|
2936
3250
|
}
|
|
2937
3251
|
function key(agent, sourceId) {
|
|
2938
3252
|
return [agent, sourceId].join("\0");
|
|
2939
3253
|
}
|
|
2940
3254
|
function listChats({
|
|
2941
|
-
includeTrashed
|
|
3255
|
+
includeTrashed,
|
|
3256
|
+
projects,
|
|
3257
|
+
tags: tagSelection
|
|
2942
3258
|
}) {
|
|
2943
3259
|
const visibility = loadChatVisibility(metadata2, { includeTrashed });
|
|
2944
|
-
const rows = archive2.read.listChatRows();
|
|
3260
|
+
const rows = archive2.read.listChatRows(projects ? { projects } : void 0);
|
|
3261
|
+
let passesTagFilter = null;
|
|
3262
|
+
if (tagSelection) {
|
|
3263
|
+
const realTagIds = tagSelection.filter((t) => t !== "");
|
|
3264
|
+
const wantUntagged = tagSelection.includes("");
|
|
3265
|
+
const allowedByTags = realTagIds.length > 0 ? new Set(tags3.listChatIdsWithAllTags(realTagIds)) : null;
|
|
3266
|
+
const taggedChatIds = wantUntagged ? new Set(tags3.listTagsByChat().keys()) : null;
|
|
3267
|
+
passesTagFilter = (chatInternalId) => {
|
|
3268
|
+
if (allowedByTags && !allowedByTags.has(chatInternalId)) return false;
|
|
3269
|
+
if (taggedChatIds && taggedChatIds.has(chatInternalId)) return false;
|
|
3270
|
+
return true;
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
const toResponse = loadHydration();
|
|
3274
|
+
const chats2 = [];
|
|
3275
|
+
for (const row of rows) {
|
|
3276
|
+
if (!visibility.isVisible(row.id)) continue;
|
|
3277
|
+
if (passesTagFilter && !passesTagFilter(row.id)) continue;
|
|
3278
|
+
chats2.push(toResponse(row, visibility));
|
|
3279
|
+
}
|
|
3280
|
+
return chats2;
|
|
3281
|
+
}
|
|
3282
|
+
function loadHydration(scope) {
|
|
2945
3283
|
const tsRangeByKey = new Map(
|
|
2946
|
-
archive2.read.listChatTsRanges().map((r) => [key(r.agent, r.sourceId), r])
|
|
3284
|
+
archive2.read.listChatTsRanges(scope).map((r) => [key(r.agent, r.sourceId), r])
|
|
2947
3285
|
);
|
|
2948
3286
|
const sourcePathByKey = new Map(
|
|
2949
|
-
archive2.read.listLatestRawSourcePaths().map((r) => [key(r.agent, r.sourceId), r.sourcePath])
|
|
3287
|
+
archive2.read.listLatestRawSourcePaths(scope).map((r) => [key(r.agent, r.sourceId), r.sourcePath])
|
|
2950
3288
|
);
|
|
2951
3289
|
const firstUserTextByKey = new Map(
|
|
2952
|
-
archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
|
|
3290
|
+
archive2.read.listFirstUserTexts(scope).map((r) => [key(r.agent, r.sourceId), r.text])
|
|
2953
3291
|
);
|
|
2954
3292
|
const customTitleById = metadata2.listCustomTitles();
|
|
2955
|
-
const
|
|
2956
|
-
|
|
2957
|
-
if (!visibility.isVisible(row.id)) continue;
|
|
2958
|
-
const isDeleted = visibility.isTrashed(row.id);
|
|
3293
|
+
const tagsByChatId = tags3.listTagsByChat();
|
|
3294
|
+
return (row, visibility) => {
|
|
2959
3295
|
const rowKey = key(row.agent, row.sourceId);
|
|
2960
3296
|
const tsRange = tsRangeByKey.get(rowKey);
|
|
2961
3297
|
const firstSeenAtMs = row.firstSeenAt.getTime();
|
|
@@ -2972,12 +3308,73 @@ function createChatReader({
|
|
|
2972
3308
|
sourceFilePath: sourcePathByKey.get(rowKey) ?? null,
|
|
2973
3309
|
createdAt: tsRange?.minTs ?? firstSeenAtMs,
|
|
2974
3310
|
updatedAt: tsRange?.maxTs ?? firstSeenAtMs,
|
|
2975
|
-
deletedAt: visibility.deletedAt(row.id)
|
|
3311
|
+
deletedAt: visibility.deletedAt(row.id),
|
|
3312
|
+
tags: tagsByChatId.get(row.id) ?? []
|
|
2976
3313
|
};
|
|
2977
|
-
if (
|
|
2978
|
-
|
|
3314
|
+
if (visibility.isTrashed(row.id)) chat.isDeleted = true;
|
|
3315
|
+
return chat;
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
function listChatsPage({
|
|
3319
|
+
sort,
|
|
3320
|
+
direction,
|
|
3321
|
+
limit,
|
|
3322
|
+
cursor,
|
|
3323
|
+
includeTrashed = false,
|
|
3324
|
+
trashedOnly = false,
|
|
3325
|
+
projects,
|
|
3326
|
+
tags: tagSelection
|
|
3327
|
+
}) {
|
|
3328
|
+
if (!pageQuery2) {
|
|
3329
|
+
throw new Error("listChatsPage requires a pageQuery dependency");
|
|
3330
|
+
}
|
|
3331
|
+
const decoded = cursor ? decodeCursor(cursor) ?? void 0 : void 0;
|
|
3332
|
+
const page = pageQuery2.queryPage({
|
|
3333
|
+
sort,
|
|
3334
|
+
direction,
|
|
3335
|
+
limit,
|
|
3336
|
+
cursor: decoded,
|
|
3337
|
+
includeTrashed,
|
|
3338
|
+
trashedOnly,
|
|
3339
|
+
projects,
|
|
3340
|
+
tags: tagSelection
|
|
3341
|
+
});
|
|
3342
|
+
const visibility = loadChatVisibility(metadata2, {
|
|
3343
|
+
includeTrashed: includeTrashed || trashedOnly
|
|
3344
|
+
});
|
|
3345
|
+
const pageIds = page.items.map((item) => item.id);
|
|
3346
|
+
const rows = archive2.read.listChatRowsByIds(pageIds);
|
|
3347
|
+
const rowById = new Map(rows.map((r) => [r.id, r]));
|
|
3348
|
+
const toResponse = loadHydration({
|
|
3349
|
+
sourceIds: rows.map((r) => r.sourceId)
|
|
3350
|
+
});
|
|
3351
|
+
const chats2 = [];
|
|
3352
|
+
for (const item of page.items) {
|
|
3353
|
+
const row = rowById.get(item.id);
|
|
3354
|
+
if (row) chats2.push(toResponse(row, visibility));
|
|
2979
3355
|
}
|
|
2980
|
-
return
|
|
3356
|
+
return {
|
|
3357
|
+
chats: chats2,
|
|
3358
|
+
nextCursor: page.nextCursor ? encodeCursor(page.nextCursor) : null
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
function listCounts({
|
|
3362
|
+
includeTrashed = false
|
|
3363
|
+
}) {
|
|
3364
|
+
if (!countsQuery2) {
|
|
3365
|
+
throw new Error("listCounts requires a countsQuery dependency");
|
|
3366
|
+
}
|
|
3367
|
+
return countsQuery2.queryCounts({ includeTrashed });
|
|
3368
|
+
}
|
|
3369
|
+
function listFilteredTotal({
|
|
3370
|
+
includeTrashed = false,
|
|
3371
|
+
projects,
|
|
3372
|
+
tags: tags4
|
|
3373
|
+
}) {
|
|
3374
|
+
if (!countsQuery2) {
|
|
3375
|
+
throw new Error("listFilteredTotal requires a countsQuery dependency");
|
|
3376
|
+
}
|
|
3377
|
+
return countsQuery2.queryFilteredTotal({ includeTrashed, projects, tags: tags4 });
|
|
2981
3378
|
}
|
|
2982
3379
|
function getMessages(id, { includeTrashed }) {
|
|
2983
3380
|
const row = findChat(id);
|
|
@@ -2991,19 +3388,122 @@ function createChatReader({
|
|
|
2991
3388
|
timestamp: m.ts.toISOString()
|
|
2992
3389
|
}));
|
|
2993
3390
|
}
|
|
2994
|
-
return {
|
|
3391
|
+
return {
|
|
3392
|
+
listChats,
|
|
3393
|
+
listChatsPage,
|
|
3394
|
+
listCounts,
|
|
3395
|
+
listFilteredTotal,
|
|
3396
|
+
getMessages,
|
|
3397
|
+
findChat
|
|
3398
|
+
};
|
|
2995
3399
|
}
|
|
2996
3400
|
|
|
3401
|
+
// src/list-contract.ts
|
|
3402
|
+
var MAX_PAGE_LIMIT = 200;
|
|
3403
|
+
|
|
2997
3404
|
// src/app.ts
|
|
2998
|
-
|
|
3405
|
+
var STREAM_HEARTBEAT_MS = 25e3;
|
|
3406
|
+
function createApp({
|
|
3407
|
+
archive: archive2,
|
|
3408
|
+
metadata: metadata2,
|
|
3409
|
+
tags: tags3,
|
|
3410
|
+
pageQuery: pageQuery2,
|
|
3411
|
+
countsQuery: countsQuery2,
|
|
3412
|
+
listEvents: listEvents2,
|
|
3413
|
+
webDistDir: webDistDir2
|
|
3414
|
+
}) {
|
|
2999
3415
|
const app2 = new Hono2();
|
|
3000
|
-
const reader = createChatReader({
|
|
3416
|
+
const reader = createChatReader({
|
|
3417
|
+
archive: archive2,
|
|
3418
|
+
metadata: metadata2,
|
|
3419
|
+
tags: tags3,
|
|
3420
|
+
pageQuery: pageQuery2,
|
|
3421
|
+
countsQuery: countsQuery2
|
|
3422
|
+
});
|
|
3001
3423
|
app2.get("/api/chats", (c) => {
|
|
3424
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3425
|
+
const limitParam = c.req.query("limit");
|
|
3426
|
+
if (limitParam !== void 0) {
|
|
3427
|
+
if (!pageQuery2) {
|
|
3428
|
+
return c.json({ error: "Pagination is not available" }, 501);
|
|
3429
|
+
}
|
|
3430
|
+
const limit = Number.parseInt(limitParam, 10);
|
|
3431
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_PAGE_LIMIT) {
|
|
3432
|
+
return c.json({ error: "Invalid limit" }, 400);
|
|
3433
|
+
}
|
|
3434
|
+
const sort = c.req.query("sort") ?? "updatedAt";
|
|
3435
|
+
if (sort !== "createdAt" && sort !== "updatedAt" && sort !== "deletedAt" && sort !== "title") {
|
|
3436
|
+
return c.json({ error: "Invalid sort" }, 400);
|
|
3437
|
+
}
|
|
3438
|
+
const direction = c.req.query("direction") ?? "desc";
|
|
3439
|
+
if (direction !== "asc" && direction !== "desc") {
|
|
3440
|
+
return c.json({ error: "Invalid direction" }, 400);
|
|
3441
|
+
}
|
|
3442
|
+
const trashedOnly = c.req.query("trashedOnly") === "true";
|
|
3443
|
+
const projects2 = c.req.queries("project");
|
|
3444
|
+
const tagsParam2 = c.req.query("tags");
|
|
3445
|
+
const tagSelection = tagsParam2 === void 0 ? void 0 : tagsParam2.split(",");
|
|
3446
|
+
const { chats: chats3, nextCursor } = reader.listChatsPage({
|
|
3447
|
+
sort,
|
|
3448
|
+
direction,
|
|
3449
|
+
limit,
|
|
3450
|
+
cursor: c.req.query("cursor"),
|
|
3451
|
+
includeTrashed,
|
|
3452
|
+
trashedOnly,
|
|
3453
|
+
projects: projects2,
|
|
3454
|
+
tags: tagSelection
|
|
3455
|
+
});
|
|
3456
|
+
return c.json({ chats: chats3, nextCursor });
|
|
3457
|
+
}
|
|
3458
|
+
const projects = c.req.queries("project");
|
|
3459
|
+
const tagsParam = c.req.query("tags");
|
|
3460
|
+
const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
|
|
3002
3461
|
const chats2 = reader.listChats({
|
|
3003
|
-
includeTrashed
|
|
3462
|
+
includeTrashed,
|
|
3463
|
+
projects,
|
|
3464
|
+
tags: tags4
|
|
3004
3465
|
});
|
|
3005
3466
|
return c.json({ chats: chats2 });
|
|
3006
3467
|
});
|
|
3468
|
+
app2.get("/api/chats/stream", (c) => {
|
|
3469
|
+
if (!listEvents2) {
|
|
3470
|
+
return c.json({ error: "Live updates are not available" }, 501);
|
|
3471
|
+
}
|
|
3472
|
+
const hub = listEvents2;
|
|
3473
|
+
return streamSSE(c, async (stream2) => {
|
|
3474
|
+
const unsubscribe = hub.subscribe((event) => {
|
|
3475
|
+
void stream2.writeSSE({ event: event.type, data: "" });
|
|
3476
|
+
});
|
|
3477
|
+
await new Promise((resolve3) => {
|
|
3478
|
+
const heartbeat = setInterval(() => {
|
|
3479
|
+
void stream2.writeSSE({ event: "ping", data: "" });
|
|
3480
|
+
}, STREAM_HEARTBEAT_MS);
|
|
3481
|
+
stream2.onAbort(() => {
|
|
3482
|
+
clearInterval(heartbeat);
|
|
3483
|
+
unsubscribe();
|
|
3484
|
+
resolve3();
|
|
3485
|
+
});
|
|
3486
|
+
});
|
|
3487
|
+
});
|
|
3488
|
+
});
|
|
3489
|
+
app2.get("/api/chats/counts", (c) => {
|
|
3490
|
+
if (!countsQuery2) {
|
|
3491
|
+
return c.json({ error: "Counts are not available" }, 501);
|
|
3492
|
+
}
|
|
3493
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3494
|
+
return c.json(reader.listCounts({ includeTrashed }));
|
|
3495
|
+
});
|
|
3496
|
+
app2.get("/api/chats/list-total", (c) => {
|
|
3497
|
+
if (!countsQuery2) {
|
|
3498
|
+
return c.json({ error: "Counts are not available" }, 501);
|
|
3499
|
+
}
|
|
3500
|
+
const includeTrashed = c.req.query("includeTrashed") === "true";
|
|
3501
|
+
const projects = c.req.queries("project");
|
|
3502
|
+
const tagsParam = c.req.query("tags");
|
|
3503
|
+
const tags4 = tagsParam === void 0 ? void 0 : tagsParam.split(",");
|
|
3504
|
+
const total = reader.listFilteredTotal({ includeTrashed, projects, tags: tags4 });
|
|
3505
|
+
return c.json({ total });
|
|
3506
|
+
});
|
|
3007
3507
|
app2.delete("/api/chats/:id", (c) => {
|
|
3008
3508
|
const row = reader.findChat(c.req.param("id"));
|
|
3009
3509
|
if (!row) {
|
|
@@ -3051,6 +3551,85 @@ function createApp({ archive: archive2, metadata: metadata2, webDistDir: webDist
|
|
|
3051
3551
|
}
|
|
3052
3552
|
return c.json({ messages: messages2 });
|
|
3053
3553
|
});
|
|
3554
|
+
app2.get("/api/tags", (c) => {
|
|
3555
|
+
return c.json({ tags: tags3.listTags() });
|
|
3556
|
+
});
|
|
3557
|
+
app2.post("/api/tags", async (c) => {
|
|
3558
|
+
let body;
|
|
3559
|
+
try {
|
|
3560
|
+
body = await c.req.json();
|
|
3561
|
+
} catch {
|
|
3562
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3563
|
+
}
|
|
3564
|
+
const name = body && typeof body === "object" ? body.name : void 0;
|
|
3565
|
+
const color = body && typeof body === "object" ? body.color : void 0;
|
|
3566
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
3567
|
+
return c.json({ error: "Invalid name" }, 400);
|
|
3568
|
+
}
|
|
3569
|
+
if (!isColorToken(color)) {
|
|
3570
|
+
return c.json({ error: "Invalid color" }, 400);
|
|
3571
|
+
}
|
|
3572
|
+
const tag = tags3.createTag(name.trim(), color);
|
|
3573
|
+
return c.json({ tag }, 201);
|
|
3574
|
+
});
|
|
3575
|
+
app2.patch("/api/tags/:id", async (c) => {
|
|
3576
|
+
const id = c.req.param("id");
|
|
3577
|
+
if (!tags3.getTag(id)) {
|
|
3578
|
+
return c.json({ error: "Tag not found" }, 404);
|
|
3579
|
+
}
|
|
3580
|
+
let body;
|
|
3581
|
+
try {
|
|
3582
|
+
body = await c.req.json();
|
|
3583
|
+
} catch {
|
|
3584
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3585
|
+
}
|
|
3586
|
+
const name = body && typeof body === "object" ? body.name : void 0;
|
|
3587
|
+
const color = body && typeof body === "object" ? body.color : void 0;
|
|
3588
|
+
if (name !== void 0) {
|
|
3589
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
3590
|
+
return c.json({ error: "Invalid name" }, 400);
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
if (color !== void 0 && !isColorToken(color)) {
|
|
3594
|
+
return c.json({ error: "Invalid color" }, 400);
|
|
3595
|
+
}
|
|
3596
|
+
if (typeof name === "string") tags3.renameTag(id, name.trim());
|
|
3597
|
+
if (isColorToken(color)) tags3.recolorTag(id, color);
|
|
3598
|
+
return c.body(null, 204);
|
|
3599
|
+
});
|
|
3600
|
+
app2.delete("/api/tags/:id", (c) => {
|
|
3601
|
+
if (!tags3.getTag(c.req.param("id"))) {
|
|
3602
|
+
return c.json({ error: "Tag not found" }, 404);
|
|
3603
|
+
}
|
|
3604
|
+
const result = tags3.deleteTag(c.req.param("id"));
|
|
3605
|
+
return c.json(result);
|
|
3606
|
+
});
|
|
3607
|
+
app2.post("/api/chats/:id/tags", async (c) => {
|
|
3608
|
+
const row = reader.findChat(c.req.param("id"));
|
|
3609
|
+
if (!row) {
|
|
3610
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
3611
|
+
}
|
|
3612
|
+
let body;
|
|
3613
|
+
try {
|
|
3614
|
+
body = await c.req.json();
|
|
3615
|
+
} catch {
|
|
3616
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
3617
|
+
}
|
|
3618
|
+
const tagId = body && typeof body === "object" ? body.tagId : void 0;
|
|
3619
|
+
if (typeof tagId !== "string" || !tags3.getTag(tagId)) {
|
|
3620
|
+
return c.json({ error: "Tag not found" }, 404);
|
|
3621
|
+
}
|
|
3622
|
+
tags3.assignTag(row.id, tagId);
|
|
3623
|
+
return c.body(null, 204);
|
|
3624
|
+
});
|
|
3625
|
+
app2.delete("/api/chats/:id/tags/:tagId", (c) => {
|
|
3626
|
+
const row = reader.findChat(c.req.param("id"));
|
|
3627
|
+
if (!row) {
|
|
3628
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
3629
|
+
}
|
|
3630
|
+
tags3.removeTag(row.id, c.req.param("tagId"));
|
|
3631
|
+
return c.body(null, 204);
|
|
3632
|
+
});
|
|
3054
3633
|
if (webDistDir2) {
|
|
3055
3634
|
app2.use("*", serveStatic({ root: webDistDir2 }));
|
|
3056
3635
|
app2.use("*", serveStatic({ root: webDistDir2, path: "index.html" }));
|
|
@@ -4374,7 +4953,7 @@ var QueryPromise = class {
|
|
|
4374
4953
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
4375
4954
|
const nullifyMap = {};
|
|
4376
4955
|
const result = columns.reduce(
|
|
4377
|
-
(result2, { path:
|
|
4956
|
+
(result2, { path: path8, field }, columnIndex) => {
|
|
4378
4957
|
let decoder;
|
|
4379
4958
|
if (is(field, Column)) {
|
|
4380
4959
|
decoder = field;
|
|
@@ -4386,8 +4965,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4386
4965
|
decoder = field.sql.decoder;
|
|
4387
4966
|
}
|
|
4388
4967
|
let node = result2;
|
|
4389
|
-
for (const [pathChunkIndex, pathChunk] of
|
|
4390
|
-
if (pathChunkIndex <
|
|
4968
|
+
for (const [pathChunkIndex, pathChunk] of path8.entries()) {
|
|
4969
|
+
if (pathChunkIndex < path8.length - 1) {
|
|
4391
4970
|
if (!(pathChunk in node)) {
|
|
4392
4971
|
node[pathChunk] = {};
|
|
4393
4972
|
}
|
|
@@ -4395,8 +4974,8 @@ function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
|
4395
4974
|
} else {
|
|
4396
4975
|
const rawValue = row[columnIndex];
|
|
4397
4976
|
const value = node[pathChunk] = rawValue === null ? null : decoder.mapFromDriverValue(rawValue);
|
|
4398
|
-
if (joinsNotNullableMap && is(field, Column) &&
|
|
4399
|
-
const objectName =
|
|
4977
|
+
if (joinsNotNullableMap && is(field, Column) && path8.length === 2) {
|
|
4978
|
+
const objectName = path8[0];
|
|
4400
4979
|
if (!(objectName in nullifyMap)) {
|
|
4401
4980
|
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
4402
4981
|
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
@@ -4440,8 +5019,8 @@ function haveSameKeys(left, right) {
|
|
|
4440
5019
|
if (leftKeys.length !== rightKeys.length) {
|
|
4441
5020
|
return false;
|
|
4442
5021
|
}
|
|
4443
|
-
for (const [
|
|
4444
|
-
if (key !== rightKeys[
|
|
5022
|
+
for (const [index2, key] of leftKeys.entries()) {
|
|
5023
|
+
if (key !== rightKeys[index2]) {
|
|
4445
5024
|
return false;
|
|
4446
5025
|
}
|
|
4447
5026
|
}
|
|
@@ -4821,19 +5400,19 @@ function extractTablesRelationalConfig(schema, configHelpers) {
|
|
|
4821
5400
|
const relations2 = value.config(
|
|
4822
5401
|
configHelpers(value.table)
|
|
4823
5402
|
);
|
|
4824
|
-
let
|
|
5403
|
+
let primaryKey2;
|
|
4825
5404
|
for (const [relationName, relation] of Object.entries(relations2)) {
|
|
4826
5405
|
if (tableName) {
|
|
4827
5406
|
const tableConfig = tablesConfig[tableName];
|
|
4828
5407
|
tableConfig.relations[relationName] = relation;
|
|
4829
|
-
if (
|
|
4830
|
-
tableConfig.primaryKey.push(...
|
|
5408
|
+
if (primaryKey2) {
|
|
5409
|
+
tableConfig.primaryKey.push(...primaryKey2);
|
|
4831
5410
|
}
|
|
4832
5411
|
} else {
|
|
4833
5412
|
if (!(dbName in relationsBuffer)) {
|
|
4834
5413
|
relationsBuffer[dbName] = {
|
|
4835
5414
|
relations: {},
|
|
4836
|
-
primaryKey
|
|
5415
|
+
primaryKey: primaryKey2
|
|
4837
5416
|
};
|
|
4838
5417
|
}
|
|
4839
5418
|
relationsBuffer[dbName].relations[relationName] = relation;
|
|
@@ -4956,10 +5535,10 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
4956
5535
|
}
|
|
4957
5536
|
|
|
4958
5537
|
// src/storage/openStore.ts
|
|
4959
|
-
import
|
|
4960
|
-
import
|
|
5538
|
+
import fs3 from "fs";
|
|
5539
|
+
import path2 from "path";
|
|
4961
5540
|
import { fileURLToPath } from "url";
|
|
4962
|
-
import
|
|
5541
|
+
import Database2 from "better-sqlite3";
|
|
4963
5542
|
|
|
4964
5543
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/better-sqlite3/driver.js
|
|
4965
5544
|
import Client from "better-sqlite3";
|
|
@@ -5677,10 +6256,49 @@ var Index = class {
|
|
|
5677
6256
|
this.config = { ...config, table };
|
|
5678
6257
|
}
|
|
5679
6258
|
};
|
|
6259
|
+
function index(name) {
|
|
6260
|
+
return new IndexBuilderOn(name, false);
|
|
6261
|
+
}
|
|
5680
6262
|
function uniqueIndex(name) {
|
|
5681
6263
|
return new IndexBuilderOn(name, true);
|
|
5682
6264
|
}
|
|
5683
6265
|
|
|
6266
|
+
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/sqlite-core/primary-keys.js
|
|
6267
|
+
function primaryKey(...config) {
|
|
6268
|
+
if (config[0].columns) {
|
|
6269
|
+
return new PrimaryKeyBuilder2(config[0].columns, config[0].name);
|
|
6270
|
+
}
|
|
6271
|
+
return new PrimaryKeyBuilder2(config);
|
|
6272
|
+
}
|
|
6273
|
+
var PrimaryKeyBuilder2 = class {
|
|
6274
|
+
static [entityKind] = "SQLitePrimaryKeyBuilder";
|
|
6275
|
+
/** @internal */
|
|
6276
|
+
columns;
|
|
6277
|
+
/** @internal */
|
|
6278
|
+
name;
|
|
6279
|
+
constructor(columns, name) {
|
|
6280
|
+
this.columns = columns;
|
|
6281
|
+
this.name = name;
|
|
6282
|
+
}
|
|
6283
|
+
/** @internal */
|
|
6284
|
+
build(table) {
|
|
6285
|
+
return new PrimaryKey2(table, this.columns, this.name);
|
|
6286
|
+
}
|
|
6287
|
+
};
|
|
6288
|
+
var PrimaryKey2 = class {
|
|
6289
|
+
constructor(table, columns, name) {
|
|
6290
|
+
this.table = table;
|
|
6291
|
+
this.columns = columns;
|
|
6292
|
+
this.name = name;
|
|
6293
|
+
}
|
|
6294
|
+
static [entityKind] = "SQLitePrimaryKey";
|
|
6295
|
+
columns;
|
|
6296
|
+
name;
|
|
6297
|
+
getName() {
|
|
6298
|
+
return this.name ?? `${this.table[SQLiteTable.Symbol.Name]}_${this.columns.map((column) => column.name).join("_")}_pk`;
|
|
6299
|
+
}
|
|
6300
|
+
};
|
|
6301
|
+
|
|
5684
6302
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/sqlite-core/utils.js
|
|
5685
6303
|
function extractUsedTable(table) {
|
|
5686
6304
|
if (is(table, SQLiteTable)) {
|
|
@@ -6029,8 +6647,8 @@ var SQLiteDialect = class {
|
|
|
6029
6647
|
}
|
|
6030
6648
|
const joinsArray = [];
|
|
6031
6649
|
if (joins) {
|
|
6032
|
-
for (const [
|
|
6033
|
-
if (
|
|
6650
|
+
for (const [index2, joinMeta] of joins.entries()) {
|
|
6651
|
+
if (index2 === 0) {
|
|
6034
6652
|
joinsArray.push(sql` `);
|
|
6035
6653
|
}
|
|
6036
6654
|
const table = joinMeta.table;
|
|
@@ -6050,7 +6668,7 @@ var SQLiteDialect = class {
|
|
|
6050
6668
|
sql`${sql.raw(joinMeta.joinType)} join ${table}${onSql}`
|
|
6051
6669
|
);
|
|
6052
6670
|
}
|
|
6053
|
-
if (
|
|
6671
|
+
if (index2 < joins.length - 1) {
|
|
6054
6672
|
joinsArray.push(sql` `);
|
|
6055
6673
|
}
|
|
6056
6674
|
}
|
|
@@ -6063,9 +6681,9 @@ var SQLiteDialect = class {
|
|
|
6063
6681
|
buildOrderBy(orderBy) {
|
|
6064
6682
|
const orderByList = [];
|
|
6065
6683
|
if (orderBy) {
|
|
6066
|
-
for (const [
|
|
6684
|
+
for (const [index2, orderByValue] of orderBy.entries()) {
|
|
6067
6685
|
orderByList.push(orderByValue);
|
|
6068
|
-
if (
|
|
6686
|
+
if (index2 < orderBy.length - 1) {
|
|
6069
6687
|
orderByList.push(sql`, `);
|
|
6070
6688
|
}
|
|
6071
6689
|
}
|
|
@@ -6118,9 +6736,9 @@ var SQLiteDialect = class {
|
|
|
6118
6736
|
const havingSql = having ? sql` having ${having}` : void 0;
|
|
6119
6737
|
const groupByList = [];
|
|
6120
6738
|
if (groupBy) {
|
|
6121
|
-
for (const [
|
|
6739
|
+
for (const [index2, groupByValue] of groupBy.entries()) {
|
|
6122
6740
|
groupByList.push(groupByValue);
|
|
6123
|
-
if (
|
|
6741
|
+
if (index2 < groupBy.length - 1) {
|
|
6124
6742
|
groupByList.push(sql`, `);
|
|
6125
6743
|
}
|
|
6126
6744
|
}
|
|
@@ -8527,20 +9145,20 @@ function drizzle(...params) {
|
|
|
8527
9145
|
|
|
8528
9146
|
// ../node_modules/.pnpm/drizzle-orm@0.45.2_@types+better-sqlite3@7.6.13_better-sqlite3@12.9.0/node_modules/drizzle-orm/migrator.js
|
|
8529
9147
|
import crypto4 from "crypto";
|
|
8530
|
-
import
|
|
9148
|
+
import fs2 from "fs";
|
|
8531
9149
|
function readMigrationFiles(config) {
|
|
8532
9150
|
const migrationFolderTo = config.migrationsFolder;
|
|
8533
9151
|
const migrationQueries = [];
|
|
8534
9152
|
const journalPath = `${migrationFolderTo}/meta/_journal.json`;
|
|
8535
|
-
if (!
|
|
9153
|
+
if (!fs2.existsSync(journalPath)) {
|
|
8536
9154
|
throw new Error(`Can't find meta/_journal.json file`);
|
|
8537
9155
|
}
|
|
8538
|
-
const journalAsString =
|
|
9156
|
+
const journalAsString = fs2.readFileSync(`${migrationFolderTo}/meta/_journal.json`).toString();
|
|
8539
9157
|
const journal = JSON.parse(journalAsString);
|
|
8540
9158
|
for (const journalEntry of journal.entries) {
|
|
8541
9159
|
const migrationPath = `${migrationFolderTo}/${journalEntry.tag}.sql`;
|
|
8542
9160
|
try {
|
|
8543
|
-
const query =
|
|
9161
|
+
const query = fs2.readFileSync(`${migrationFolderTo}/${journalEntry.tag}.sql`).toString();
|
|
8544
9162
|
const result = query.split("--> statement-breakpoint").map((it) => {
|
|
8545
9163
|
return it;
|
|
8546
9164
|
});
|
|
@@ -8565,12 +9183,12 @@ function migrate(db, config) {
|
|
|
8565
9183
|
|
|
8566
9184
|
// src/storage/openStore.ts
|
|
8567
9185
|
function resolveMigrationsFolder(callerUrl, migrationsSubdir) {
|
|
8568
|
-
const here =
|
|
9186
|
+
const here = path2.dirname(fileURLToPath(callerUrl));
|
|
8569
9187
|
const candidates = [
|
|
8570
|
-
|
|
8571
|
-
|
|
9188
|
+
path2.join(here, "../..", migrationsSubdir),
|
|
9189
|
+
path2.join(here, ".", migrationsSubdir)
|
|
8572
9190
|
];
|
|
8573
|
-
const found = candidates.find((p) =>
|
|
9191
|
+
const found = candidates.find((p) => fs3.existsSync(p));
|
|
8574
9192
|
if (!found) {
|
|
8575
9193
|
throw new Error(`Could not locate migrations folder "${migrationsSubdir}"`);
|
|
8576
9194
|
}
|
|
@@ -8584,8 +9202,8 @@ function openStore({
|
|
|
8584
9202
|
schema
|
|
8585
9203
|
}) {
|
|
8586
9204
|
const migrationsFolder = resolveMigrationsFolder(callerUrl, migrationsSubdir);
|
|
8587
|
-
|
|
8588
|
-
const sqlite = new
|
|
9205
|
+
fs3.mkdirSync(dataDir2, { recursive: true });
|
|
9206
|
+
const sqlite = new Database2(path2.join(dataDir2, dbFile));
|
|
8589
9207
|
const db = drizzle(sqlite, { schema });
|
|
8590
9208
|
migrate(db, { migrationsFolder });
|
|
8591
9209
|
return { db, sqlite };
|
|
@@ -8618,10 +9236,33 @@ var chats = sqliteTable(
|
|
|
8618
9236
|
agent: text("agent").notNull(),
|
|
8619
9237
|
sourceId: text("source_id").notNull(),
|
|
8620
9238
|
firstSeenAt: integer("first_seen_at", { mode: "timestamp_ms" }).notNull(),
|
|
9239
|
+
// Denormalized "conversation start" = min(messages.ts), kept current at
|
|
9240
|
+
// ingest and backfilled in migration 0010. The createdAt sort pages by this
|
|
9241
|
+
// (not first_seen_at, the ingest time) so the paged order matches the
|
|
9242
|
+
// reader's displayed createdAt (issue #143, reconciling ADR-0017's caveat).
|
|
9243
|
+
// Initialized to first_seen_at for a chat with no messages; since
|
|
9244
|
+
// first_seen_at is the ingest time and so >= every message ts, the running
|
|
9245
|
+
// min over (first_seen_at, message ts) equals min(messages.ts) once any
|
|
9246
|
+
// message exists, and first_seen_at when none — never NULL.
|
|
9247
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }),
|
|
9248
|
+
// Denormalized "most recent activity" = max(messages.ts), kept current at
|
|
9249
|
+
// ingest and backfilled in migration 0009. Lets the activity sort run as a
|
|
9250
|
+
// keyset index range scan instead of an aggregate that defeats the index
|
|
9251
|
+
// (ADR-0017). Initialized to first_seen_at for a chat with no messages, so
|
|
9252
|
+
// it is never NULL and always agrees with the reader's derived `updatedAt`.
|
|
9253
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }),
|
|
8621
9254
|
project: text("project"),
|
|
8622
9255
|
projectPath: text("project_path")
|
|
8623
9256
|
},
|
|
8624
|
-
(t) => [
|
|
9257
|
+
(t) => [
|
|
9258
|
+
uniqueIndex("chats_agent_source_idx").on(t.agent, t.sourceId),
|
|
9259
|
+
// Backs the server-side Project filter (`WHERE coalesce(project,'') IN …`).
|
|
9260
|
+
index("chats_project_idx").on(t.project),
|
|
9261
|
+
// Covering keyset indexes for the two server-side list sorts (ADR-0017):
|
|
9262
|
+
// (sortKey, id) so the ORDER BY + LIMIT is an index range scan either way.
|
|
9263
|
+
index("chats_created_keyset_idx").on(t.createdAt, t.id),
|
|
9264
|
+
index("chats_updated_keyset_idx").on(t.updatedAt, t.id)
|
|
9265
|
+
]
|
|
8625
9266
|
);
|
|
8626
9267
|
var rawMessages = sqliteTable(
|
|
8627
9268
|
"raw_messages",
|
|
@@ -8669,9 +9310,17 @@ var ingestionEvents = sqliteTable("ingestion_events", {
|
|
|
8669
9310
|
// src/archive/read-seam.ts
|
|
8670
9311
|
function createArchiveReadSeam(db) {
|
|
8671
9312
|
return {
|
|
8672
|
-
listChatRows() {
|
|
9313
|
+
listChatRows(opts) {
|
|
9314
|
+
const projects = opts?.projects;
|
|
9315
|
+
if (projects) {
|
|
9316
|
+
return db.select().from(chats).where(inArray(sql`coalesce(${chats.project}, '')`, projects)).all();
|
|
9317
|
+
}
|
|
8673
9318
|
return db.select().from(chats).all();
|
|
8674
9319
|
},
|
|
9320
|
+
listChatRowsByIds(ids) {
|
|
9321
|
+
if (ids.length === 0) return [];
|
|
9322
|
+
return db.select().from(chats).where(inArray(chats.id, ids)).all();
|
|
9323
|
+
},
|
|
8675
9324
|
findChatBySourceId(sourceId) {
|
|
8676
9325
|
return db.select().from(chats).where(eq(chats.sourceId, sourceId)).get() ?? null;
|
|
8677
9326
|
},
|
|
@@ -8681,15 +9330,17 @@ function createArchiveReadSeam(db) {
|
|
|
8681
9330
|
listMessagesByChat(agent, sourceId) {
|
|
8682
9331
|
return db.select().from(messages).where(and(eq(messages.agent, agent), eq(messages.sourceId, sourceId))).orderBy(asc(messages.ts)).all();
|
|
8683
9332
|
},
|
|
8684
|
-
listChatTsRanges() {
|
|
9333
|
+
listChatTsRanges(opts) {
|
|
9334
|
+
const scope = opts?.sourceIds !== void 0 ? inArray(messages.sourceId, opts.sourceIds) : sql`1 = 1`;
|
|
8685
9335
|
return db.select({
|
|
8686
9336
|
agent: messages.agent,
|
|
8687
9337
|
sourceId: messages.sourceId,
|
|
8688
9338
|
minTs: sql`min(${messages.ts})`,
|
|
8689
9339
|
maxTs: sql`max(${messages.ts})`
|
|
8690
|
-
}).from(messages).groupBy(messages.agent, messages.sourceId).all();
|
|
9340
|
+
}).from(messages).where(scope).groupBy(messages.agent, messages.sourceId).all();
|
|
8691
9341
|
},
|
|
8692
|
-
listLatestRawSourcePaths() {
|
|
9342
|
+
listLatestRawSourcePaths(opts) {
|
|
9343
|
+
const scope = opts?.sourceIds !== void 0 ? inArray(rawMessages.sourceId, opts.sourceIds) : sql`1 = 1`;
|
|
8693
9344
|
const ranked = db.select({
|
|
8694
9345
|
agent: rawMessages.agent,
|
|
8695
9346
|
sourceId: rawMessages.sourceId,
|
|
@@ -8697,14 +9348,18 @@ function createArchiveReadSeam(db) {
|
|
|
8697
9348
|
rn: sql`row_number() over (partition by ${rawMessages.agent}, ${rawMessages.sourceId} order by ${rawMessages.ingestedAt} desc)`.as(
|
|
8698
9349
|
"rn"
|
|
8699
9350
|
)
|
|
8700
|
-
}).from(rawMessages).as("ranked_raw");
|
|
9351
|
+
}).from(rawMessages).where(scope).as("ranked_raw");
|
|
8701
9352
|
return db.select({
|
|
8702
9353
|
agent: ranked.agent,
|
|
8703
9354
|
sourceId: ranked.sourceId,
|
|
8704
9355
|
sourcePath: ranked.sourcePath
|
|
8705
9356
|
}).from(ranked).where(eq(ranked.rn, 1)).all();
|
|
8706
9357
|
},
|
|
8707
|
-
listFirstUserTexts() {
|
|
9358
|
+
listFirstUserTexts(opts) {
|
|
9359
|
+
const scope = opts?.sourceIds !== void 0 ? and(
|
|
9360
|
+
eq(messages.role, "user"),
|
|
9361
|
+
inArray(messages.sourceId, opts.sourceIds)
|
|
9362
|
+
) : eq(messages.role, "user");
|
|
8708
9363
|
const ranked = db.select({
|
|
8709
9364
|
agent: messages.agent,
|
|
8710
9365
|
sourceId: messages.sourceId,
|
|
@@ -8712,7 +9367,7 @@ function createArchiveReadSeam(db) {
|
|
|
8712
9367
|
rn: sql`row_number() over (partition by ${messages.agent}, ${messages.sourceId} order by ${messages.ts} asc)`.as(
|
|
8713
9368
|
"rn"
|
|
8714
9369
|
)
|
|
8715
|
-
}).from(messages).where(
|
|
9370
|
+
}).from(messages).where(scope).as("ranked_user");
|
|
8716
9371
|
return db.select({
|
|
8717
9372
|
agent: ranked.agent,
|
|
8718
9373
|
sourceId: ranked.sourceId,
|
|
@@ -8795,6 +9450,12 @@ function createArchiveRepository({
|
|
|
8795
9450
|
agent,
|
|
8796
9451
|
sourceId,
|
|
8797
9452
|
firstSeenAt,
|
|
9453
|
+
// Seed both denormalized sort keys to first_seen_at; message upserts
|
|
9454
|
+
// pull createdAt down (min) and push updatedAt up (max) as ts arrive.
|
|
9455
|
+
// first_seen_at is the ingest time and so >= every message ts, making
|
|
9456
|
+
// it a safe upper bound for the running min.
|
|
9457
|
+
createdAt: firstSeenAt,
|
|
9458
|
+
updatedAt: firstSeenAt,
|
|
8798
9459
|
project: project ?? null,
|
|
8799
9460
|
projectPath: projectPath ?? null
|
|
8800
9461
|
}).run();
|
|
@@ -8831,6 +9492,10 @@ function createArchiveRepository({
|
|
|
8831
9492
|
)
|
|
8832
9493
|
).get();
|
|
8833
9494
|
const ts = parseTs(message.ts);
|
|
9495
|
+
db.update(chats).set({
|
|
9496
|
+
createdAt: sql`min(coalesce(${chats.createdAt}, ${ts.getTime()}), ${ts.getTime()})`,
|
|
9497
|
+
updatedAt: sql`max(coalesce(${chats.updatedAt}, 0), ${ts.getTime()})`
|
|
9498
|
+
}).where(and(eq(chats.agent, agent), eq(chats.sourceId, sourceId))).run();
|
|
8834
9499
|
if (!existing2) {
|
|
8835
9500
|
db.insert(messages).values({
|
|
8836
9501
|
agent,
|
|
@@ -8951,34 +9616,92 @@ function createCheckpointRepository({
|
|
|
8951
9616
|
}
|
|
8952
9617
|
|
|
8953
9618
|
// src/metadata/repository.ts
|
|
8954
|
-
import
|
|
8955
|
-
import
|
|
9619
|
+
import fs4 from "fs";
|
|
9620
|
+
import path3 from "path";
|
|
8956
9621
|
|
|
8957
9622
|
// src/metadata/schema.ts
|
|
8958
9623
|
var schema_exports3 = {};
|
|
8959
9624
|
__export(schema_exports3, {
|
|
8960
|
-
|
|
9625
|
+
chatSortKeys: () => chatSortKeys,
|
|
9626
|
+
chatTags: () => chatTags,
|
|
9627
|
+
chatsMeta: () => chatsMeta,
|
|
9628
|
+
tags: () => tags
|
|
8961
9629
|
});
|
|
8962
|
-
var chatsMeta = sqliteTable(
|
|
9630
|
+
var chatsMeta = sqliteTable(
|
|
9631
|
+
"chats_meta",
|
|
9632
|
+
{
|
|
9633
|
+
id: text("id").primaryKey(),
|
|
9634
|
+
isDeleted: integer("is_deleted", { mode: "boolean" }).notNull().default(false),
|
|
9635
|
+
customTitle: text("custom_title"),
|
|
9636
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
9637
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
|
|
9638
|
+
// Set when a chat is moved to Trash; null while active. Drives the Trash
|
|
9639
|
+
// view's independent "Deleted time" sort axis.
|
|
9640
|
+
deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
|
|
9641
|
+
},
|
|
9642
|
+
(table) => [
|
|
9643
|
+
// Covering keyset index for the Trash view's deleted-time axis (#145,
|
|
9644
|
+
// ADR-0017): `(deleted_at, id)` so the page's ORDER BY + LIMIT is an index
|
|
9645
|
+
// range scan, not a full-trash sort. Partial on `deleted_at IS NOT NULL`,
|
|
9646
|
+
// which holds exactly for trashed rows (restore nulls it), so the index
|
|
9647
|
+
// carries only the Trash set and the page query's matching predicate lets
|
|
9648
|
+
// the planner use it.
|
|
9649
|
+
index("chats_meta_deleted_at_idx").on(table.deletedAt, table.id).where(sql`${table.deletedAt} is not null`)
|
|
9650
|
+
]
|
|
9651
|
+
);
|
|
9652
|
+
var chatSortKeys = sqliteTable(
|
|
9653
|
+
"chat_sort_keys",
|
|
9654
|
+
{
|
|
9655
|
+
id: text("id").primaryKey(),
|
|
9656
|
+
textKey: text("text_key"),
|
|
9657
|
+
sortKey: text("sort_key")
|
|
9658
|
+
},
|
|
9659
|
+
(table) => [
|
|
9660
|
+
// Covering keyset index for the Title axis (ADR-0017/0019): `(sort_key, id)`
|
|
9661
|
+
// so `ORDER BY sort_key, id` + LIMIT is an index range scan either
|
|
9662
|
+
// direction, exactly like the time axes — never a full-table sort.
|
|
9663
|
+
index("chat_sort_keys_sort_key_idx").on(table.sortKey, table.id)
|
|
9664
|
+
]
|
|
9665
|
+
);
|
|
9666
|
+
var tags = sqliteTable("tags", {
|
|
8963
9667
|
id: text("id").primaryKey(),
|
|
8964
|
-
|
|
8965
|
-
|
|
9668
|
+
name: text("name").notNull(),
|
|
9669
|
+
color: text("color").notNull(),
|
|
8966
9670
|
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
8967
|
-
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull()
|
|
8968
|
-
// Set when a chat is moved to Trash; null while active. Drives the Trash
|
|
8969
|
-
// view's independent "Deleted time" sort axis.
|
|
8970
|
-
deletedAt: integer("deleted_at", { mode: "timestamp_ms" })
|
|
9671
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull()
|
|
8971
9672
|
});
|
|
9673
|
+
var chatTags = sqliteTable(
|
|
9674
|
+
"chat_tags",
|
|
9675
|
+
{
|
|
9676
|
+
chatId: text("chat_id").notNull(),
|
|
9677
|
+
tagId: text("tag_id").notNull().references(() => tags.id, { onDelete: "cascade" })
|
|
9678
|
+
},
|
|
9679
|
+
(table) => [
|
|
9680
|
+
primaryKey({ columns: [table.chatId, table.tagId] }),
|
|
9681
|
+
index("chat_tags_tag_id_idx").on(table.tagId)
|
|
9682
|
+
]
|
|
9683
|
+
);
|
|
9684
|
+
|
|
9685
|
+
// src/metadata/title-sort-key.ts
|
|
9686
|
+
function encodeNumericRun(run2) {
|
|
9687
|
+
const core = run2.replace(/^0+(?=\d)/, "");
|
|
9688
|
+
const len = String(core.length);
|
|
9689
|
+
return String(len.length) + len + core;
|
|
9690
|
+
}
|
|
9691
|
+
function computeSortKey(input) {
|
|
9692
|
+
const folded = input.normalize("NFKD").toLowerCase().replace(/\p{M}+/gu, "");
|
|
9693
|
+
return folded.replace(/\d+/g, encodeNumericRun);
|
|
9694
|
+
}
|
|
8972
9695
|
|
|
8973
9696
|
// src/metadata/repository.ts
|
|
8974
9697
|
var DB_FILE = "metadata.db";
|
|
8975
9698
|
var LEGACY_DB_FILE = "data.db";
|
|
8976
9699
|
function migrateLegacyDbFile(dataDir2) {
|
|
8977
|
-
const legacy =
|
|
8978
|
-
const current =
|
|
8979
|
-
if (!
|
|
8980
|
-
if (
|
|
8981
|
-
|
|
9700
|
+
const legacy = path3.join(dataDir2, LEGACY_DB_FILE);
|
|
9701
|
+
const current = path3.join(dataDir2, DB_FILE);
|
|
9702
|
+
if (!fs4.existsSync(legacy)) return;
|
|
9703
|
+
if (fs4.existsSync(current)) return;
|
|
9704
|
+
fs4.renameSync(legacy, current);
|
|
8982
9705
|
}
|
|
8983
9706
|
var CLAUDE_CODE_AGENT = "claude-code";
|
|
8984
9707
|
function createMetadataRepository({
|
|
@@ -9050,6 +9773,27 @@ function createMetadataRepository({
|
|
|
9050
9773
|
target: chatsMeta.id,
|
|
9051
9774
|
set: { customTitle: title, updatedAt: now }
|
|
9052
9775
|
}).run();
|
|
9776
|
+
if (title !== null) {
|
|
9777
|
+
const sortKey = computeSortKey(title);
|
|
9778
|
+
db.insert(chatSortKeys).values({ id: internalId, sortKey }).onConflictDoUpdate({ target: chatSortKeys.id, set: { sortKey } }).run();
|
|
9779
|
+
} else {
|
|
9780
|
+
db.update(chatSortKeys).set({ sortKey: sql`${chatSortKeys.textKey}` }).where(eq(chatSortKeys.id, internalId)).run();
|
|
9781
|
+
}
|
|
9782
|
+
},
|
|
9783
|
+
getTitleSortKey(internalId) {
|
|
9784
|
+
const row = db.select({
|
|
9785
|
+
textKey: chatSortKeys.textKey,
|
|
9786
|
+
sortKey: chatSortKeys.sortKey
|
|
9787
|
+
}).from(chatSortKeys).where(eq(chatSortKeys.id, internalId)).get();
|
|
9788
|
+
return row ?? null;
|
|
9789
|
+
},
|
|
9790
|
+
setTitleTextKey(internalId, textKey) {
|
|
9791
|
+
const customTitle = db.select({ customTitle: chatsMeta.customTitle }).from(chatsMeta).where(eq(chatsMeta.id, internalId)).get()?.customTitle;
|
|
9792
|
+
const sortKey = customTitle && customTitle.trim() ? computeSortKey(customTitle) : textKey;
|
|
9793
|
+
db.insert(chatSortKeys).values({ id: internalId, textKey, sortKey }).onConflictDoUpdate({
|
|
9794
|
+
target: chatSortKeys.id,
|
|
9795
|
+
set: { textKey, sortKey }
|
|
9796
|
+
}).run();
|
|
9053
9797
|
}
|
|
9054
9798
|
};
|
|
9055
9799
|
}
|
|
@@ -9070,8 +9814,98 @@ function rekeyLegacyRows(sqlite, db, lookupInternalId, ensureChat) {
|
|
|
9070
9814
|
sqlite.pragma(`user_version = ${REKEY_USER_VERSION}`);
|
|
9071
9815
|
}
|
|
9072
9816
|
|
|
9817
|
+
// src/metadata/tags.ts
|
|
9818
|
+
import { randomUUID } from "crypto";
|
|
9819
|
+
var DB_FILE2 = "metadata.db";
|
|
9820
|
+
function createTagRepository({
|
|
9821
|
+
dataDir: dataDir2
|
|
9822
|
+
}) {
|
|
9823
|
+
const { db } = openStore({
|
|
9824
|
+
dataDir: dataDir2,
|
|
9825
|
+
dbFile: DB_FILE2,
|
|
9826
|
+
callerUrl: import.meta.url,
|
|
9827
|
+
migrationsSubdir: "drizzle",
|
|
9828
|
+
schema: schema_exports3
|
|
9829
|
+
});
|
|
9830
|
+
function toTag(row) {
|
|
9831
|
+
return { id: row.id, name: row.name, color: row.color };
|
|
9832
|
+
}
|
|
9833
|
+
function assertColor(color) {
|
|
9834
|
+
if (!isColorToken(color)) {
|
|
9835
|
+
throw new Error(`Invalid tag color token: ${String(color)}`);
|
|
9836
|
+
}
|
|
9837
|
+
}
|
|
9838
|
+
return {
|
|
9839
|
+
createTag(name, color) {
|
|
9840
|
+
assertColor(color);
|
|
9841
|
+
const now = /* @__PURE__ */ new Date();
|
|
9842
|
+
const row = {
|
|
9843
|
+
id: randomUUID(),
|
|
9844
|
+
name,
|
|
9845
|
+
color,
|
|
9846
|
+
createdAt: now,
|
|
9847
|
+
updatedAt: now
|
|
9848
|
+
};
|
|
9849
|
+
db.insert(tags).values(row).run();
|
|
9850
|
+
return toTag(row);
|
|
9851
|
+
},
|
|
9852
|
+
listTags() {
|
|
9853
|
+
return db.select({ id: tags.id, name: tags.name, color: tags.color }).from(tags).all().map(toTag);
|
|
9854
|
+
},
|
|
9855
|
+
getTag(id) {
|
|
9856
|
+
const row = db.select({ id: tags.id, name: tags.name, color: tags.color }).from(tags).where(eq(tags.id, id)).get();
|
|
9857
|
+
return row ? toTag(row) : null;
|
|
9858
|
+
},
|
|
9859
|
+
renameTag(id, name) {
|
|
9860
|
+
db.update(tags).set({ name, updatedAt: /* @__PURE__ */ new Date() }).where(eq(tags.id, id)).run();
|
|
9861
|
+
},
|
|
9862
|
+
recolorTag(id, color) {
|
|
9863
|
+
assertColor(color);
|
|
9864
|
+
db.update(tags).set({ color, updatedAt: /* @__PURE__ */ new Date() }).where(eq(tags.id, id)).run();
|
|
9865
|
+
},
|
|
9866
|
+
deleteTag(id) {
|
|
9867
|
+
return db.transaction((tx) => {
|
|
9868
|
+
const removedFromChats = tx.select({ chatId: chatTags.chatId }).from(chatTags).where(eq(chatTags.tagId, id)).all().length;
|
|
9869
|
+
tx.delete(chatTags).where(eq(chatTags.tagId, id)).run();
|
|
9870
|
+
tx.delete(tags).where(eq(tags.id, id)).run();
|
|
9871
|
+
return { removedFromChats };
|
|
9872
|
+
});
|
|
9873
|
+
},
|
|
9874
|
+
assignTag(chatId, tagId) {
|
|
9875
|
+
db.insert(chatTags).values({ chatId, tagId }).onConflictDoNothing().run();
|
|
9876
|
+
},
|
|
9877
|
+
removeTag(chatId, tagId) {
|
|
9878
|
+
db.delete(chatTags).where(and(eq(chatTags.chatId, chatId), eq(chatTags.tagId, tagId))).run();
|
|
9879
|
+
},
|
|
9880
|
+
listTagsForChat(chatId) {
|
|
9881
|
+
return db.select({ id: tags.id, name: tags.name, color: tags.color }).from(chatTags).innerJoin(tags, eq(chatTags.tagId, tags.id)).where(eq(chatTags.chatId, chatId)).all().map(toTag);
|
|
9882
|
+
},
|
|
9883
|
+
listTagsByChat(chatIds) {
|
|
9884
|
+
const base = db.select({
|
|
9885
|
+
chatId: chatTags.chatId,
|
|
9886
|
+
id: tags.id,
|
|
9887
|
+
name: tags.name,
|
|
9888
|
+
color: tags.color
|
|
9889
|
+
}).from(chatTags).innerJoin(tags, eq(chatTags.tagId, tags.id));
|
|
9890
|
+
const rows = chatIds === void 0 ? base.all() : chatIds.length === 0 ? [] : base.where(inArray(chatTags.chatId, chatIds)).all();
|
|
9891
|
+
const byChat = /* @__PURE__ */ new Map();
|
|
9892
|
+
for (const row of rows) {
|
|
9893
|
+
const list = byChat.get(row.chatId) ?? [];
|
|
9894
|
+
list.push(toTag(row));
|
|
9895
|
+
byChat.set(row.chatId, list);
|
|
9896
|
+
}
|
|
9897
|
+
return byChat;
|
|
9898
|
+
},
|
|
9899
|
+
listChatIdsWithAllTags(tagIds) {
|
|
9900
|
+
const distinct = [...new Set(tagIds)];
|
|
9901
|
+
if (distinct.length === 0) return [];
|
|
9902
|
+
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);
|
|
9903
|
+
}
|
|
9904
|
+
};
|
|
9905
|
+
}
|
|
9906
|
+
|
|
9073
9907
|
// src/ingestion/ingest.ts
|
|
9074
|
-
import
|
|
9908
|
+
import fs5 from "fs";
|
|
9075
9909
|
async function runIngestion(opts) {
|
|
9076
9910
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
9077
9911
|
const result = {
|
|
@@ -9131,7 +9965,7 @@ async function runIngestion(opts) {
|
|
|
9131
9965
|
}
|
|
9132
9966
|
function safeStat(p) {
|
|
9133
9967
|
try {
|
|
9134
|
-
return
|
|
9968
|
+
return fs5.statSync(p);
|
|
9135
9969
|
} catch {
|
|
9136
9970
|
return null;
|
|
9137
9971
|
}
|
|
@@ -9240,7 +10074,7 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9240
10074
|
this._directoryFilter = normalizeFilter(opts.directoryFilter);
|
|
9241
10075
|
const statMethod = opts.lstat ? lstat : stat;
|
|
9242
10076
|
if (wantBigintFsStats) {
|
|
9243
|
-
this._stat = (
|
|
10077
|
+
this._stat = (path8) => statMethod(path8, { bigint: true });
|
|
9244
10078
|
} else {
|
|
9245
10079
|
this._stat = statMethod;
|
|
9246
10080
|
}
|
|
@@ -9265,8 +10099,8 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9265
10099
|
const par = this.parent;
|
|
9266
10100
|
const fil = par && par.files;
|
|
9267
10101
|
if (fil && fil.length > 0) {
|
|
9268
|
-
const { path:
|
|
9269
|
-
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent,
|
|
10102
|
+
const { path: path8, depth } = par;
|
|
10103
|
+
const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
|
|
9270
10104
|
const awaited = await Promise.all(slice);
|
|
9271
10105
|
for (const entry of awaited) {
|
|
9272
10106
|
if (!entry)
|
|
@@ -9306,20 +10140,20 @@ var ReaddirpStream = class extends Readable3 {
|
|
|
9306
10140
|
this.reading = false;
|
|
9307
10141
|
}
|
|
9308
10142
|
}
|
|
9309
|
-
async _exploreDir(
|
|
10143
|
+
async _exploreDir(path8, depth) {
|
|
9310
10144
|
let files;
|
|
9311
10145
|
try {
|
|
9312
|
-
files = await readdir(
|
|
10146
|
+
files = await readdir(path8, this._rdOptions);
|
|
9313
10147
|
} catch (error) {
|
|
9314
10148
|
this._onError(error);
|
|
9315
10149
|
}
|
|
9316
|
-
return { files, depth, path:
|
|
10150
|
+
return { files, depth, path: path8 };
|
|
9317
10151
|
}
|
|
9318
|
-
async _formatEntry(dirent,
|
|
10152
|
+
async _formatEntry(dirent, path8) {
|
|
9319
10153
|
let entry;
|
|
9320
10154
|
const basename3 = this._isDirent ? dirent.name : dirent;
|
|
9321
10155
|
try {
|
|
9322
|
-
const fullPath = presolve(pjoin(
|
|
10156
|
+
const fullPath = presolve(pjoin(path8, basename3));
|
|
9323
10157
|
entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
|
|
9324
10158
|
entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
|
|
9325
10159
|
} catch (err) {
|
|
@@ -9719,16 +10553,16 @@ var delFromSet = (main, prop, item) => {
|
|
|
9719
10553
|
};
|
|
9720
10554
|
var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
|
|
9721
10555
|
var FsWatchInstances = /* @__PURE__ */ new Map();
|
|
9722
|
-
function createFsWatchInstance(
|
|
10556
|
+
function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
|
|
9723
10557
|
const handleEvent = (rawEvent, evPath) => {
|
|
9724
|
-
listener(
|
|
9725
|
-
emitRaw(rawEvent, evPath, { watchedPath:
|
|
9726
|
-
if (evPath &&
|
|
9727
|
-
fsWatchBroadcast(sp.resolve(
|
|
10558
|
+
listener(path8);
|
|
10559
|
+
emitRaw(rawEvent, evPath, { watchedPath: path8 });
|
|
10560
|
+
if (evPath && path8 !== evPath) {
|
|
10561
|
+
fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
|
|
9728
10562
|
}
|
|
9729
10563
|
};
|
|
9730
10564
|
try {
|
|
9731
|
-
return fs_watch(
|
|
10565
|
+
return fs_watch(path8, {
|
|
9732
10566
|
persistent: options.persistent
|
|
9733
10567
|
}, handleEvent);
|
|
9734
10568
|
} catch (error) {
|
|
@@ -9744,12 +10578,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
|
|
|
9744
10578
|
listener(val1, val2, val3);
|
|
9745
10579
|
});
|
|
9746
10580
|
};
|
|
9747
|
-
var setFsWatchListener = (
|
|
10581
|
+
var setFsWatchListener = (path8, fullPath, options, handlers) => {
|
|
9748
10582
|
const { listener, errHandler, rawEmitter } = handlers;
|
|
9749
10583
|
let cont = FsWatchInstances.get(fullPath);
|
|
9750
10584
|
let watcher2;
|
|
9751
10585
|
if (!options.persistent) {
|
|
9752
|
-
watcher2 = createFsWatchInstance(
|
|
10586
|
+
watcher2 = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
|
|
9753
10587
|
if (!watcher2)
|
|
9754
10588
|
return;
|
|
9755
10589
|
return watcher2.close.bind(watcher2);
|
|
@@ -9760,7 +10594,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
9760
10594
|
addAndConvert(cont, KEY_RAW, rawEmitter);
|
|
9761
10595
|
} else {
|
|
9762
10596
|
watcher2 = createFsWatchInstance(
|
|
9763
|
-
|
|
10597
|
+
path8,
|
|
9764
10598
|
options,
|
|
9765
10599
|
fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
|
|
9766
10600
|
errHandler,
|
|
@@ -9775,7 +10609,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
9775
10609
|
cont.watcherUnusable = true;
|
|
9776
10610
|
if (isWindows && error.code === "EPERM") {
|
|
9777
10611
|
try {
|
|
9778
|
-
const fd = await open(
|
|
10612
|
+
const fd = await open(path8, "r");
|
|
9779
10613
|
await fd.close();
|
|
9780
10614
|
broadcastErr(error);
|
|
9781
10615
|
} catch (err) {
|
|
@@ -9806,7 +10640,7 @@ var setFsWatchListener = (path5, fullPath, options, handlers) => {
|
|
|
9806
10640
|
};
|
|
9807
10641
|
};
|
|
9808
10642
|
var FsWatchFileInstances = /* @__PURE__ */ new Map();
|
|
9809
|
-
var setFsWatchFileListener = (
|
|
10643
|
+
var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
|
|
9810
10644
|
const { listener, rawEmitter } = handlers;
|
|
9811
10645
|
let cont = FsWatchFileInstances.get(fullPath);
|
|
9812
10646
|
const copts = cont && cont.options;
|
|
@@ -9828,7 +10662,7 @@ var setFsWatchFileListener = (path5, fullPath, options, handlers) => {
|
|
|
9828
10662
|
});
|
|
9829
10663
|
const currmtime = curr.mtimeMs;
|
|
9830
10664
|
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
|
|
9831
|
-
foreach(cont.listeners, (listener2) => listener2(
|
|
10665
|
+
foreach(cont.listeners, (listener2) => listener2(path8, curr));
|
|
9832
10666
|
}
|
|
9833
10667
|
})
|
|
9834
10668
|
};
|
|
@@ -9858,13 +10692,13 @@ var NodeFsHandler = class {
|
|
|
9858
10692
|
* @param listener on fs change
|
|
9859
10693
|
* @returns closer for the watcher instance
|
|
9860
10694
|
*/
|
|
9861
|
-
_watchWithNodeFs(
|
|
10695
|
+
_watchWithNodeFs(path8, listener) {
|
|
9862
10696
|
const opts = this.fsw.options;
|
|
9863
|
-
const directory = sp.dirname(
|
|
9864
|
-
const basename3 = sp.basename(
|
|
10697
|
+
const directory = sp.dirname(path8);
|
|
10698
|
+
const basename3 = sp.basename(path8);
|
|
9865
10699
|
const parent = this.fsw._getWatchedDir(directory);
|
|
9866
10700
|
parent.add(basename3);
|
|
9867
|
-
const absolutePath = sp.resolve(
|
|
10701
|
+
const absolutePath = sp.resolve(path8);
|
|
9868
10702
|
const options = {
|
|
9869
10703
|
persistent: opts.persistent
|
|
9870
10704
|
};
|
|
@@ -9874,12 +10708,12 @@ var NodeFsHandler = class {
|
|
|
9874
10708
|
if (opts.usePolling) {
|
|
9875
10709
|
const enableBin = opts.interval !== opts.binaryInterval;
|
|
9876
10710
|
options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
|
|
9877
|
-
closer = setFsWatchFileListener(
|
|
10711
|
+
closer = setFsWatchFileListener(path8, absolutePath, options, {
|
|
9878
10712
|
listener,
|
|
9879
10713
|
rawEmitter: this.fsw._emitRaw
|
|
9880
10714
|
});
|
|
9881
10715
|
} else {
|
|
9882
|
-
closer = setFsWatchListener(
|
|
10716
|
+
closer = setFsWatchListener(path8, absolutePath, options, {
|
|
9883
10717
|
listener,
|
|
9884
10718
|
errHandler: this._boundHandleError,
|
|
9885
10719
|
rawEmitter: this.fsw._emitRaw
|
|
@@ -9901,7 +10735,7 @@ var NodeFsHandler = class {
|
|
|
9901
10735
|
let prevStats = stats;
|
|
9902
10736
|
if (parent.has(basename3))
|
|
9903
10737
|
return;
|
|
9904
|
-
const listener = async (
|
|
10738
|
+
const listener = async (path8, newStats) => {
|
|
9905
10739
|
if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
|
|
9906
10740
|
return;
|
|
9907
10741
|
if (!newStats || newStats.mtimeMs === 0) {
|
|
@@ -9915,11 +10749,11 @@ var NodeFsHandler = class {
|
|
|
9915
10749
|
this.fsw._emit(EV.CHANGE, file, newStats2);
|
|
9916
10750
|
}
|
|
9917
10751
|
if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
|
|
9918
|
-
this.fsw._closeFile(
|
|
10752
|
+
this.fsw._closeFile(path8);
|
|
9919
10753
|
prevStats = newStats2;
|
|
9920
10754
|
const closer2 = this._watchWithNodeFs(file, listener);
|
|
9921
10755
|
if (closer2)
|
|
9922
|
-
this.fsw._addPathCloser(
|
|
10756
|
+
this.fsw._addPathCloser(path8, closer2);
|
|
9923
10757
|
} else {
|
|
9924
10758
|
prevStats = newStats2;
|
|
9925
10759
|
}
|
|
@@ -9951,7 +10785,7 @@ var NodeFsHandler = class {
|
|
|
9951
10785
|
* @param item basename of this item
|
|
9952
10786
|
* @returns true if no more processing is needed for this entry.
|
|
9953
10787
|
*/
|
|
9954
|
-
async _handleSymlink(entry, directory,
|
|
10788
|
+
async _handleSymlink(entry, directory, path8, item) {
|
|
9955
10789
|
if (this.fsw.closed) {
|
|
9956
10790
|
return;
|
|
9957
10791
|
}
|
|
@@ -9961,7 +10795,7 @@ var NodeFsHandler = class {
|
|
|
9961
10795
|
this.fsw._incrReadyCount();
|
|
9962
10796
|
let linkPath;
|
|
9963
10797
|
try {
|
|
9964
|
-
linkPath = await fsrealpath(
|
|
10798
|
+
linkPath = await fsrealpath(path8);
|
|
9965
10799
|
} catch (e) {
|
|
9966
10800
|
this.fsw._emitReady();
|
|
9967
10801
|
return true;
|
|
@@ -9971,12 +10805,12 @@ var NodeFsHandler = class {
|
|
|
9971
10805
|
if (dir.has(item)) {
|
|
9972
10806
|
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
|
|
9973
10807
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
9974
|
-
this.fsw._emit(EV.CHANGE,
|
|
10808
|
+
this.fsw._emit(EV.CHANGE, path8, entry.stats);
|
|
9975
10809
|
}
|
|
9976
10810
|
} else {
|
|
9977
10811
|
dir.add(item);
|
|
9978
10812
|
this.fsw._symlinkPaths.set(full, linkPath);
|
|
9979
|
-
this.fsw._emit(EV.ADD,
|
|
10813
|
+
this.fsw._emit(EV.ADD, path8, entry.stats);
|
|
9980
10814
|
}
|
|
9981
10815
|
this.fsw._emitReady();
|
|
9982
10816
|
return true;
|
|
@@ -9994,39 +10828,39 @@ var NodeFsHandler = class {
|
|
|
9994
10828
|
return;
|
|
9995
10829
|
const previous = this.fsw._getWatchedDir(wh.path);
|
|
9996
10830
|
const current = /* @__PURE__ */ new Set();
|
|
9997
|
-
let
|
|
10831
|
+
let stream2 = this.fsw._readdirp(directory, {
|
|
9998
10832
|
fileFilter: (entry) => wh.filterPath(entry),
|
|
9999
10833
|
directoryFilter: (entry) => wh.filterDir(entry)
|
|
10000
10834
|
});
|
|
10001
|
-
if (!
|
|
10835
|
+
if (!stream2)
|
|
10002
10836
|
return;
|
|
10003
|
-
|
|
10837
|
+
stream2.on(STR_DATA, async (entry) => {
|
|
10004
10838
|
if (this.fsw.closed) {
|
|
10005
|
-
|
|
10839
|
+
stream2 = void 0;
|
|
10006
10840
|
return;
|
|
10007
10841
|
}
|
|
10008
10842
|
const item = entry.path;
|
|
10009
|
-
let
|
|
10843
|
+
let path8 = sp.join(directory, item);
|
|
10010
10844
|
current.add(item);
|
|
10011
|
-
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory,
|
|
10845
|
+
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
|
|
10012
10846
|
return;
|
|
10013
10847
|
}
|
|
10014
10848
|
if (this.fsw.closed) {
|
|
10015
|
-
|
|
10849
|
+
stream2 = void 0;
|
|
10016
10850
|
return;
|
|
10017
10851
|
}
|
|
10018
10852
|
if (item === target || !target && !previous.has(item)) {
|
|
10019
10853
|
this.fsw._incrReadyCount();
|
|
10020
|
-
|
|
10021
|
-
this._addToNodeFs(
|
|
10854
|
+
path8 = sp.join(dir, sp.relative(dir, path8));
|
|
10855
|
+
this._addToNodeFs(path8, initialAdd, wh, depth + 1);
|
|
10022
10856
|
}
|
|
10023
10857
|
}).on(EV.ERROR, this._boundHandleError);
|
|
10024
10858
|
return new Promise((resolve3, reject) => {
|
|
10025
|
-
if (!
|
|
10859
|
+
if (!stream2)
|
|
10026
10860
|
return reject();
|
|
10027
|
-
|
|
10861
|
+
stream2.once(STR_END, () => {
|
|
10028
10862
|
if (this.fsw.closed) {
|
|
10029
|
-
|
|
10863
|
+
stream2 = void 0;
|
|
10030
10864
|
return;
|
|
10031
10865
|
}
|
|
10032
10866
|
const wasThrottled = throttler ? throttler.clear() : false;
|
|
@@ -10036,7 +10870,7 @@ var NodeFsHandler = class {
|
|
|
10036
10870
|
}).forEach((item) => {
|
|
10037
10871
|
this.fsw._remove(directory, item);
|
|
10038
10872
|
});
|
|
10039
|
-
|
|
10873
|
+
stream2 = void 0;
|
|
10040
10874
|
if (wasThrottled)
|
|
10041
10875
|
this._handleRead(directory, false, wh, target, dir, depth, throttler);
|
|
10042
10876
|
});
|
|
@@ -10087,13 +10921,13 @@ var NodeFsHandler = class {
|
|
|
10087
10921
|
* @param depth Child path actually targeted for watch
|
|
10088
10922
|
* @param target Child path actually targeted for watch
|
|
10089
10923
|
*/
|
|
10090
|
-
async _addToNodeFs(
|
|
10924
|
+
async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
|
|
10091
10925
|
const ready = this.fsw._emitReady;
|
|
10092
|
-
if (this.fsw._isIgnored(
|
|
10926
|
+
if (this.fsw._isIgnored(path8) || this.fsw.closed) {
|
|
10093
10927
|
ready();
|
|
10094
10928
|
return false;
|
|
10095
10929
|
}
|
|
10096
|
-
const wh = this.fsw._getWatchHelpers(
|
|
10930
|
+
const wh = this.fsw._getWatchHelpers(path8);
|
|
10097
10931
|
if (priorWh) {
|
|
10098
10932
|
wh.filterPath = (entry) => priorWh.filterPath(entry);
|
|
10099
10933
|
wh.filterDir = (entry) => priorWh.filterDir(entry);
|
|
@@ -10109,8 +10943,8 @@ var NodeFsHandler = class {
|
|
|
10109
10943
|
const follow = this.fsw.options.followSymlinks;
|
|
10110
10944
|
let closer;
|
|
10111
10945
|
if (stats.isDirectory()) {
|
|
10112
|
-
const absPath = sp.resolve(
|
|
10113
|
-
const targetPath = follow ? await fsrealpath(
|
|
10946
|
+
const absPath = sp.resolve(path8);
|
|
10947
|
+
const targetPath = follow ? await fsrealpath(path8) : path8;
|
|
10114
10948
|
if (this.fsw.closed)
|
|
10115
10949
|
return;
|
|
10116
10950
|
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
|
|
@@ -10120,29 +10954,29 @@ var NodeFsHandler = class {
|
|
|
10120
10954
|
this.fsw._symlinkPaths.set(absPath, targetPath);
|
|
10121
10955
|
}
|
|
10122
10956
|
} else if (stats.isSymbolicLink()) {
|
|
10123
|
-
const targetPath = follow ? await fsrealpath(
|
|
10957
|
+
const targetPath = follow ? await fsrealpath(path8) : path8;
|
|
10124
10958
|
if (this.fsw.closed)
|
|
10125
10959
|
return;
|
|
10126
10960
|
const parent = sp.dirname(wh.watchPath);
|
|
10127
10961
|
this.fsw._getWatchedDir(parent).add(wh.watchPath);
|
|
10128
10962
|
this.fsw._emit(EV.ADD, wh.watchPath, stats);
|
|
10129
|
-
closer = await this._handleDir(parent, stats, initialAdd, depth,
|
|
10963
|
+
closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
|
|
10130
10964
|
if (this.fsw.closed)
|
|
10131
10965
|
return;
|
|
10132
10966
|
if (targetPath !== void 0) {
|
|
10133
|
-
this.fsw._symlinkPaths.set(sp.resolve(
|
|
10967
|
+
this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
|
|
10134
10968
|
}
|
|
10135
10969
|
} else {
|
|
10136
10970
|
closer = this._handleFile(wh.watchPath, stats, initialAdd);
|
|
10137
10971
|
}
|
|
10138
10972
|
ready();
|
|
10139
10973
|
if (closer)
|
|
10140
|
-
this.fsw._addPathCloser(
|
|
10974
|
+
this.fsw._addPathCloser(path8, closer);
|
|
10141
10975
|
return false;
|
|
10142
10976
|
} catch (error) {
|
|
10143
10977
|
if (this.fsw._handleError(error)) {
|
|
10144
10978
|
ready();
|
|
10145
|
-
return
|
|
10979
|
+
return path8;
|
|
10146
10980
|
}
|
|
10147
10981
|
}
|
|
10148
10982
|
}
|
|
@@ -10185,24 +11019,24 @@ function createPattern(matcher) {
|
|
|
10185
11019
|
}
|
|
10186
11020
|
return () => false;
|
|
10187
11021
|
}
|
|
10188
|
-
function normalizePath(
|
|
10189
|
-
if (typeof
|
|
11022
|
+
function normalizePath(path8) {
|
|
11023
|
+
if (typeof path8 !== "string")
|
|
10190
11024
|
throw new Error("string expected");
|
|
10191
|
-
|
|
10192
|
-
|
|
11025
|
+
path8 = sp2.normalize(path8);
|
|
11026
|
+
path8 = path8.replace(/\\/g, "/");
|
|
10193
11027
|
let prepend = false;
|
|
10194
|
-
if (
|
|
11028
|
+
if (path8.startsWith("//"))
|
|
10195
11029
|
prepend = true;
|
|
10196
|
-
|
|
11030
|
+
path8 = path8.replace(DOUBLE_SLASH_RE, "/");
|
|
10197
11031
|
if (prepend)
|
|
10198
|
-
|
|
10199
|
-
return
|
|
11032
|
+
path8 = "/" + path8;
|
|
11033
|
+
return path8;
|
|
10200
11034
|
}
|
|
10201
11035
|
function matchPatterns(patterns, testString, stats) {
|
|
10202
|
-
const
|
|
10203
|
-
for (let
|
|
10204
|
-
const pattern = patterns[
|
|
10205
|
-
if (pattern(
|
|
11036
|
+
const path8 = normalizePath(testString);
|
|
11037
|
+
for (let index2 = 0; index2 < patterns.length; index2++) {
|
|
11038
|
+
const pattern = patterns[index2];
|
|
11039
|
+
if (pattern(path8, stats)) {
|
|
10206
11040
|
return true;
|
|
10207
11041
|
}
|
|
10208
11042
|
}
|
|
@@ -10240,19 +11074,19 @@ var toUnix = (string) => {
|
|
|
10240
11074
|
}
|
|
10241
11075
|
return str;
|
|
10242
11076
|
};
|
|
10243
|
-
var normalizePathToUnix = (
|
|
10244
|
-
var normalizeIgnored = (cwd = "") => (
|
|
10245
|
-
if (typeof
|
|
10246
|
-
return normalizePathToUnix(sp2.isAbsolute(
|
|
11077
|
+
var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
|
|
11078
|
+
var normalizeIgnored = (cwd = "") => (path8) => {
|
|
11079
|
+
if (typeof path8 === "string") {
|
|
11080
|
+
return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
|
|
10247
11081
|
} else {
|
|
10248
|
-
return
|
|
11082
|
+
return path8;
|
|
10249
11083
|
}
|
|
10250
11084
|
};
|
|
10251
|
-
var getAbsolutePath = (
|
|
10252
|
-
if (sp2.isAbsolute(
|
|
10253
|
-
return
|
|
11085
|
+
var getAbsolutePath = (path8, cwd) => {
|
|
11086
|
+
if (sp2.isAbsolute(path8)) {
|
|
11087
|
+
return path8;
|
|
10254
11088
|
}
|
|
10255
|
-
return sp2.join(cwd,
|
|
11089
|
+
return sp2.join(cwd, path8);
|
|
10256
11090
|
};
|
|
10257
11091
|
var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
|
|
10258
11092
|
var DirEntry = class {
|
|
@@ -10317,10 +11151,10 @@ var WatchHelper = class {
|
|
|
10317
11151
|
dirParts;
|
|
10318
11152
|
followSymlinks;
|
|
10319
11153
|
statMethod;
|
|
10320
|
-
constructor(
|
|
11154
|
+
constructor(path8, follow, fsw) {
|
|
10321
11155
|
this.fsw = fsw;
|
|
10322
|
-
const watchPath =
|
|
10323
|
-
this.path =
|
|
11156
|
+
const watchPath = path8;
|
|
11157
|
+
this.path = path8 = path8.replace(REPLACER_RE, "");
|
|
10324
11158
|
this.watchPath = watchPath;
|
|
10325
11159
|
this.fullWatchPath = sp2.resolve(watchPath);
|
|
10326
11160
|
this.dirParts = [];
|
|
@@ -10460,20 +11294,20 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10460
11294
|
this._closePromise = void 0;
|
|
10461
11295
|
let paths = unifyPaths(paths_);
|
|
10462
11296
|
if (cwd) {
|
|
10463
|
-
paths = paths.map((
|
|
10464
|
-
const absPath = getAbsolutePath(
|
|
11297
|
+
paths = paths.map((path8) => {
|
|
11298
|
+
const absPath = getAbsolutePath(path8, cwd);
|
|
10465
11299
|
return absPath;
|
|
10466
11300
|
});
|
|
10467
11301
|
}
|
|
10468
|
-
paths.forEach((
|
|
10469
|
-
this._removeIgnoredPath(
|
|
11302
|
+
paths.forEach((path8) => {
|
|
11303
|
+
this._removeIgnoredPath(path8);
|
|
10470
11304
|
});
|
|
10471
11305
|
this._userIgnored = void 0;
|
|
10472
11306
|
if (!this._readyCount)
|
|
10473
11307
|
this._readyCount = 0;
|
|
10474
11308
|
this._readyCount += paths.length;
|
|
10475
|
-
Promise.all(paths.map(async (
|
|
10476
|
-
const res = await this._nodeFsHandler._addToNodeFs(
|
|
11309
|
+
Promise.all(paths.map(async (path8) => {
|
|
11310
|
+
const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
|
|
10477
11311
|
if (res)
|
|
10478
11312
|
this._emitReady();
|
|
10479
11313
|
return res;
|
|
@@ -10495,17 +11329,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10495
11329
|
return this;
|
|
10496
11330
|
const paths = unifyPaths(paths_);
|
|
10497
11331
|
const { cwd } = this.options;
|
|
10498
|
-
paths.forEach((
|
|
10499
|
-
if (!sp2.isAbsolute(
|
|
11332
|
+
paths.forEach((path8) => {
|
|
11333
|
+
if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
|
|
10500
11334
|
if (cwd)
|
|
10501
|
-
|
|
10502
|
-
|
|
11335
|
+
path8 = sp2.join(cwd, path8);
|
|
11336
|
+
path8 = sp2.resolve(path8);
|
|
10503
11337
|
}
|
|
10504
|
-
this._closePath(
|
|
10505
|
-
this._addIgnoredPath(
|
|
10506
|
-
if (this._watched.has(
|
|
11338
|
+
this._closePath(path8);
|
|
11339
|
+
this._addIgnoredPath(path8);
|
|
11340
|
+
if (this._watched.has(path8)) {
|
|
10507
11341
|
this._addIgnoredPath({
|
|
10508
|
-
path:
|
|
11342
|
+
path: path8,
|
|
10509
11343
|
recursive: true
|
|
10510
11344
|
});
|
|
10511
11345
|
}
|
|
@@ -10528,7 +11362,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10528
11362
|
if (promise instanceof Promise)
|
|
10529
11363
|
closers.push(promise);
|
|
10530
11364
|
}));
|
|
10531
|
-
this._streams.forEach((
|
|
11365
|
+
this._streams.forEach((stream2) => stream2.destroy());
|
|
10532
11366
|
this._userIgnored = void 0;
|
|
10533
11367
|
this._readyCount = 0;
|
|
10534
11368
|
this._readyEmitted = false;
|
|
@@ -10549,8 +11383,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10549
11383
|
const watchList = {};
|
|
10550
11384
|
this._watched.forEach((entry, dir) => {
|
|
10551
11385
|
const key = this.options.cwd ? sp2.relative(this.options.cwd, dir) : dir;
|
|
10552
|
-
const
|
|
10553
|
-
watchList[
|
|
11386
|
+
const index2 = key || ONE_DOT;
|
|
11387
|
+
watchList[index2] = entry.getChildren().sort();
|
|
10554
11388
|
});
|
|
10555
11389
|
return watchList;
|
|
10556
11390
|
}
|
|
@@ -10569,38 +11403,38 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10569
11403
|
* @param stats arguments to be passed with event
|
|
10570
11404
|
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
|
|
10571
11405
|
*/
|
|
10572
|
-
async _emit(event,
|
|
11406
|
+
async _emit(event, path8, stats) {
|
|
10573
11407
|
if (this.closed)
|
|
10574
11408
|
return;
|
|
10575
11409
|
const opts = this.options;
|
|
10576
11410
|
if (isWindows)
|
|
10577
|
-
|
|
11411
|
+
path8 = sp2.normalize(path8);
|
|
10578
11412
|
if (opts.cwd)
|
|
10579
|
-
|
|
10580
|
-
const args = [
|
|
11413
|
+
path8 = sp2.relative(opts.cwd, path8);
|
|
11414
|
+
const args = [path8];
|
|
10581
11415
|
if (stats != null)
|
|
10582
11416
|
args.push(stats);
|
|
10583
11417
|
const awf = opts.awaitWriteFinish;
|
|
10584
11418
|
let pw;
|
|
10585
|
-
if (awf && (pw = this._pendingWrites.get(
|
|
11419
|
+
if (awf && (pw = this._pendingWrites.get(path8))) {
|
|
10586
11420
|
pw.lastChange = /* @__PURE__ */ new Date();
|
|
10587
11421
|
return this;
|
|
10588
11422
|
}
|
|
10589
11423
|
if (opts.atomic) {
|
|
10590
11424
|
if (event === EVENTS.UNLINK) {
|
|
10591
|
-
this._pendingUnlinks.set(
|
|
11425
|
+
this._pendingUnlinks.set(path8, [event, ...args]);
|
|
10592
11426
|
setTimeout(() => {
|
|
10593
|
-
this._pendingUnlinks.forEach((entry,
|
|
11427
|
+
this._pendingUnlinks.forEach((entry, path9) => {
|
|
10594
11428
|
this.emit(...entry);
|
|
10595
11429
|
this.emit(EVENTS.ALL, ...entry);
|
|
10596
|
-
this._pendingUnlinks.delete(
|
|
11430
|
+
this._pendingUnlinks.delete(path9);
|
|
10597
11431
|
});
|
|
10598
11432
|
}, typeof opts.atomic === "number" ? opts.atomic : 100);
|
|
10599
11433
|
return this;
|
|
10600
11434
|
}
|
|
10601
|
-
if (event === EVENTS.ADD && this._pendingUnlinks.has(
|
|
11435
|
+
if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
|
|
10602
11436
|
event = EVENTS.CHANGE;
|
|
10603
|
-
this._pendingUnlinks.delete(
|
|
11437
|
+
this._pendingUnlinks.delete(path8);
|
|
10604
11438
|
}
|
|
10605
11439
|
}
|
|
10606
11440
|
if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
|
|
@@ -10618,16 +11452,16 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10618
11452
|
this.emitWithAll(event, args);
|
|
10619
11453
|
}
|
|
10620
11454
|
};
|
|
10621
|
-
this._awaitWriteFinish(
|
|
11455
|
+
this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
|
|
10622
11456
|
return this;
|
|
10623
11457
|
}
|
|
10624
11458
|
if (event === EVENTS.CHANGE) {
|
|
10625
|
-
const isThrottled = !this._throttle(EVENTS.CHANGE,
|
|
11459
|
+
const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
|
|
10626
11460
|
if (isThrottled)
|
|
10627
11461
|
return this;
|
|
10628
11462
|
}
|
|
10629
11463
|
if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
|
|
10630
|
-
const fullPath = opts.cwd ? sp2.join(opts.cwd,
|
|
11464
|
+
const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
|
|
10631
11465
|
let stats2;
|
|
10632
11466
|
try {
|
|
10633
11467
|
stats2 = await stat3(fullPath);
|
|
@@ -10658,23 +11492,23 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10658
11492
|
* @param timeout duration of time to suppress duplicate actions
|
|
10659
11493
|
* @returns tracking object or false if action should be suppressed
|
|
10660
11494
|
*/
|
|
10661
|
-
_throttle(actionType,
|
|
11495
|
+
_throttle(actionType, path8, timeout) {
|
|
10662
11496
|
if (!this._throttled.has(actionType)) {
|
|
10663
11497
|
this._throttled.set(actionType, /* @__PURE__ */ new Map());
|
|
10664
11498
|
}
|
|
10665
11499
|
const action2 = this._throttled.get(actionType);
|
|
10666
11500
|
if (!action2)
|
|
10667
11501
|
throw new Error("invalid throttle");
|
|
10668
|
-
const actionPath = action2.get(
|
|
11502
|
+
const actionPath = action2.get(path8);
|
|
10669
11503
|
if (actionPath) {
|
|
10670
11504
|
actionPath.count++;
|
|
10671
11505
|
return false;
|
|
10672
11506
|
}
|
|
10673
11507
|
let timeoutObject;
|
|
10674
11508
|
const clear = () => {
|
|
10675
|
-
const item = action2.get(
|
|
11509
|
+
const item = action2.get(path8);
|
|
10676
11510
|
const count = item ? item.count : 0;
|
|
10677
|
-
action2.delete(
|
|
11511
|
+
action2.delete(path8);
|
|
10678
11512
|
clearTimeout(timeoutObject);
|
|
10679
11513
|
if (item)
|
|
10680
11514
|
clearTimeout(item.timeoutObject);
|
|
@@ -10682,7 +11516,7 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10682
11516
|
};
|
|
10683
11517
|
timeoutObject = setTimeout(clear, timeout);
|
|
10684
11518
|
const thr = { timeoutObject, clear, count: 0 };
|
|
10685
|
-
action2.set(
|
|
11519
|
+
action2.set(path8, thr);
|
|
10686
11520
|
return thr;
|
|
10687
11521
|
}
|
|
10688
11522
|
_incrReadyCount() {
|
|
@@ -10696,44 +11530,44 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10696
11530
|
* @param event
|
|
10697
11531
|
* @param awfEmit Callback to be called when ready for event to be emitted.
|
|
10698
11532
|
*/
|
|
10699
|
-
_awaitWriteFinish(
|
|
11533
|
+
_awaitWriteFinish(path8, threshold, event, awfEmit) {
|
|
10700
11534
|
const awf = this.options.awaitWriteFinish;
|
|
10701
11535
|
if (typeof awf !== "object")
|
|
10702
11536
|
return;
|
|
10703
11537
|
const pollInterval = awf.pollInterval;
|
|
10704
11538
|
let timeoutHandler;
|
|
10705
|
-
let fullPath =
|
|
10706
|
-
if (this.options.cwd && !sp2.isAbsolute(
|
|
10707
|
-
fullPath = sp2.join(this.options.cwd,
|
|
11539
|
+
let fullPath = path8;
|
|
11540
|
+
if (this.options.cwd && !sp2.isAbsolute(path8)) {
|
|
11541
|
+
fullPath = sp2.join(this.options.cwd, path8);
|
|
10708
11542
|
}
|
|
10709
11543
|
const now = /* @__PURE__ */ new Date();
|
|
10710
11544
|
const writes = this._pendingWrites;
|
|
10711
11545
|
function awaitWriteFinishFn(prevStat) {
|
|
10712
11546
|
statcb(fullPath, (err, curStat) => {
|
|
10713
|
-
if (err || !writes.has(
|
|
11547
|
+
if (err || !writes.has(path8)) {
|
|
10714
11548
|
if (err && err.code !== "ENOENT")
|
|
10715
11549
|
awfEmit(err);
|
|
10716
11550
|
return;
|
|
10717
11551
|
}
|
|
10718
11552
|
const now2 = Number(/* @__PURE__ */ new Date());
|
|
10719
11553
|
if (prevStat && curStat.size !== prevStat.size) {
|
|
10720
|
-
writes.get(
|
|
11554
|
+
writes.get(path8).lastChange = now2;
|
|
10721
11555
|
}
|
|
10722
|
-
const pw = writes.get(
|
|
11556
|
+
const pw = writes.get(path8);
|
|
10723
11557
|
const df = now2 - pw.lastChange;
|
|
10724
11558
|
if (df >= threshold) {
|
|
10725
|
-
writes.delete(
|
|
11559
|
+
writes.delete(path8);
|
|
10726
11560
|
awfEmit(void 0, curStat);
|
|
10727
11561
|
} else {
|
|
10728
11562
|
timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
|
|
10729
11563
|
}
|
|
10730
11564
|
});
|
|
10731
11565
|
}
|
|
10732
|
-
if (!writes.has(
|
|
10733
|
-
writes.set(
|
|
11566
|
+
if (!writes.has(path8)) {
|
|
11567
|
+
writes.set(path8, {
|
|
10734
11568
|
lastChange: now,
|
|
10735
11569
|
cancelWait: () => {
|
|
10736
|
-
writes.delete(
|
|
11570
|
+
writes.delete(path8);
|
|
10737
11571
|
clearTimeout(timeoutHandler);
|
|
10738
11572
|
return event;
|
|
10739
11573
|
}
|
|
@@ -10744,8 +11578,8 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10744
11578
|
/**
|
|
10745
11579
|
* Determines whether user has asked to ignore this path.
|
|
10746
11580
|
*/
|
|
10747
|
-
_isIgnored(
|
|
10748
|
-
if (this.options.atomic && DOT_RE.test(
|
|
11581
|
+
_isIgnored(path8, stats) {
|
|
11582
|
+
if (this.options.atomic && DOT_RE.test(path8))
|
|
10749
11583
|
return true;
|
|
10750
11584
|
if (!this._userIgnored) {
|
|
10751
11585
|
const { cwd } = this.options;
|
|
@@ -10755,17 +11589,17 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10755
11589
|
const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
|
|
10756
11590
|
this._userIgnored = anymatch(list, void 0);
|
|
10757
11591
|
}
|
|
10758
|
-
return this._userIgnored(
|
|
11592
|
+
return this._userIgnored(path8, stats);
|
|
10759
11593
|
}
|
|
10760
|
-
_isntIgnored(
|
|
10761
|
-
return !this._isIgnored(
|
|
11594
|
+
_isntIgnored(path8, stat4) {
|
|
11595
|
+
return !this._isIgnored(path8, stat4);
|
|
10762
11596
|
}
|
|
10763
11597
|
/**
|
|
10764
11598
|
* Provides a set of common helpers and properties relating to symlink handling.
|
|
10765
11599
|
* @param path file or directory pattern being watched
|
|
10766
11600
|
*/
|
|
10767
|
-
_getWatchHelpers(
|
|
10768
|
-
return new WatchHelper(
|
|
11601
|
+
_getWatchHelpers(path8) {
|
|
11602
|
+
return new WatchHelper(path8, this.options.followSymlinks, this);
|
|
10769
11603
|
}
|
|
10770
11604
|
// Directory helpers
|
|
10771
11605
|
// -----------------
|
|
@@ -10797,63 +11631,63 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10797
11631
|
* @param item base path of item/directory
|
|
10798
11632
|
*/
|
|
10799
11633
|
_remove(directory, item, isDirectory) {
|
|
10800
|
-
const
|
|
10801
|
-
const fullPath = sp2.resolve(
|
|
10802
|
-
isDirectory = isDirectory != null ? isDirectory : this._watched.has(
|
|
10803
|
-
if (!this._throttle("remove",
|
|
11634
|
+
const path8 = sp2.join(directory, item);
|
|
11635
|
+
const fullPath = sp2.resolve(path8);
|
|
11636
|
+
isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
|
|
11637
|
+
if (!this._throttle("remove", path8, 100))
|
|
10804
11638
|
return;
|
|
10805
11639
|
if (!isDirectory && this._watched.size === 1) {
|
|
10806
11640
|
this.add(directory, item, true);
|
|
10807
11641
|
}
|
|
10808
|
-
const wp = this._getWatchedDir(
|
|
11642
|
+
const wp = this._getWatchedDir(path8);
|
|
10809
11643
|
const nestedDirectoryChildren = wp.getChildren();
|
|
10810
|
-
nestedDirectoryChildren.forEach((nested) => this._remove(
|
|
11644
|
+
nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
|
|
10811
11645
|
const parent = this._getWatchedDir(directory);
|
|
10812
11646
|
const wasTracked = parent.has(item);
|
|
10813
11647
|
parent.remove(item);
|
|
10814
11648
|
if (this._symlinkPaths.has(fullPath)) {
|
|
10815
11649
|
this._symlinkPaths.delete(fullPath);
|
|
10816
11650
|
}
|
|
10817
|
-
let relPath =
|
|
11651
|
+
let relPath = path8;
|
|
10818
11652
|
if (this.options.cwd)
|
|
10819
|
-
relPath = sp2.relative(this.options.cwd,
|
|
11653
|
+
relPath = sp2.relative(this.options.cwd, path8);
|
|
10820
11654
|
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
|
|
10821
11655
|
const event = this._pendingWrites.get(relPath).cancelWait();
|
|
10822
11656
|
if (event === EVENTS.ADD)
|
|
10823
11657
|
return;
|
|
10824
11658
|
}
|
|
10825
|
-
this._watched.delete(
|
|
11659
|
+
this._watched.delete(path8);
|
|
10826
11660
|
this._watched.delete(fullPath);
|
|
10827
11661
|
const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
|
|
10828
|
-
if (wasTracked && !this._isIgnored(
|
|
10829
|
-
this._emit(eventName,
|
|
10830
|
-
this._closePath(
|
|
11662
|
+
if (wasTracked && !this._isIgnored(path8))
|
|
11663
|
+
this._emit(eventName, path8);
|
|
11664
|
+
this._closePath(path8);
|
|
10831
11665
|
}
|
|
10832
11666
|
/**
|
|
10833
11667
|
* Closes all watchers for a path
|
|
10834
11668
|
*/
|
|
10835
|
-
_closePath(
|
|
10836
|
-
this._closeFile(
|
|
10837
|
-
const dir = sp2.dirname(
|
|
10838
|
-
this._getWatchedDir(dir).remove(sp2.basename(
|
|
11669
|
+
_closePath(path8) {
|
|
11670
|
+
this._closeFile(path8);
|
|
11671
|
+
const dir = sp2.dirname(path8);
|
|
11672
|
+
this._getWatchedDir(dir).remove(sp2.basename(path8));
|
|
10839
11673
|
}
|
|
10840
11674
|
/**
|
|
10841
11675
|
* Closes only file-specific watchers
|
|
10842
11676
|
*/
|
|
10843
|
-
_closeFile(
|
|
10844
|
-
const closers = this._closers.get(
|
|
11677
|
+
_closeFile(path8) {
|
|
11678
|
+
const closers = this._closers.get(path8);
|
|
10845
11679
|
if (!closers)
|
|
10846
11680
|
return;
|
|
10847
11681
|
closers.forEach((closer) => closer());
|
|
10848
|
-
this._closers.delete(
|
|
11682
|
+
this._closers.delete(path8);
|
|
10849
11683
|
}
|
|
10850
|
-
_addPathCloser(
|
|
11684
|
+
_addPathCloser(path8, closer) {
|
|
10851
11685
|
if (!closer)
|
|
10852
11686
|
return;
|
|
10853
|
-
let list = this._closers.get(
|
|
11687
|
+
let list = this._closers.get(path8);
|
|
10854
11688
|
if (!list) {
|
|
10855
11689
|
list = [];
|
|
10856
|
-
this._closers.set(
|
|
11690
|
+
this._closers.set(path8, list);
|
|
10857
11691
|
}
|
|
10858
11692
|
list.push(closer);
|
|
10859
11693
|
}
|
|
@@ -10861,18 +11695,18 @@ var FSWatcher = class extends EventEmitter {
|
|
|
10861
11695
|
if (this.closed)
|
|
10862
11696
|
return;
|
|
10863
11697
|
const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
|
|
10864
|
-
let
|
|
10865
|
-
this._streams.add(
|
|
10866
|
-
|
|
10867
|
-
|
|
11698
|
+
let stream2 = readdirp(root, options);
|
|
11699
|
+
this._streams.add(stream2);
|
|
11700
|
+
stream2.once(STR_CLOSE, () => {
|
|
11701
|
+
stream2 = void 0;
|
|
10868
11702
|
});
|
|
10869
|
-
|
|
10870
|
-
if (
|
|
10871
|
-
this._streams.delete(
|
|
10872
|
-
|
|
11703
|
+
stream2.once(STR_END, () => {
|
|
11704
|
+
if (stream2) {
|
|
11705
|
+
this._streams.delete(stream2);
|
|
11706
|
+
stream2 = void 0;
|
|
10873
11707
|
}
|
|
10874
11708
|
});
|
|
10875
|
-
return
|
|
11709
|
+
return stream2;
|
|
10876
11710
|
}
|
|
10877
11711
|
};
|
|
10878
11712
|
function watch(paths, options = {}) {
|
|
@@ -10951,13 +11785,13 @@ function startWatcher(opts) {
|
|
|
10951
11785
|
}
|
|
10952
11786
|
return {
|
|
10953
11787
|
ready,
|
|
10954
|
-
notifyChange(
|
|
10955
|
-
if (watcher2) watcher2.emit("change",
|
|
10956
|
-
else scheduleIngest(
|
|
11788
|
+
notifyChange(path8) {
|
|
11789
|
+
if (watcher2) watcher2.emit("change", path8);
|
|
11790
|
+
else scheduleIngest(path8);
|
|
10957
11791
|
},
|
|
10958
|
-
notifyUnlink(
|
|
10959
|
-
if (watcher2) watcher2.emit("unlink",
|
|
10960
|
-
else recordUnlink(
|
|
11792
|
+
notifyUnlink(path8) {
|
|
11793
|
+
if (watcher2) watcher2.emit("unlink", path8);
|
|
11794
|
+
else recordUnlink(path8);
|
|
10961
11795
|
},
|
|
10962
11796
|
async close() {
|
|
10963
11797
|
closed = true;
|
|
@@ -10969,39 +11803,39 @@ function startWatcher(opts) {
|
|
|
10969
11803
|
}
|
|
10970
11804
|
|
|
10971
11805
|
// src/plugins/claude-code/plugin.ts
|
|
10972
|
-
import
|
|
10973
|
-
import
|
|
11806
|
+
import fs6 from "fs";
|
|
11807
|
+
import path4 from "path";
|
|
10974
11808
|
import readline from "readline";
|
|
10975
11809
|
var ClaudeCodePlugin = class {
|
|
10976
11810
|
id = "claude-code";
|
|
10977
11811
|
displayName = "Claude Code";
|
|
10978
11812
|
async *discover(env) {
|
|
10979
|
-
const projectsDir =
|
|
10980
|
-
if (!
|
|
10981
|
-
const projects =
|
|
11813
|
+
const projectsDir = path4.join(env.homeDir, ".claude", "projects");
|
|
11814
|
+
if (!fs6.existsSync(projectsDir)) return;
|
|
11815
|
+
const projects = fs6.readdirSync(projectsDir, { withFileTypes: true });
|
|
10982
11816
|
for (const project of projects) {
|
|
10983
11817
|
if (!project.isDirectory()) continue;
|
|
10984
|
-
const projectPath =
|
|
10985
|
-
const files =
|
|
11818
|
+
const projectPath = path4.join(projectsDir, project.name);
|
|
11819
|
+
const files = fs6.readdirSync(projectPath, { withFileTypes: true });
|
|
10986
11820
|
for (const file of files) {
|
|
10987
11821
|
if (!file.isFile() || !file.name.endsWith(".jsonl")) continue;
|
|
10988
|
-
const sourcePath =
|
|
11822
|
+
const sourcePath = path4.join(projectPath, file.name);
|
|
10989
11823
|
const sourceId = file.name.replace(/\.jsonl$/, "");
|
|
10990
11824
|
const cwd = await readCwdFromJsonl(sourcePath);
|
|
10991
11825
|
yield {
|
|
10992
11826
|
sourceId,
|
|
10993
11827
|
sourcePath,
|
|
10994
11828
|
watchPaths: [sourcePath],
|
|
10995
|
-
project: cwd ?
|
|
11829
|
+
project: cwd ? path4.basename(cwd) : void 0,
|
|
10996
11830
|
projectPath: cwd ?? void 0
|
|
10997
11831
|
};
|
|
10998
11832
|
}
|
|
10999
11833
|
}
|
|
11000
11834
|
}
|
|
11001
11835
|
async *extractRaw(ref) {
|
|
11002
|
-
if (!
|
|
11003
|
-
const
|
|
11004
|
-
const rl = readline.createInterface({ input:
|
|
11836
|
+
if (!fs6.existsSync(ref.sourcePath)) return;
|
|
11837
|
+
const stream2 = fs6.createReadStream(ref.sourcePath, { encoding: "utf-8" });
|
|
11838
|
+
const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
|
|
11005
11839
|
let lineNo = 0;
|
|
11006
11840
|
for await (const line of rl) {
|
|
11007
11841
|
lineNo += 1;
|
|
@@ -11049,8 +11883,8 @@ var ClaudeCodePlugin = class {
|
|
|
11049
11883
|
};
|
|
11050
11884
|
async function readCwdFromJsonl(sourcePath) {
|
|
11051
11885
|
try {
|
|
11052
|
-
const
|
|
11053
|
-
const rl = readline.createInterface({ input:
|
|
11886
|
+
const stream2 = fs6.createReadStream(sourcePath, { encoding: "utf-8" });
|
|
11887
|
+
const rl = readline.createInterface({ input: stream2, crlfDelay: Infinity });
|
|
11054
11888
|
try {
|
|
11055
11889
|
for await (const line of rl) {
|
|
11056
11890
|
if (!line) continue;
|
|
@@ -11066,7 +11900,7 @@ async function readCwdFromJsonl(sourcePath) {
|
|
|
11066
11900
|
}
|
|
11067
11901
|
} finally {
|
|
11068
11902
|
rl.close();
|
|
11069
|
-
|
|
11903
|
+
stream2.destroy();
|
|
11070
11904
|
}
|
|
11071
11905
|
} catch {
|
|
11072
11906
|
}
|
|
@@ -11155,10 +11989,131 @@ Environment:
|
|
|
11155
11989
|
PORT=<n> Set the HTTP port (overridden by --port)
|
|
11156
11990
|
`;
|
|
11157
11991
|
|
|
11992
|
+
// src/config/data-dir.ts
|
|
11993
|
+
import path5 from "path";
|
|
11994
|
+
function resolveDataDir(env, homeDir) {
|
|
11995
|
+
const override = env.CHAT_LOGBOOK_DATA_DIR?.trim();
|
|
11996
|
+
if (override) {
|
|
11997
|
+
return path5.resolve(override);
|
|
11998
|
+
}
|
|
11999
|
+
return path5.join(homeDir, ".chat-logbook");
|
|
12000
|
+
}
|
|
12001
|
+
|
|
12002
|
+
// src/list-counts.ts
|
|
12003
|
+
import fs7 from "fs";
|
|
12004
|
+
import path6 from "path";
|
|
12005
|
+
import Database3 from "better-sqlite3";
|
|
12006
|
+
var ARCHIVE_DB2 = "archive.db";
|
|
12007
|
+
var METADATA_DB2 = "metadata.db";
|
|
12008
|
+
function createChatCountsQuery({
|
|
12009
|
+
dataDir: dataDir2
|
|
12010
|
+
}) {
|
|
12011
|
+
const archive2 = new Database3(path6.join(dataDir2, ARCHIVE_DB2), {
|
|
12012
|
+
readonly: true
|
|
12013
|
+
});
|
|
12014
|
+
const metadataPath = path6.join(dataDir2, METADATA_DB2);
|
|
12015
|
+
const hasMetadata = fs7.existsSync(metadataPath);
|
|
12016
|
+
if (hasMetadata) {
|
|
12017
|
+
archive2.prepare("ATTACH DATABASE ? AS meta").run(metadataPath);
|
|
12018
|
+
}
|
|
12019
|
+
function queryCounts({
|
|
12020
|
+
includeTrashed = false
|
|
12021
|
+
}) {
|
|
12022
|
+
const viewClause = viewPredicate(includeTrashed, hasMetadata);
|
|
12023
|
+
const total = archive2.prepare(`SELECT count(*) AS n FROM chats c ${viewClause}`).get().n;
|
|
12024
|
+
const projects = archive2.prepare(
|
|
12025
|
+
`SELECT coalesce(c.project, '') AS project,
|
|
12026
|
+
count(*) AS count,
|
|
12027
|
+
max(c.updated_at) AS lastActiveAt
|
|
12028
|
+
FROM chats c ${viewClause}
|
|
12029
|
+
GROUP BY coalesce(c.project, '')`
|
|
12030
|
+
).all();
|
|
12031
|
+
const tags3 = hasMetadata ? archive2.prepare(
|
|
12032
|
+
`SELECT ct.tag_id AS tagId, count(*) AS count
|
|
12033
|
+
FROM meta.chat_tags ct
|
|
12034
|
+
JOIN chats c ON c.id = ct.chat_id
|
|
12035
|
+
${viewClause}
|
|
12036
|
+
GROUP BY ct.tag_id`
|
|
12037
|
+
).all() : [];
|
|
12038
|
+
const untagged = hasMetadata ? archive2.prepare(
|
|
12039
|
+
`SELECT count(*) AS n
|
|
12040
|
+
FROM chats c ${viewClause}
|
|
12041
|
+
AND c.id NOT IN (SELECT chat_id FROM meta.chat_tags)`
|
|
12042
|
+
).get().n : total;
|
|
12043
|
+
return { total, projects, tags: tags3, untagged };
|
|
12044
|
+
}
|
|
12045
|
+
function queryFilteredTotal({
|
|
12046
|
+
includeTrashed = false,
|
|
12047
|
+
projects,
|
|
12048
|
+
tags: tags3
|
|
12049
|
+
}) {
|
|
12050
|
+
const clauses = [];
|
|
12051
|
+
const params = [];
|
|
12052
|
+
if (hasMetadata) {
|
|
12053
|
+
clauses.push(
|
|
12054
|
+
includeTrashed ? "c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)" : "c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)"
|
|
12055
|
+
);
|
|
12056
|
+
} else if (includeTrashed) {
|
|
12057
|
+
return 0;
|
|
12058
|
+
}
|
|
12059
|
+
const filter = buildFilterClauses({ projects, tags: tags3, hasMetadata });
|
|
12060
|
+
clauses.push(...filter.clauses);
|
|
12061
|
+
params.push(...filter.params);
|
|
12062
|
+
const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
|
|
12063
|
+
return archive2.prepare(`SELECT count(*) AS n FROM chats c ${where}`).get(...params).n;
|
|
12064
|
+
}
|
|
12065
|
+
return {
|
|
12066
|
+
queryCounts,
|
|
12067
|
+
queryFilteredTotal,
|
|
12068
|
+
close() {
|
|
12069
|
+
archive2.close();
|
|
12070
|
+
}
|
|
12071
|
+
};
|
|
12072
|
+
}
|
|
12073
|
+
function viewPredicate(includeTrashed, hasMetadata) {
|
|
12074
|
+
if (!hasMetadata) {
|
|
12075
|
+
return includeTrashed ? "WHERE 0" : "";
|
|
12076
|
+
}
|
|
12077
|
+
return includeTrashed ? "WHERE c.id IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)" : "WHERE c.id NOT IN (SELECT id FROM meta.chats_meta WHERE is_deleted = 1)";
|
|
12078
|
+
}
|
|
12079
|
+
|
|
12080
|
+
// src/list-events.ts
|
|
12081
|
+
function createListEventHub() {
|
|
12082
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12083
|
+
return {
|
|
12084
|
+
publish(event) {
|
|
12085
|
+
for (const listener of listeners) listener(event);
|
|
12086
|
+
},
|
|
12087
|
+
subscribe(listener) {
|
|
12088
|
+
listeners.add(listener);
|
|
12089
|
+
return () => {
|
|
12090
|
+
listeners.delete(listener);
|
|
12091
|
+
};
|
|
12092
|
+
}
|
|
12093
|
+
};
|
|
12094
|
+
}
|
|
12095
|
+
|
|
12096
|
+
// src/metadata/reconcile-title-sort-keys.ts
|
|
12097
|
+
function reconcileTitleSortKeys({
|
|
12098
|
+
archive: archive2,
|
|
12099
|
+
metadata: metadata2
|
|
12100
|
+
}) {
|
|
12101
|
+
const key = (agent, sourceId) => [agent, sourceId].join("\0");
|
|
12102
|
+
const firstTextByKey = new Map(
|
|
12103
|
+
archive2.read.listFirstUserTexts().map((r) => [key(r.agent, r.sourceId), r.text])
|
|
12104
|
+
);
|
|
12105
|
+
for (const row of archive2.read.listChatRows()) {
|
|
12106
|
+
const baseTitle = deriveBaseTitle(
|
|
12107
|
+
firstTextByKey.get(key(row.agent, row.sourceId))
|
|
12108
|
+
);
|
|
12109
|
+
metadata2.setTitleTextKey(row.id, computeSortKey(baseTitle));
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
|
|
11158
12113
|
// src/index.ts
|
|
11159
|
-
var __dirname =
|
|
11160
|
-
var pkgPath =
|
|
11161
|
-
var pkg = JSON.parse(
|
|
12114
|
+
var __dirname = path7.dirname(fileURLToPath2(import.meta.url));
|
|
12115
|
+
var pkgPath = path7.join(__dirname, "../../package.json");
|
|
12116
|
+
var pkg = JSON.parse(fs8.readFileSync(pkgPath, "utf-8"));
|
|
11162
12117
|
var action = parseCliArgs(process.argv.slice(2), {
|
|
11163
12118
|
PORT: process.env.PORT
|
|
11164
12119
|
});
|
|
@@ -11175,26 +12130,58 @@ if (action.kind === "error") {
|
|
|
11175
12130
|
process.exit(1);
|
|
11176
12131
|
}
|
|
11177
12132
|
updateNotifier({ pkg }).notify({ defer: false, isGlobal: true });
|
|
11178
|
-
var dataDir =
|
|
11179
|
-
|
|
12133
|
+
var dataDir = resolveDataDir(
|
|
12134
|
+
{ CHAT_LOGBOOK_DATA_DIR: process.env.CHAT_LOGBOOK_DATA_DIR },
|
|
12135
|
+
os.homedir()
|
|
12136
|
+
);
|
|
12137
|
+
var webDistDir = path7.join(__dirname, "../../web/dist");
|
|
11180
12138
|
var port = action.port;
|
|
11181
12139
|
var archive = createArchiveRepository({ dataDir });
|
|
11182
12140
|
var checkpoint = createCheckpointRepository({ dataDir });
|
|
11183
12141
|
var metadata = createMetadataRepository({ dataDir });
|
|
11184
|
-
var
|
|
12142
|
+
var tags2 = createTagRepository({ dataDir });
|
|
12143
|
+
var pageQuery = createChatPageQuery({ dataDir });
|
|
12144
|
+
var countsQuery = createChatCountsQuery({ dataDir });
|
|
12145
|
+
var listEvents = createListEventHub();
|
|
12146
|
+
var app = createApp({
|
|
12147
|
+
archive,
|
|
12148
|
+
metadata,
|
|
12149
|
+
tags: tags2,
|
|
12150
|
+
pageQuery,
|
|
12151
|
+
countsQuery,
|
|
12152
|
+
listEvents,
|
|
12153
|
+
webDistDir
|
|
12154
|
+
});
|
|
11185
12155
|
var initialIngest = startIngestionInBackground({
|
|
11186
12156
|
plugins,
|
|
11187
12157
|
archive,
|
|
11188
12158
|
checkpoint,
|
|
11189
12159
|
env: { homeDir: os.homedir() }
|
|
11190
12160
|
});
|
|
12161
|
+
function reconcileTitles() {
|
|
12162
|
+
try {
|
|
12163
|
+
reconcileTitleSortKeys({ archive, metadata });
|
|
12164
|
+
} catch (err) {
|
|
12165
|
+
console.error("[title-sort-key] reconcile failed:", err);
|
|
12166
|
+
}
|
|
12167
|
+
}
|
|
11191
12168
|
var watcher = startWatcher({
|
|
11192
12169
|
plugins,
|
|
11193
12170
|
archive,
|
|
11194
12171
|
checkpoint,
|
|
11195
|
-
env: { homeDir: os.homedir() }
|
|
12172
|
+
env: { homeDir: os.homedir() },
|
|
12173
|
+
// Each watcher-driven ingest can add a chat or change a first user message;
|
|
12174
|
+
// refresh the Title keys so the Title axis stays current, then push a
|
|
12175
|
+
// `changed` event so connected clients reconcile their loaded window (#132).
|
|
12176
|
+
onIngest: () => {
|
|
12177
|
+
reconcileTitles();
|
|
12178
|
+
listEvents.publish({ type: "changed" });
|
|
12179
|
+
}
|
|
11196
12180
|
});
|
|
11197
|
-
void initialIngest.done.then(() =>
|
|
12181
|
+
void initialIngest.done.then(() => {
|
|
12182
|
+
reconcileTitles();
|
|
12183
|
+
return watcher.ready;
|
|
12184
|
+
}).catch(() => {
|
|
11198
12185
|
});
|
|
11199
12186
|
function shutdown() {
|
|
11200
12187
|
void watcher.close().finally(() => process.exit(0));
|