@getpaseo/server 0.3.0-beta.4 → 0.3.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.
Files changed (32) hide show
  1. package/dist/server/server/agent/mcp-shared.d.ts +2 -2
  2. package/dist/server/server/agent-history-search.d.ts +48 -0
  3. package/dist/server/server/agent-history-search.js +129 -0
  4. package/dist/server/server/session.d.ts +7 -0
  5. package/dist/server/server/session.js +56 -0
  6. package/dist/server/server/websocket-server.js +2 -0
  7. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-280bd28c6e8031e562e84a5381d2c1c1.js → desktop-attachment-bridge-78bca55e001d8c210fd05d625c99acf2.js} +1 -1
  8. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-bridge-280bd28c6e8031e562e84a5381d2c1c1.js.br → desktop-attachment-bridge-78bca55e001d8c210fd05d625c99acf2.js.br} +0 -0
  9. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-78bca55e001d8c210fd05d625c99acf2.js.gz +0 -0
  10. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-453c818cb8f94014304c6c7d1a2b2e40.js → desktop-attachment-store-fb478be7d05fee363960b3c2f31779a1.js} +1 -1
  11. package/dist/server/web-ui/_expo/static/js/web/{desktop-attachment-store-453c818cb8f94014304c6c7d1a2b2e40.js.br → desktop-attachment-store-fb478be7d05fee363960b3c2f31779a1.js.br} +0 -0
  12. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-fb478be7d05fee363960b3c2f31779a1.js.gz +0 -0
  13. package/dist/server/web-ui/_expo/static/js/web/{index-c0d625890939265efbcbfc306c551af8.js → index-fa155a82030f278e4fc83bdb473c422b.js} +90 -86
  14. package/dist/server/web-ui/_expo/static/js/web/index-fa155a82030f278e4fc83bdb473c422b.js.br +0 -0
  15. package/dist/server/web-ui/_expo/static/js/web/index-fa155a82030f278e4fc83bdb473c422b.js.gz +0 -0
  16. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-8edd5327b474f3356d5a859d90d9ae09.js → indexeddb-attachment-store-3800a3c9dea67ae868ad80bd1234f699.js} +1 -1
  17. package/dist/server/web-ui/_expo/static/js/web/{indexeddb-attachment-store-8edd5327b474f3356d5a859d90d9ae09.js.br → indexeddb-attachment-store-3800a3c9dea67ae868ad80bd1234f699.js.br} +0 -0
  18. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-3800a3c9dea67ae868ad80bd1234f699.js.gz +0 -0
  19. package/dist/server/web-ui/_expo/static/js/web/{native-file-attachment-store-ede45663e8a17dd5b4ac6c657cdd161b.js → native-file-attachment-store-06b3e5a3594801e3ad5d3853a5aec56b.js} +3 -3
  20. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-06b3e5a3594801e3ad5d3853a5aec56b.js.br +0 -0
  21. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-06b3e5a3594801e3ad5d3853a5aec56b.js.gz +0 -0
  22. package/dist/server/web-ui/index.html +1 -1
  23. package/dist/server/web-ui/index.html.br +0 -0
  24. package/dist/server/web-ui/index.html.gz +0 -0
  25. package/package.json +5 -5
  26. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-bridge-280bd28c6e8031e562e84a5381d2c1c1.js.gz +0 -0
  27. package/dist/server/web-ui/_expo/static/js/web/desktop-attachment-store-453c818cb8f94014304c6c7d1a2b2e40.js.gz +0 -0
  28. package/dist/server/web-ui/_expo/static/js/web/index-c0d625890939265efbcbfc306c551af8.js.br +0 -0
  29. package/dist/server/web-ui/_expo/static/js/web/index-c0d625890939265efbcbfc306c551af8.js.gz +0 -0
  30. package/dist/server/web-ui/_expo/static/js/web/indexeddb-attachment-store-8edd5327b474f3356d5a859d90d9ae09.js.gz +0 -0
  31. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-ede45663e8a17dd5b4ac6c657cdd161b.js.br +0 -0
  32. package/dist/server/web-ui/_expo/static/js/web/native-file-attachment-store-ede45663e8a17dd5b4ac6c657cdd161b.js.gz +0 -0
