@hyperdrive.bot/paseo-server 0.2.8 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/server/file-explorer/service.js +9 -0
- package/dist/server/server/search/extract.d.ts +53 -0
- package/dist/server/server/search/extract.js +222 -0
- package/dist/server/server/search/index-store.d.ts +83 -0
- package/dist/server/server/search/index-store.js +241 -0
- package/dist/server/server/search/indexer.d.ts +69 -0
- package/dist/server/server/search/indexer.js +362 -0
- package/dist/server/server/search/resolve-cwd.d.ts +40 -0
- package/dist/server/server/search/resolve-cwd.js +109 -0
- package/dist/server/server/search/session-search-service.d.ts +59 -0
- package/dist/server/server/search/session-search-service.js +171 -0
- package/dist/server/server/session.js +10 -7
- package/dist/server/server/workspace/recall-search-feature.js +25 -16
- package/dist/server/server/workspace/sessions-search-route.d.ts +18 -46
- package/dist/server/server/workspace/sessions-search-route.js +27 -163
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-998e1be07f321f26e398086e261fbee3.js → desktop-attachment-bridge-dd96011e23063ea32a8d93c8e75c11b7.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-dd96011e23063ea32a8d93c8e75c11b7.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-dd96011e23063ea32a8d93c8e75c11b7.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-dd96011e23063ea32a8d93c8e75c11b7.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-dd96011e23063ea32a8d93c8e75c11b7.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-7e067a0e3c8d6639d94a1ce72c9ec0d2.js → desktop-attachment-store-1526526b7e379374c85e4d816bbb7742.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-1526526b7e379374c85e4d816bbb7742.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-1526526b7e379374c85e4d816bbb7742.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-1526526b7e379374c85e4d816bbb7742.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-1526526b7e379374c85e4d816bbb7742.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-e2ffd4d4419cad7f5153525e95f645b8.js → index-fad30dd59837afbdf33b7d9190c754e9.js} +246 -245
- package/dist/server/web-ui/_expo/static/js/web/index-fad30dd59837afbdf33b7d9190c754e9.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-fad30dd59837afbdf33b7d9190c754e9.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-fad30dd59837afbdf33b7d9190c754e9.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-e2ffd4d4419cad7f5153525e95f645b8.js.map.gz → index-fad30dd59837afbdf33b7d9190c754e9.js.map.gz} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-7fead1a370b17c2a180153dbfdd444f3.js → indexeddb-attachment-store-d12fb34cd3595fa34df2d42d9121d0d7.js} +3 -3
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-d12fb34cd3595fa34df2d42d9121d0d7.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-d12fb34cd3595fa34df2d42d9121d0d7.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-d12fb34cd3595fa34df2d42d9121d0d7.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-d12fb34cd3595fa34df2d42d9121d0d7.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-8a937b8ecc2b5546131709e2bdda97cb.js → native-file-attachment-store-c04241cce5bea0c64d4e31fea6e45288.js} +5 -5
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-c04241cce5bea0c64d4e31fea6e45288.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-c04241cce5bea0c64d4e31fea6e45288.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-c04241cce5bea0c64d4e31fea6e45288.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-c04241cce5bea0c64d4e31fea6e45288.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{profiler-8ca15c1659e234673816bc7bad0f88b6.js → profiler-24af32fdd64b094ad2dc78cf1625bd07.js} +11 -11
- package/dist/server/web-ui/_expo/static/js/web/profiler-24af32fdd64b094ad2dc78cf1625bd07.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-24af32fdd64b094ad2dc78cf1625bd07.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-24af32fdd64b094ad2dc78cf1625bd07.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{profiler-8ca15c1659e234673816bc7bad0f88b6.js.map.gz → profiler-24af32fdd64b094ad2dc78cf1625bd07.js.map.gz} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{startRecording-1a240d0212111d3eb294a9407e136e6a.js → startRecording-d113d3bf7d809773ff5c95198366c905.js} +60 -60
- package/dist/server/web-ui/_expo/static/js/web/startRecording-d113d3bf7d809773ff5c95198366c905.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-d113d3bf7d809773ff5c95198366c905.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-d113d3bf7d809773ff5c95198366c905.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{startRecording-1a240d0212111d3eb294a9407e136e6a.js.map.gz → startRecording-d113d3bf7d809773ff5c95198366c905.js.map.gz} +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +2 -2
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-998e1be07f321f26e398086e261fbee3.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-998e1be07f321f26e398086e261fbee3.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-998e1be07f321f26e398086e261fbee3.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-998e1be07f321f26e398086e261fbee3.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7e067a0e3c8d6639d94a1ce72c9ec0d2.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7e067a0e3c8d6639d94a1ce72c9ec0d2.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7e067a0e3c8d6639d94a1ce72c9ec0d2.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-7e067a0e3c8d6639d94a1ce72c9ec0d2.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-e2ffd4d4419cad7f5153525e95f645b8.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-e2ffd4d4419cad7f5153525e95f645b8.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-e2ffd4d4419cad7f5153525e95f645b8.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-7fead1a370b17c2a180153dbfdd444f3.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-7fead1a370b17c2a180153dbfdd444f3.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-7fead1a370b17c2a180153dbfdd444f3.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-7fead1a370b17c2a180153dbfdd444f3.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8a937b8ecc2b5546131709e2bdda97cb.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8a937b8ecc2b5546131709e2bdda97cb.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8a937b8ecc2b5546131709e2bdda97cb.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-8a937b8ecc2b5546131709e2bdda97cb.js.map.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-8ca15c1659e234673816bc7bad0f88b6.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-8ca15c1659e234673816bc7bad0f88b6.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/profiler-8ca15c1659e234673816bc7bad0f88b6.js.map.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-1a240d0212111d3eb294a9407e136e6a.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-1a240d0212111d3eb294a9407e136e6a.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/startRecording-1a240d0212111d3eb294a9407e136e6a.js.map.br +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// MARRA CUSTOMIZATION — the daemon-side session search service.
|
|
2
|
+
//
|
|
3
|
+
// Owns the FTS5 index and the background indexing loop, and exposes a single
|
|
4
|
+
// `search()` the three transports share (HTTP `GET /sessions/search`, the WS
|
|
5
|
+
// `search_sessions_request` handler, and `watch.sessionSearch`).
|
|
6
|
+
//
|
|
7
|
+
// WHY THIS EXISTS — the recall CLI it replaces failed structurally:
|
|
8
|
+
// • a subprocess spawn per keystroke,
|
|
9
|
+
// • a full Tantivy re-index inside every spawn,
|
|
10
|
+
// • a global writer lock serialising those spawns against each other.
|
|
11
|
+
// On a loaded host (39-agent fleet) every cmd+K keystroke hit the subprocess
|
|
12
|
+
// timeout, so search was disabled by default in 73ae98f3.
|
|
13
|
+
//
|
|
14
|
+
// Here indexing and querying are decoupled: writes happen only on a timer in
|
|
15
|
+
// the background, queries are pure reads against a warm SQLite file. Measured
|
|
16
|
+
// on the real 4.6 GB / 8,117-file corpus:
|
|
17
|
+
// • steady-state pass: 8,117 files stat'd, ~0 bytes read, 0.9 s
|
|
18
|
+
// • query p95: 0.5 ms – 32.7 ms (vs recall's 30 s timeout)
|
|
19
|
+
//
|
|
20
|
+
// The indexing loop is intentionally NOT a worker_thread. Measured cost of a
|
|
21
|
+
// steady-state pass is ~0.9 s of mostly-async stat() calls; the parse work
|
|
22
|
+
// that would justify a thread only happens on first build or after a large
|
|
23
|
+
// append. Spawning a thread would add ~30 MB RSS and a serialisation boundary
|
|
24
|
+
// around the SQLite handle to save single-digit milliseconds of event-loop
|
|
25
|
+
// time per minute. If a future corpus makes passes expensive, `runIndexPass`
|
|
26
|
+
// is already a pure function of (store, adapters) and can move to a worker
|
|
27
|
+
// with no change to callers.
|
|
28
|
+
import { SessionIndexStore } from "./index-store.js";
|
|
29
|
+
import { defaultAdapters, defaultIndexPath, runIndexPass } from "./indexer.js";
|
|
30
|
+
import { createCwdResolver } from "./resolve-cwd.js";
|
|
31
|
+
/**
|
|
32
|
+
* Under a test runner, never auto-start indexing.
|
|
33
|
+
*
|
|
34
|
+
* Every current call site injects a fake, so the real service is not
|
|
35
|
+
* constructed in the suite today. But a future test that calls
|
|
36
|
+
* `runSessionsSearch` without a dep would otherwise construct the singleton
|
|
37
|
+
* and immediately begin walking the DEVELOPER'S real ~/.claude/projects —
|
|
38
|
+
* gigabytes of disk I/O in the middle of a unit test, competing with the
|
|
39
|
+
* timing-sensitive pty tests that share the pool. Cheap guard, removes a whole
|
|
40
|
+
* class of "why is the suite flaky on my machine" before it can start.
|
|
41
|
+
*/
|
|
42
|
+
function isTestRunner() {
|
|
43
|
+
return process.env.VITEST === "true" || process.env.NODE_ENV === "test";
|
|
44
|
+
}
|
|
45
|
+
const DEFAULT_INTERVAL_MS = 60000;
|
|
46
|
+
export class SessionSearchService {
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.timer = null;
|
|
49
|
+
this.abort = new AbortController();
|
|
50
|
+
this.passInFlight = null;
|
|
51
|
+
/** False until the first pass finishes, so callers can be honest about warm-up. */
|
|
52
|
+
this.warm = false;
|
|
53
|
+
this.store = new SessionIndexStore(options.indexPath ?? defaultIndexPath());
|
|
54
|
+
this.adapters = options.adapters ?? defaultAdapters();
|
|
55
|
+
this.logger = options.logger;
|
|
56
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
57
|
+
if (!options.manual && !isTestRunner())
|
|
58
|
+
this.start();
|
|
59
|
+
}
|
|
60
|
+
start() {
|
|
61
|
+
// Kick a pass immediately so a fresh daemon warms without waiting a full
|
|
62
|
+
// interval, then settle into the timer.
|
|
63
|
+
void this.runPass();
|
|
64
|
+
this.timer = setInterval(() => void this.runPass(), this.intervalMs);
|
|
65
|
+
// Never hold the process open for an index refresh.
|
|
66
|
+
this.timer.unref?.();
|
|
67
|
+
}
|
|
68
|
+
/** One indexing pass. Overlapping calls join the in-flight pass. */
|
|
69
|
+
async runPass() {
|
|
70
|
+
if (this.passInFlight) {
|
|
71
|
+
await this.passInFlight;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const pending = runIndexPass(this.store, {
|
|
75
|
+
adapters: this.adapters,
|
|
76
|
+
...(this.logger ? { logger: this.logger } : {}),
|
|
77
|
+
signal: this.abort.signal,
|
|
78
|
+
})
|
|
79
|
+
.then(() => {
|
|
80
|
+
this.warm = true;
|
|
81
|
+
return undefined;
|
|
82
|
+
})
|
|
83
|
+
.catch((err) => {
|
|
84
|
+
// A failed pass must never take the daemon down or break querying —
|
|
85
|
+
// the previous index stays queryable and the next pass retries.
|
|
86
|
+
this.logger?.warn({ err }, "session-search: index pass failed");
|
|
87
|
+
})
|
|
88
|
+
.finally(() => {
|
|
89
|
+
this.passInFlight = null;
|
|
90
|
+
});
|
|
91
|
+
this.passInFlight = pending;
|
|
92
|
+
await pending;
|
|
93
|
+
}
|
|
94
|
+
/** True once at least one indexing pass has completed. */
|
|
95
|
+
isWarm() {
|
|
96
|
+
return this.warm;
|
|
97
|
+
}
|
|
98
|
+
stats() {
|
|
99
|
+
return this.store.stats();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Query the index and return hits in the transport's existing shape.
|
|
103
|
+
*
|
|
104
|
+
* Every hit's cwd is resolved to a directory that exists on THIS machine
|
|
105
|
+
* before it is returned, because the palette resumes a session into its cwd
|
|
106
|
+
* — a hit pointing at another machine's path is a result you cannot click.
|
|
107
|
+
*/
|
|
108
|
+
async search(params) {
|
|
109
|
+
const query = (params.query ?? "").trim();
|
|
110
|
+
const sources = params.sources
|
|
111
|
+
? params.sources
|
|
112
|
+
.split(",")
|
|
113
|
+
.map((s) => s.trim())
|
|
114
|
+
.filter((s) => s.length > 0)
|
|
115
|
+
: undefined;
|
|
116
|
+
const hits = this.store.search({
|
|
117
|
+
query,
|
|
118
|
+
...(params.limit !== undefined ? { limit: params.limit } : {}),
|
|
119
|
+
...(sources ? { sources } : {}),
|
|
120
|
+
});
|
|
121
|
+
const resolveCwd = createCwdResolver();
|
|
122
|
+
const results = [];
|
|
123
|
+
for (const hit of hits) {
|
|
124
|
+
const resolved = await resolveCwd(hit.cwd);
|
|
125
|
+
results.push({
|
|
126
|
+
sid: hit.sid,
|
|
127
|
+
source: hit.source,
|
|
128
|
+
cwd: resolved.cwd,
|
|
129
|
+
timestamp: hit.timestamp,
|
|
130
|
+
snippet: hit.snippet,
|
|
131
|
+
resumeCommand: buildResumeCommand(hit.source, hit.sid),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return { query, results };
|
|
135
|
+
}
|
|
136
|
+
close() {
|
|
137
|
+
this.abort.abort();
|
|
138
|
+
if (this.timer)
|
|
139
|
+
clearInterval(this.timer);
|
|
140
|
+
this.timer = null;
|
|
141
|
+
this.store.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function buildResumeCommand(source, sid) {
|
|
145
|
+
switch (source.toLowerCase()) {
|
|
146
|
+
case "claude":
|
|
147
|
+
return `claude --resume ${sid}`;
|
|
148
|
+
case "codex":
|
|
149
|
+
return `codex resume ${sid}`;
|
|
150
|
+
default:
|
|
151
|
+
return "";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Process-wide singleton
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
//
|
|
158
|
+
// One index per daemon. The three transports resolve the same instance, so
|
|
159
|
+
// there is exactly one writer and no lock contention — the failure that made
|
|
160
|
+
// the recall implementation return "LockBusy" and look broken.
|
|
161
|
+
let singleton = null;
|
|
162
|
+
export function getSessionSearchService(options = {}) {
|
|
163
|
+
singleton ?? (singleton = new SessionSearchService(options));
|
|
164
|
+
return singleton;
|
|
165
|
+
}
|
|
166
|
+
/** Test seam: drop the singleton so a fixture instance can take its place. */
|
|
167
|
+
export function resetSessionSearchServiceForTests() {
|
|
168
|
+
singleton?.close();
|
|
169
|
+
singleton = null;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=session-search-service.js.map
|
|
@@ -3397,15 +3397,18 @@ export class Session {
|
|
|
3397
3397
|
}
|
|
3398
3398
|
}
|
|
3399
3399
|
// COMPAT(sessionsListRpc): WS equivalent of HTTP `GET /sessions/search`.
|
|
3400
|
-
//
|
|
3401
|
-
//
|
|
3402
|
-
//
|
|
3403
|
-
//
|
|
3400
|
+
// Queries the SQLite FTS5 session index via the shared `runSessionsSearch`
|
|
3401
|
+
// core (same core as the HTTP route). This is the path the command palette
|
|
3402
|
+
// actually uses — the relay forbids HTTP search — so it is the one that has
|
|
3403
|
+
// to stay fast and never throw. Errors are returned in the payload; the
|
|
3404
|
+
// client turns a payload error into a thrown rejection so its useQuery falls
|
|
3405
|
+
// back to an empty list.
|
|
3404
3406
|
async handleSearchSessionsRequest(msg) {
|
|
3405
3407
|
const { requestId, query, limit, sources } = msg;
|
|
3406
|
-
//
|
|
3407
|
-
// stale client that saw the capability before this
|
|
3408
|
-
// empty result set so the palette
|
|
3408
|
+
// Kill switch (PASEO_DISABLE_SESSION_SEARCH=1) → never touch the index.
|
|
3409
|
+
// Also defends against a stale client that saw the capability before this
|
|
3410
|
+
// daemon disabled it. Returns an empty result set so the palette falls back
|
|
3411
|
+
// to its default listAllSessions browse.
|
|
3409
3412
|
if (!recallSearchEnabled()) {
|
|
3410
3413
|
this.emit({
|
|
3411
3414
|
type: "search_sessions_response",
|
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Single source of truth for the session-search capability.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// can never drift.
|
|
3
|
+
// The `server_info` handshake flag (`buildServerInfoStatusPayload()` in
|
|
4
|
+
// websocket-server.ts) AND the daemon-side entry points that serve search (the
|
|
5
|
+
// `search_sessions_request` and `watch.sessionSearch` handlers in session.ts)
|
|
6
|
+
// all read this, so the advertised capability and the code that actually
|
|
7
|
+
// answers a query can never drift.
|
|
9
8
|
//
|
|
10
|
-
// DISABLED BY DEFAULT
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// (`listAllSessions`) and the watch session-search serves recent sessions.
|
|
9
|
+
// HISTORY — this was DISABLED BY DEFAULT on 2026-07-28 (73ae98f3) because the
|
|
10
|
+
// engine was the `recall` CLI: it re-indexed its Tantivy store on every
|
|
11
|
+
// invocation and, under a loaded host, blew past its subprocess timeout,
|
|
12
|
+
// failing cmd+K and piling CPU onto an already-busy daemon.
|
|
15
13
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
14
|
+
// RE-ENABLED BY DEFAULT on 2026-07-29: the engine is now an in-process SQLite
|
|
15
|
+
// FTS5 index maintained incrementally in the background (`../search/`). The
|
|
16
|
+
// three causes of the original failure are structurally gone — no subprocess,
|
|
17
|
+
// no re-index on read, no writer lock on the query path. Measured on the real
|
|
18
|
+
// 8,117-file / 4.6 GB corpus: query p95 0.5–32.7 ms against a 150 ms debounce,
|
|
19
|
+
// and a steady-state index pass reads ~0 bytes in 0.9 s.
|
|
20
|
+
//
|
|
21
|
+
// Escape hatch, read at call time so it needs no rebuild or restart: export
|
|
22
|
+
// `PASEO_DISABLE_SESSION_SEARCH=1` to turn search off, and the palette falls
|
|
23
|
+
// back to the title/metadata browse (`listAllSessions`).
|
|
24
|
+
//
|
|
25
|
+
// `PASEO_ENABLE_RECALL_SEARCH` is intentionally no longer consulted: it is
|
|
26
|
+
// redundant now that search is on by default, and keeping it would mean a
|
|
27
|
+
// host that never set it stays broken for no reason.
|
|
19
28
|
export function recallSearchEnabled() {
|
|
20
|
-
return process.env.
|
|
29
|
+
return process.env.PASEO_DISABLE_SESSION_SEARCH !== "1";
|
|
21
30
|
}
|
|
22
31
|
//# sourceMappingURL=recall-search-feature.js.map
|
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
import type express from "express";
|
|
2
2
|
import type pino from "pino";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
cwd: string;
|
|
7
|
-
timestamp: string;
|
|
8
|
-
snippet: string;
|
|
9
|
-
resumeCommand: string;
|
|
10
|
-
}
|
|
11
|
-
export interface SearchResultsResponse {
|
|
3
|
+
import { type SearchResultEntry, type SearchResultsResponse } from "../search/session-search-service.js";
|
|
4
|
+
export type { SearchResultEntry, SearchResultsResponse };
|
|
5
|
+
export interface RunSessionsSearchParams {
|
|
12
6
|
query: string;
|
|
13
|
-
results
|
|
7
|
+
/** Max results. Clamped to [1, 100]; defaults to 20. */
|
|
8
|
+
limit?: number;
|
|
9
|
+
/** Comma-separated source list (e.g. "paseo,claude"). Defaults to all. */
|
|
10
|
+
sources?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RunSessionsSearchDeps {
|
|
13
|
+
/** Override the search implementation (tests inject a fixture). */
|
|
14
|
+
runSearch?: (params: RunSessionsSearchParams) => Promise<SearchResultsResponse>;
|
|
14
15
|
}
|
|
15
16
|
export interface SessionsSearchRouteOptions {
|
|
16
|
-
/** Override the
|
|
17
|
-
|
|
18
|
-
/** Override the spawn factory for tests. Returns stdout JSON synchronously. */
|
|
19
|
-
spawnRecall?: (args: string[]) => Promise<string>;
|
|
20
|
-
/** Subprocess timeout in ms. Defaults to 30s (recall re-indexes on every
|
|
21
|
-
* invocation; a large corpus can push cold reads past 8s). */
|
|
22
|
-
timeoutMs?: number;
|
|
23
|
-
/** Override the openable check (tests inject a fixture). Defaults to on-disk existence. */
|
|
24
|
-
isOpenable?: (entry: SearchResultEntry) => Promise<boolean>;
|
|
17
|
+
/** Override the search implementation (tests inject a fixture). */
|
|
18
|
+
runSearch?: (params: RunSessionsSearchParams) => Promise<SearchResultsResponse>;
|
|
25
19
|
}
|
|
26
20
|
export declare class RecallNotInstalledError extends Error {
|
|
27
21
|
constructor();
|
|
@@ -30,38 +24,16 @@ export declare class RecallTimedOutError extends Error {
|
|
|
30
24
|
readonly waitedMs: number;
|
|
31
25
|
constructor(waitedMs: number);
|
|
32
26
|
}
|
|
33
|
-
/** Thrown when the recall subprocess emits stdout that isn't valid JSON. */
|
|
34
27
|
export declare class RecallParseError extends Error {
|
|
35
28
|
constructor();
|
|
36
29
|
}
|
|
37
|
-
export interface RunSessionsSearchParams {
|
|
38
|
-
query: string;
|
|
39
|
-
/** Max results. Clamped to [1, 100]; defaults to 20. */
|
|
40
|
-
limit?: number;
|
|
41
|
-
/** Comma-separated source list (e.g. "paseo,claude"). Defaults to "paseo,claude". */
|
|
42
|
-
sources?: string;
|
|
43
|
-
}
|
|
44
|
-
export interface RunSessionsSearchDeps {
|
|
45
|
-
/** Override the spawn factory (tests inject a fixture). */
|
|
46
|
-
runRecall?: (args: string[]) => Promise<string>;
|
|
47
|
-
recallBinaryPath?: string;
|
|
48
|
-
timeoutMs?: number;
|
|
49
|
-
/**
|
|
50
|
-
* Override the openable check (tests inject a fixture). Defaults to verifying
|
|
51
|
-
* the hit's transcript/agent record still exists on disk — recall's index
|
|
52
|
-
* goes stale and returns deleted sessions.
|
|
53
|
-
*/
|
|
54
|
-
isOpenable?: (entry: SearchResultEntry) => Promise<boolean>;
|
|
55
|
-
}
|
|
56
30
|
/**
|
|
57
|
-
* Core
|
|
58
|
-
* by the HTTP `GET /sessions/search` route AND the `search_sessions_request`
|
|
59
|
-
* WS handler (so the command palette works over the relay).
|
|
31
|
+
* Core session FTS, shared by every transport.
|
|
60
32
|
*
|
|
61
|
-
* Sub-`MIN_QUERY_LEN` queries return `{ results: [] }`
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
33
|
+
* Sub-`MIN_QUERY_LEN` queries return `{ results: [] }` without touching the
|
|
34
|
+
* index. Hits reference a file that existed when it was indexed, and every
|
|
35
|
+
* returned `cwd` is resolved to a directory that exists on THIS machine — a
|
|
36
|
+
* hit you cannot resume is not a useful hit.
|
|
65
37
|
*/
|
|
66
38
|
export declare function runSessionsSearch(params: RunSessionsSearchParams, logger: pino.Logger, deps?: RunSessionsSearchDeps): Promise<SearchResultsResponse>;
|
|
67
39
|
export declare function registerSessionsSearchRoute(app: express.Express, logger: pino.Logger, options?: SessionsSearchRouteOptions): void;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isSessionOpenable } from "./session-openable.js";
|
|
3
|
-
import { recallSearchEnabled } from "./recall-search-feature.js";
|
|
4
|
-
const DEFAULT_TIMEOUT_MS = 30000;
|
|
5
|
-
const MAX_LIMIT = 100;
|
|
6
|
-
const DEFAULT_LIMIT = 20;
|
|
1
|
+
import { getSessionSearchService, } from "../search/session-search-service.js";
|
|
7
2
|
const MIN_QUERY_LEN = 2;
|
|
8
|
-
|
|
3
|
+
// COMPAT: these error types were thrown by the recall subprocess engine and
|
|
4
|
+
// are still imported by session.ts. The FTS engine cannot produce them —
|
|
5
|
+
// there is no subprocess to be missing, to time out, or to emit unparseable
|
|
6
|
+
// stdout — but they stay exported so existing catch-sites keep compiling.
|
|
7
|
+
// Remove once session.ts drops the imports.
|
|
9
8
|
export class RecallNotInstalledError extends Error {
|
|
10
9
|
constructor() {
|
|
11
10
|
super("recall binary not found on PATH");
|
|
@@ -19,192 +18,57 @@ export class RecallTimedOutError extends Error {
|
|
|
19
18
|
this.name = "RecallTimedOutError";
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
|
-
/** Thrown when the recall subprocess emits stdout that isn't valid JSON. */
|
|
23
21
|
export class RecallParseError extends Error {
|
|
24
22
|
constructor() {
|
|
25
23
|
super("recall_parse_failed");
|
|
26
24
|
this.name = "RecallParseError";
|
|
27
25
|
}
|
|
28
26
|
}
|
|
29
|
-
function spawnRecallDefault(binaryPath, args, timeoutMs) {
|
|
30
|
-
return new Promise((resolve, reject) => {
|
|
31
|
-
const child = spawn(binaryPath, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
32
|
-
let stdout = "";
|
|
33
|
-
let stderr = "";
|
|
34
|
-
const timer = setTimeout(() => {
|
|
35
|
-
child.kill("SIGTERM");
|
|
36
|
-
reject(new RecallTimedOutError(timeoutMs));
|
|
37
|
-
}, timeoutMs);
|
|
38
|
-
child.stdout.on("data", (chunk) => {
|
|
39
|
-
stdout += chunk.toString("utf-8");
|
|
40
|
-
});
|
|
41
|
-
child.stderr.on("data", (chunk) => {
|
|
42
|
-
stderr += chunk.toString("utf-8");
|
|
43
|
-
});
|
|
44
|
-
child.on("error", (err) => {
|
|
45
|
-
clearTimeout(timer);
|
|
46
|
-
if (err.code === "ENOENT") {
|
|
47
|
-
reject(new RecallNotInstalledError());
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
reject(err);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
child.on("close", (code) => {
|
|
54
|
-
clearTimeout(timer);
|
|
55
|
-
if (code === 0) {
|
|
56
|
-
resolve(stdout);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
reject(new Error(`recall exited with code ${code}: ${stderr.trim()}`));
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
// recall (re)builds its Tantivy index on each invocation and takes a writer
|
|
65
|
-
// lock. The command palette debounce fires a search per keystroke, so without
|
|
66
|
-
// serialization concurrent recall processes collide on the lock:
|
|
67
|
-
// "Failed to acquire Lockfile: LockBusy — Failed to acquire index lock"
|
|
68
|
-
// → the search returns nothing and the palette looks broken. Serialize all
|
|
69
|
-
// recall spawns within this daemon process to one-at-a-time, and retry once on
|
|
70
|
-
// a lock-busy error (covers a stale lock from a killed process too).
|
|
71
|
-
const serializeRecall = (() => {
|
|
72
|
-
let chain = Promise.resolve();
|
|
73
|
-
return (fn) => {
|
|
74
|
-
const run = chain.then(fn, fn);
|
|
75
|
-
chain = run.then(() => undefined, () => undefined);
|
|
76
|
-
return run;
|
|
77
|
-
};
|
|
78
|
-
})();
|
|
79
|
-
function isRecallLockBusy(error) {
|
|
80
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
-
return (message.includes("LockBusy") ||
|
|
82
|
-
message.includes("acquire index lock") ||
|
|
83
|
-
message.includes("Failed to acquire"));
|
|
84
|
-
}
|
|
85
|
-
function spawnRecallSerialized(binaryPath, args, timeoutMs) {
|
|
86
|
-
return serializeRecall(async () => {
|
|
87
|
-
try {
|
|
88
|
-
return await spawnRecallDefault(binaryPath, args, timeoutMs);
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
if (isRecallLockBusy(error)) {
|
|
92
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
93
|
-
return await spawnRecallDefault(binaryPath, args, timeoutMs);
|
|
94
|
-
}
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
function buildSnippet(messages) {
|
|
100
|
-
const first = messages[0];
|
|
101
|
-
if (!first)
|
|
102
|
-
return "";
|
|
103
|
-
const collapsed = first.content.replace(/\s+/g, " ").trim();
|
|
104
|
-
return collapsed.length > 220 ? `${collapsed.slice(0, 220)}…` : collapsed;
|
|
105
|
-
}
|
|
106
|
-
function toEntry(raw) {
|
|
107
|
-
return {
|
|
108
|
-
sid: raw.session_id,
|
|
109
|
-
source: raw.source,
|
|
110
|
-
cwd: raw.cwd,
|
|
111
|
-
timestamp: raw.timestamp,
|
|
112
|
-
snippet: buildSnippet(raw.relevant_messages ?? []),
|
|
113
|
-
resumeCommand: raw.resume_command,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
27
|
/**
|
|
117
|
-
* Core
|
|
118
|
-
* by the HTTP `GET /sessions/search` route AND the `search_sessions_request`
|
|
119
|
-
* WS handler (so the command palette works over the relay).
|
|
28
|
+
* Core session FTS, shared by every transport.
|
|
120
29
|
*
|
|
121
|
-
* Sub-`MIN_QUERY_LEN` queries return `{ results: [] }`
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
30
|
+
* Sub-`MIN_QUERY_LEN` queries return `{ results: [] }` without touching the
|
|
31
|
+
* index. Hits reference a file that existed when it was indexed, and every
|
|
32
|
+
* returned `cwd` is resolved to a directory that exists on THIS machine — a
|
|
33
|
+
* hit you cannot resume is not a useful hit.
|
|
125
34
|
*/
|
|
126
35
|
export async function runSessionsSearch(params, logger, deps = {}) {
|
|
127
36
|
const query = (params.query ?? "").trim();
|
|
128
37
|
if (query.length < MIN_QUERY_LEN) {
|
|
129
38
|
return { query, results: [] };
|
|
130
39
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const args = ["search", query, "--source", sources, "--limit", String(limit)];
|
|
140
|
-
const stdout = await runRecall(args);
|
|
141
|
-
let parsed;
|
|
142
|
-
try {
|
|
143
|
-
parsed = JSON.parse(stdout);
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
logger.error({ err, stdout: stdout.slice(0, 200) }, "failed to parse recall stdout");
|
|
147
|
-
throw new RecallParseError();
|
|
148
|
-
}
|
|
149
|
-
const mapped = Array.isArray(parsed.results)
|
|
150
|
-
? parsed.results.map(toEntry)
|
|
151
|
-
: [];
|
|
152
|
-
// Drop hits whose underlying source no longer exists on disk (recall's index
|
|
153
|
-
// goes stale). Without this the palette offers sessions that fail to open.
|
|
154
|
-
const isOpenable = deps.isOpenable ?? ((entry) => isSessionOpenable(entry));
|
|
155
|
-
const openableFlags = await Promise.all(mapped.map((entry) => isOpenable(entry)));
|
|
156
|
-
const results = mapped.filter((_, index) => openableFlags[index]);
|
|
157
|
-
const dropped = mapped.length - results.length;
|
|
158
|
-
if (dropped > 0) {
|
|
159
|
-
logger.debug({ dropped, kept: results.length }, "filtered unopenable recall hits");
|
|
160
|
-
}
|
|
161
|
-
return { query: parsed.query ?? query, results };
|
|
40
|
+
if (deps.runSearch)
|
|
41
|
+
return deps.runSearch({ ...params, query });
|
|
42
|
+
const service = getSessionSearchService({ logger });
|
|
43
|
+
return service.search({
|
|
44
|
+
query,
|
|
45
|
+
...(params.limit !== undefined ? { limit: params.limit } : {}),
|
|
46
|
+
...(params.sources !== undefined ? { sources: params.sources } : {}),
|
|
47
|
+
});
|
|
162
48
|
}
|
|
163
49
|
export function registerSessionsSearchRoute(app, logger, options = {}) {
|
|
164
50
|
const routeLogger = logger.child({ component: "sessions-search-route" });
|
|
165
|
-
const binaryPath = options.recallBinaryPath ?? "recall";
|
|
166
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
167
|
-
const runRecall = options.spawnRecall ?? ((args) => spawnRecallSerialized(binaryPath, args, timeoutMs));
|
|
168
51
|
app.get("/sessions/search", (req, res) => {
|
|
169
52
|
void (async () => {
|
|
170
53
|
const query = typeof req.query.q === "string" ? req.query.q.trim() : "";
|
|
171
|
-
// COMPAT(recallSearch): recall disabled → never spawn the CLI; return empty so
|
|
172
|
-
// a direct (desktop/web) palette falls back to its default listAllSessions browse.
|
|
173
|
-
if (!recallSearchEnabled()) {
|
|
174
|
-
res.status(200).json({ query, results: [] });
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
54
|
const rawLimit = typeof req.query.limit === "string" ? Number.parseInt(req.query.limit, 10) : undefined;
|
|
178
55
|
const sources = typeof req.query.source === "string" && req.query.source.length > 0
|
|
179
56
|
? req.query.source
|
|
180
57
|
: undefined;
|
|
181
58
|
try {
|
|
182
|
-
const out = await runSessionsSearch({
|
|
183
|
-
|
|
184
|
-
...(
|
|
185
|
-
|
|
59
|
+
const out = await runSessionsSearch({
|
|
60
|
+
query,
|
|
61
|
+
...(rawLimit !== undefined && Number.isFinite(rawLimit) ? { limit: rawLimit } : {}),
|
|
62
|
+
...(sources !== undefined ? { sources } : {}),
|
|
63
|
+
}, routeLogger, options.runSearch ? { runSearch: options.runSearch } : {});
|
|
186
64
|
res.status(200).json(out);
|
|
187
65
|
}
|
|
188
66
|
catch (err) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
res.status(503).json({ error: "recall_not_installed" });
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
if (err instanceof RecallTimedOutError) {
|
|
195
|
-
routeLogger.warn({ waitedMs: err.waitedMs, query }, "recall subprocess timed out");
|
|
196
|
-
res.status(504).json({ error: "recall_timeout" });
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (err instanceof RecallParseError) {
|
|
200
|
-
res.status(500).json({ error: "recall_parse_failed" });
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
routeLogger.error({ err, query }, "recall subprocess failed");
|
|
204
|
-
res.status(500).json({ error: "recall_failed" });
|
|
67
|
+
routeLogger.error({ err, query }, "session search failed");
|
|
68
|
+
res.status(500).json({ error: "search_failed" });
|
|
205
69
|
}
|
|
206
70
|
})();
|
|
207
71
|
});
|
|
208
|
-
routeLogger.info(
|
|
72
|
+
routeLogger.info("Registered GET /sessions/search (sqlite fts5)");
|
|
209
73
|
}
|
|
210
74
|
//# sourceMappingURL=sessions-search-route.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},
|
|
2
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.writeDesktopAttachmentBase64=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_base64",{attachmentId:n.attachmentId,base64:n.base64,extension:n.extension??null})},e.writeDesktopAttachmentBytes=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_bytes",{attachmentId:n.attachmentId,bytes:n.bytes,extension:n.extension??null})},e.copyDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("copy_attachment_file",{attachmentId:n.attachmentId,sourcePath:n.sourcePath,extension:n.extension??null})},e.deleteDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("delete_attachment_file",{path:n.path})},e.garbageCollectDesktopAttachmentFiles=async function(n){return await(0,t.invokeDesktopCommand)("garbage_collect_attachment_files",{referencedIds:[...n.referencedIds]})};var t=r(d[0])},
|
|
3
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.readDesktopFileBase64=s,e.createDesktopFileReader=function(){return{readFileBase64:s}},e.createBrowserObjectUrlMinter=function(){return{tryCreate({mimeType:t,base64:n}){if("undefined"==typeof URL||"function"!=typeof URL.createObjectURL)return null;const o=c(n),s=new ArrayBuffer(o.byteLength);new Uint8Array(s).set(o);const u=new Blob([s],{type:t});return URL.createObjectURL(u)},revoke(t){"undefined"!=typeof URL&&"function"==typeof URL.revokeObjectURL&&URL.revokeObjectURL(t)}}},e.createDesktopPreviewUrlResolver=function(t){const n=new Set;return{async resolve(o){const s=await t.reader.readFileBase64(o.storageKey),c=t.objectUrls.tryCreate({mimeType:o.mimeType,base64:s});return null===c?`data:${o.mimeType};base64,${s}`:(n.add(c),c)},async release({url:o}){n.has(o)&&(n.delete(o),t.objectUrls.revoke(o))}}};var t=r(d[0]),n=r(d[1]),o=r(d[2]);async function s(o){return await(0,n.invokeDesktopCommand)("read_file_base64",{path:(0,t.fileUriToPath)(o)})}function c(t){return new Uint8Array(o.Buffer.from(t,"base64"))}},
|
|
4
|
-
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-bridge-
|
|
5
|
-
//# debugId=
|
|
1
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentBridge=function(){const l=(0,s.createDesktopPreviewUrlResolver)({reader:(0,s.createDesktopFileReader)(),objectUrls:(0,s.createBrowserObjectUrlMinter)()});return{copyFile:t.copyDesktopAttachmentFile,writeBase64:t.writeDesktopAttachmentBase64,writeBytes:t.writeDesktopAttachmentBytes,deleteFile:t.deleteDesktopAttachmentFile,garbageCollect:t.garbageCollectDesktopAttachmentFiles,readFileBase64:s.readDesktopFileBase64,resolvePreviewUrl:t=>l.resolve(t),releasePreviewUrl:t=>l.release(t)}};var t=r(d[0]),s=r(d[1])},4776,[4781,4782]);
|
|
2
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.writeDesktopAttachmentBase64=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_base64",{attachmentId:n.attachmentId,base64:n.base64,extension:n.extension??null})},e.writeDesktopAttachmentBytes=async function(n){return await(0,t.invokeDesktopCommand)("write_attachment_bytes",{attachmentId:n.attachmentId,bytes:n.bytes,extension:n.extension??null})},e.copyDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("copy_attachment_file",{attachmentId:n.attachmentId,sourcePath:n.sourcePath,extension:n.extension??null})},e.deleteDesktopAttachmentFile=async function(n){return await(0,t.invokeDesktopCommand)("delete_attachment_file",{path:n.path})},e.garbageCollectDesktopAttachmentFiles=async function(n){return await(0,t.invokeDesktopCommand)("garbage_collect_attachment_files",{referencedIds:[...n.referencedIds]})};var t=r(d[0])},4781,[3406]);
|
|
3
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.readDesktopFileBase64=s,e.createDesktopFileReader=function(){return{readFileBase64:s}},e.createBrowserObjectUrlMinter=function(){return{tryCreate({mimeType:t,base64:n}){if("undefined"==typeof URL||"function"!=typeof URL.createObjectURL)return null;const o=c(n),s=new ArrayBuffer(o.byteLength);new Uint8Array(s).set(o);const u=new Blob([s],{type:t});return URL.createObjectURL(u)},revoke(t){"undefined"!=typeof URL&&"function"==typeof URL.revokeObjectURL&&URL.revokeObjectURL(t)}}},e.createDesktopPreviewUrlResolver=function(t){const n=new Set;return{async resolve(o){const s=await t.reader.readFileBase64(o.storageKey),c=t.objectUrls.tryCreate({mimeType:o.mimeType,base64:s});return null===c?`data:${o.mimeType};base64,${s}`:(n.add(c),c)},async release({url:o}){n.has(o)&&(n.delete(o),t.objectUrls.revoke(o))}}};var t=r(d[0]),n=r(d[1]),o=r(d[2]);async function s(o){return await(0,n.invokeDesktopCommand)("read_file_base64",{path:(0,t.fileUriToPath)(o)})}function c(t){return new Uint8Array(o.Buffer.from(t,"base64"))}},4782,[4478,3406,52]);
|
|
4
|
+
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-bridge-dd96011e23063ea32a8d93c8e75c11b7.js.map
|
|
5
|
+
//# debugId=9957cc72-3a9f-4def-9949-c3b1006a22a8
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},
|
|
2
|
-
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-store-
|
|
3
|
-
//# debugId=
|
|
1
|
+
__d(function(g,r,i,a,m,e,d){"use strict";Object.defineProperty(e,'__esModule',{value:!0}),e.createDesktopAttachmentStore=function(n){return{storageType:"desktop-file",async save(s){const o=s.id??(0,t.generateAttachmentId)(),l=s.fileName??null;if("file_uri"===s.source.kind)return await c(n,{id:o,uri:s.source.uri,mimeType:s.mimeType,fileName:l});if("data_url"===s.source.kind){const c=(0,t.parseDataUrl)(s.source.dataUrl),p=(0,t.normalizeMimeType)(s.mimeType??c.mimeType);return await y(n,{id:o,base64:c.base64,mimeType:p,fileName:l})}if("bytes"===s.source.kind){const c=(0,t.normalizeMimeType)(s.mimeType);return await p(n,{id:o,bytes:s.source.bytes,mimeType:c,fileName:l})}const f=(0,t.normalizeMimeType)(s.mimeType??s.source.blob.type),u=await(0,t.blobToBase64)(s.source.blob);return await y(n,{id:o,base64:u,mimeType:f,fileName:l})},encodeBase64:async({attachment:t})=>(f(t),await n.readFileBase64(t.storageKey)),resolvePreviewUrl:async({attachment:t})=>(f(t),await n.resolvePreviewUrl(t)),async releasePreviewUrl({attachment:t,url:s}){f(t),await n.releasePreviewUrl({url:s})},async delete({attachment:t}){f(t),await n.deleteFile({path:t.storageKey})},async garbageCollect({referencedIds:t}){await n.garbageCollect({referencedIds:Array.from(t)})}}};var t=r(d[0]);const n={"image/png":".png","image/jpeg":".jpg","image/jpg":".jpg","image/gif":".gif","image/webp":".webp","image/avif":".avif","image/heic":".heic","image/heif":".heif","image/tiff":".tiff","image/bmp":".bmp","image/svg+xml":".svg"};function s(t){const n=t.replace(/\\/g,"/").split("/"),s=n[n.length-1];return s&&s.length>0?s:null}function o(s){const o=(0,t.getFileExtensionFromName)(s.fileName);if(o)return o;const l=(0,t.getFileExtensionFromName)(s.sourcePath);return l||(n[s.mimeType]??".img")}function l(t){return{id:t.id,mimeType:t.mimeType,storageType:"desktop-file",storageKey:t.storagePath,fileName:t.fileName??null,byteSize:t.byteSize,createdAt:Date.now()}}async function c(n,c){const y=(0,t.fileUriToPath)(c.uri),p=c.fileName??s(y),f=(0,t.normalizeMimeType)(c.mimeType),u=o({fileName:p,sourcePath:y,mimeType:f}),T=await n.copyFile({attachmentId:c.id,sourcePath:y,extension:u});return l({id:c.id,mimeType:f,storagePath:T.path,byteSize:T.byteSize,fileName:p})}async function y(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBase64({attachmentId:n.id,base64:n.base64,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}async function p(t,n){const s=o({fileName:n.fileName,mimeType:n.mimeType}),c=await t.writeBytes({attachmentId:n.id,bytes:n.bytes,extension:s});return l({id:n.id,mimeType:n.mimeType,storagePath:c.path,byteSize:c.byteSize,fileName:n.fileName})}function f(t){if("desktop-file"!==t.storageType)throw new Error(`Unsupported desktop attachment storage type '${t.storageType}'.`)}},4775,[4478]);
|
|
2
|
+
//# sourceMappingURL=/_expo/static/js/web/desktop-attachment-store-1526526b7e379374c85e4d816bbb7742.js.map
|
|
3
|
+
//# debugId=7c4e6bb3-27e3-443f-a7f2-85de05d44e99
|
|
Binary file
|
|
Binary file
|
|
Binary file
|