@@ -8,9 +8,9 @@ import type { AgentProvider } from "./agent-sdk-types.js";
8
8
  export declare const AgentProviderEnum: z.ZodString;
9
9
  export declare const AgentStatusEnum: z.ZodEnum<{
10
10
  error: "error";
11
+ initializing: "initializing";
11
12
  idle: "idle";
12
13
  running: "running";
13
- initializing: "initializing";
14
14
  closed: "closed";
15
15
  }>;
16
16
  export declare const ProviderModeSchema: z.ZodObject<{
@@ -83,7 +83,7 @@ export declare function serializeSnapshotWithMetadata(agentStorage: AgentStorage
83
83
  createdAt: string;
84
84
  updatedAt: string;
85
85
  lastUserMessageAt: string | null;
86
- status: "error" | "idle" | "running" | "initializing" | "closed";
86
+ status: "error" | "initializing" | "idle" | "running" | "closed";
87
87
  capabilities: import("@getpaseo/protocol/agent-types").AgentCapabilityFlags;
88
88
  currentModeId: string | null;
89
89
  availableModes: import("@getpaseo/protocol/agent-types").AgentMode[];
@@ -0,0 +1,48 @@
1
+ import type { AgentSearchMatch, AgentSnapshotPayload, ProjectPlacementPayload } from "@getpaseo/protocol/messages";
2
+ /**
3
+ * History search ranks what the daemon already knows about a session: the four
4
+ * names attached to it. Transcripts are deliberately out — they are unbounded,
5
+ * and a partial-transcript index would answer "not found" for sessions that do
6
+ * contain the phrase, which is worse than never claiming to search them.
7
+ *
8
+ * Field order is the tie-break order. Workspace and agent titles are what
9
+ * people actually recall; a project name matches every session in the repo, so
10
+ * it ranks last and only decides ties.
11
+ */
12
+ export interface AgentHistorySearchCandidate {
13
+ agent: AgentSnapshotPayload;
14
+ project: ProjectPlacementPayload;
15
+ }
16
+ /**
17
+ * Every token has to match something, so adding a word always narrows. Tokens
18
+ * may land on different fields — "stripe main" finds the Stripe workspace on
19
+ * the main branch.
20
+ */
21
+ export declare function scoreAgentHistoryCandidate(query: string, candidate: AgentHistorySearchCandidate): number | null;
22
+ /**
23
+ * Where the query landed in each field, for the rows that will actually be
24
+ * rendered. The ranker already knows this; sending it means the client never
25
+ * has to re-derive a second opinion that could disagree with the ranking it is
26
+ * explaining.
27
+ */
28
+ export declare function describeAgentHistoryMatches(query: string, candidate: AgentHistorySearchCandidate): AgentSearchMatch[];
29
+ export interface RankedAgentHistoryCandidate<T extends AgentHistorySearchCandidate> {
30
+ candidate: T;
31
+ searchScore: number;
32
+ }
33
+ /**
34
+ * Ranks the whole candidate set rather than a page of it. The daemon holds
35
+ * every persisted agent in memory already, so search is complete by
36
+ * construction and the client never has to warn that it only looked at what it
37
+ * had loaded.
38
+ *
39
+ * A searched response is one page: the best `limit` matches, and a flag saying
40
+ * more matched. There is no search cursor. Offsets into a ranking that is
41
+ * recomputed per request duplicate and skip rows as history mutates underneath
42
+ * them, and a keyset cursor would buy the ability to walk past result 200 of a
43
+ * relevance order — which is browsing, not searching. Narrowing the query is
44
+ * the answer, and the global top K is exactly recoverable from each host's own
45
+ * top K, which is what lets several hosts merge without a federated pager.
46
+ */
47
+ export declare function rankAgentHistoryCandidates<T extends AgentHistorySearchCandidate>(query: string, candidates: readonly T[], compareTies: (left: T, right: T) => number): RankedAgentHistoryCandidate<T>[];
48
+ //# sourceMappingURL=agent-history-search.d.ts.map
@@ -0,0 +1,129 @@
1
+ import { compareMatchScores, fuzzyPolicyForToken, matchRanges, scoreMatch, tokenizeQuery, } from "@getpaseo/protocol/search/text-match";
2
+ /** In tie-break order; the index into this list is a field's rank. */
3
+ const SEARCH_FIELDS = ["workspace", "title", "branch", "project"];
4
+ function searchableFields(candidate) {
5
+ const { agent, project } = candidate;
6
+ return [
7
+ project.workspaceName ?? "",
8
+ agent.title ?? "",
9
+ project.checkout.currentBranch ?? "",
10
+ project.projectName,
11
+ ];
12
+ }
13
+ function bestFieldMatch(token, fields) {
14
+ const fuzzy = fuzzyPolicyForToken(token);
15
+ let best = null;
16
+ for (let fieldRank = 0; fieldRank < fields.length; fieldRank += 1) {
17
+ const field = fields[fieldRank];
18
+ if (!field)
19
+ continue;
20
+ const score = scoreMatch(token, field, { fuzzy });
21
+ if (!score)
22
+ continue;
23
+ if (!best || compareMatchScores(score, best.score) < 0) {
24
+ best = { score, fieldRank, token };
25
+ }
26
+ }
27
+ return best;
28
+ }
29
+ /** Overlapping spans from different tokens would double-mark the same glyphs. */
30
+ function mergeRanges(ranges) {
31
+ const sorted = [...ranges].sort((left, right) => left.start - right.start);
32
+ const merged = [];
33
+ for (const range of sorted) {
34
+ const last = merged.at(-1);
35
+ if (last && range.start <= last.start + last.length) {
36
+ last.length = Math.max(last.length, range.start + range.length - last.start);
37
+ continue;
38
+ }
39
+ merged.push({ ...range });
40
+ }
41
+ return merged;
42
+ }
43
+ /**
44
+ * One number so the wire can carry relevance and several hosts' pages can be
45
+ * merged by it. Tier dominates, then which field matched, then how deep into
46
+ * that field the match sat.
47
+ */
48
+ function toSearchScore(matches) {
49
+ let total = 0;
50
+ for (const match of matches) {
51
+ total += match.score.tier * 1000 + match.fieldRank * 100 + Math.min(match.score.offset, 99);
52
+ }
53
+ return total;
54
+ }
55
+ /**
56
+ * Every token has to match something, so adding a word always narrows. Tokens
57
+ * may land on different fields — "stripe main" finds the Stripe workspace on
58
+ * the main branch.
59
+ */
60
+ export function scoreAgentHistoryCandidate(query, candidate) {
61
+ const matches = matchAgentHistoryCandidate(query, candidate);
62
+ return matches ? toSearchScore(matches) : null;
63
+ }
64
+ function matchAgentHistoryCandidate(query, candidate) {
65
+ const tokens = tokenizeQuery(query);
66
+ if (tokens.length === 0)
67
+ return null;
68
+ const fields = searchableFields(candidate);
69
+ const matches = [];
70
+ for (const token of tokens) {
71
+ const match = bestFieldMatch(token, fields);
72
+ if (!match)
73
+ return null;
74
+ matches.push(match);
75
+ }
76
+ return matches;
77
+ }
78
+ /**
79
+ * Where the query landed in each field, for the rows that will actually be
80
+ * rendered. The ranker already knows this; sending it means the client never
81
+ * has to re-derive a second opinion that could disagree with the ranking it is
82
+ * explaining.
83
+ */
84
+ export function describeAgentHistoryMatches(query, candidate) {
85
+ const matches = matchAgentHistoryCandidate(query, candidate);
86
+ if (!matches)
87
+ return [];
88
+ const fields = searchableFields(candidate);
89
+ const rangesByField = new Map();
90
+ for (const match of matches) {
91
+ const field = SEARCH_FIELDS[match.fieldRank];
92
+ const ranges = matchRanges(match.token, fields[match.fieldRank], match.score);
93
+ rangesByField.set(field, [...(rangesByField.get(field) ?? []), ...ranges]);
94
+ }
95
+ return [...rangesByField.entries()].map(([field, ranges]) => ({
96
+ field,
97
+ ranges: mergeRanges(ranges),
98
+ }));
99
+ }
100
+ /**
101
+ * Ranks the whole candidate set rather than a page of it. The daemon holds
102
+ * every persisted agent in memory already, so search is complete by
103
+ * construction and the client never has to warn that it only looked at what it
104
+ * had loaded.
105
+ *
106
+ * A searched response is one page: the best `limit` matches, and a flag saying
107
+ * more matched. There is no search cursor. Offsets into a ranking that is
108
+ * recomputed per request duplicate and skip rows as history mutates underneath
109
+ * them, and a keyset cursor would buy the ability to walk past result 200 of a
110
+ * relevance order — which is browsing, not searching. Narrowing the query is
111
+ * the answer, and the global top K is exactly recoverable from each host's own
112
+ * top K, which is what lets several hosts merge without a federated pager.
113
+ */
114
+ export function rankAgentHistoryCandidates(query, candidates, compareTies) {
115
+ const ranked = [];
116
+ for (const candidate of candidates) {
117
+ const searchScore = scoreAgentHistoryCandidate(query, candidate);
118
+ if (searchScore === null)
119
+ continue;
120
+ ranked.push({ candidate, searchScore });
121
+ }
122
+ ranked.sort((left, right) => {
123
+ if (left.searchScore !== right.searchScore)
124
+ return left.searchScore - right.searchScore;
125
+ return compareTies(left.candidate, right.candidate);
126
+ });
127
+ return ranked;
128
+ }
129
+ //# sourceMappingURL=agent-history-search.js.map
@@ -364,6 +364,13 @@ export declare class Session {
364
364
  private buildActiveProjectPlacementsByWorkspaceId;
365
365
  private collectFetchAgentsEntries;
366
366
  private listFetchAgentsEntries;
367
+ /**
368
+ * The searched history page. Ranking has to see every candidate before it can
369
+ * name the best one, so this path resolves placements for the whole set
370
+ * instead of stopping at the page limit — that is what makes a query answer
371
+ * from all persisted sessions rather than from the first page of them.
372
+ */
373
+ private listRankedAgentHistoryEntries;
367
374
  private readonly agentsPager;
368
375
  private decodeAgentCursor;
369
376
  private describeWorkspaceRecord;
@@ -8,6 +8,7 @@ import { serializeAgentStreamEvent, } from "./messages.js";
8
8
  import { TerminalSessionController } from "../terminal/terminal-session-controller.js";
9
9
  import { CursorError } from "./pagination/cursor.js";
10
10
  import { SortablePager } from "./pagination/sortable-pager.js";
11
+ import { describeAgentHistoryMatches, rankAgentHistoryCandidates } from "./agent-history-search.js";
11
12
  import { buildConfigOverrides, isStoredAgentProviderAvailable, toAgentPersistenceHandle, } from "./persistence-hooks.js";
12
13
  import { ensureAgentLoaded, ensureUnarchivedAgentLoaded } from "./agent/agent-loading.js";
13
14
  import { formatSystemNotificationPrompt, sendPromptToAgent, waitForAgentRunStartWithTimeout, unarchiveAgentState, } from "./agent/agent-prompt.js";
@@ -131,6 +132,15 @@ export function resolveWaitForFinishError(options) {
131
132
  const message = options.final?.lastError;
132
133
  return typeof message === "string" && message.trim().length > 0 ? message : "Agent failed";
133
134
  }
135
+ /**
136
+ * Only history carries a query. The active-agents directory filters on
137
+ * structure and never ranks, so it always reads as no query at all.
138
+ */
139
+ function agentDirectorySearchQuery(request) {
140
+ if (request.type !== "fetch_agent_history_request")
141
+ return "";
142
+ return request.search?.trim() ?? "";
143
+ }
134
144
  class SessionRequestError extends Error {
135
145
  constructor(code, message) {
136
146
  super(message);
@@ -3198,6 +3208,17 @@ export class Session {
3198
3208
  placementByWorkspaceId.set(workspaceId, placementPromise);
3199
3209
  return placementPromise;
3200
3210
  };
3211
+ const search = agentDirectorySearchQuery(request);
3212
+ if (search) {
3213
+ return this.listRankedAgentHistoryEntries({
3214
+ search,
3215
+ agents,
3216
+ sort,
3217
+ filter,
3218
+ getPlacement,
3219
+ page: request.page,
3220
+ });
3221
+ }
3201
3222
  let candidates = [...agents];
3202
3223
  candidates.sort((left, right) => this.agentsPager.compare(left, right, sort));
3203
3224
  const cursorToken = request.page?.cursor;
@@ -3226,6 +3247,41 @@ export class Session {
3226
3247
  },
3227
3248
  };
3228
3249
  }
3250
+ /**
3251
+ * The searched history page. Ranking has to see every candidate before it can
3252
+ * name the best one, so this path resolves placements for the whole set
3253
+ * instead of stopping at the page limit — that is what makes a query answer
3254
+ * from all persisted sessions rather than from the first page of them.
3255
+ */
3256
+ async listRankedAgentHistoryEntries(params) {
3257
+ const { search, agents, sort, filter, getPlacement, page } = params;
3258
+ if (page?.cursor) {
3259
+ // A ranked result set has no pages to walk, so a cursor here is caller
3260
+ // misuse. Returning the ranked head instead would hide it.
3261
+ throw new SessionRequestError("invalid_cursor", "A history search returns one ranked page; it cannot be paged with a cursor.");
3262
+ }
3263
+ const allEntries = await this.collectFetchAgentsEntries({
3264
+ candidates: agents,
3265
+ limit: Number.MAX_SAFE_INTEGER,
3266
+ getPlacement,
3267
+ filter,
3268
+ });
3269
+ const ranked = rankAgentHistoryCandidates(search, allEntries, (left, right) => this.agentsPager.compare(left.agent, right.agent, sort));
3270
+ const limit = page?.limit ?? 200;
3271
+ // Ranges are derived only for the rows that will be rendered; ranking
3272
+ // itself never needs them.
3273
+ const entries = ranked.slice(0, limit).map((result) => Object.assign({}, result.candidate, {
3274
+ searchScore: result.searchScore,
3275
+ searchMatches: describeAgentHistoryMatches(search, result.candidate),
3276
+ }));
3277
+ return {
3278
+ entries,
3279
+ // No next page exists, so `hasMore` is false and truncation is reported
3280
+ // on its own field. See the note on rankAgentHistoryCandidates.
3281
+ pageInfo: { nextCursor: null, prevCursor: null, hasMore: false },
3282
+ searchTruncated: ranked.length > limit,
3283
+ };
3284
+ }
3229
3285
  decodeAgentCursor(token, sort) {
3230
3286
  try {
3231
3287
  return this.agentsPager.decode(token, sort);
@@ -1062,6 +1062,8 @@ export class VoiceAssistantWebSocketServer {
1062
1062
  rewind: true,
1063
1063
  // COMPAT(agentTimelinePromptIndex): added in v0.2.X, drop the gate when floor >= v0.2.X.
1064
1064
  agentTimelinePromptIndex: true,
1065
+ // COMPAT(agentHistorySearch): added in v0.3.0, remove gate after 2027-02-07.
1066
+ agentHistorySearch: true,
1065
1067
  // COMPAT(checkoutRefresh): added in v0.1.86, remove gate after 2026-11-29.
1066
1068
  checkoutRefresh: true,
1067
1069
  // COMPAT(workspaceMultiplicity): added in v0.1.97, drop the gate when floor >= v0.1.97
@@ -1 +1 @@
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])},4672,[4123,4124]);
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])},4676,[4123,4124]);
@@ -1 +1 @@
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}'.`)}},4671,[4117]);
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}'.`)}},4675,[4117]);