@poco-ai/agentero-dsh-pdf 0.1.0 → 0.1.2

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.
@@ -6,6 +6,7 @@ declare module '@deepseek-ai/cordis' {
6
6
  registerOpener(kind: 'paper' | 'note', opener: (id: string) => void): () => void;
7
7
  request(url: string, init?: RequestInit): Promise<Response>;
8
8
  getVault(): Promise<{
9
+ path: string;
9
10
  scope: string;
10
11
  }>;
11
12
  };
@@ -51,6 +51,8 @@ export interface PdfInjected {
51
51
  loadPdf: (ref: string) => Promise<ArrayBuffer>;
52
52
  loadMarks: (ref: string) => Promise<PaintMark[]>;
53
53
  addMark: (body: MarkAdd) => Promise<string | undefined>;
54
+ /** Vault-relative ref for a file-resource address, or an empty string outside the vault. */
55
+ resolveAddress: (address: string) => string;
54
56
  /** Send a question and citation as a new conversation turn. */
55
57
  ask: (text: string) => Promise<void>;
56
58
  /** Write the PDF outline back to the file; resolves after the host saved it. */
@@ -0,0 +1,43 @@
1
+ /**
2
+ * File resources the right Sidebar routes to this plugin.
3
+ *
4
+ * PDF links and mentions arrive as `dsh-resource://file/...` addresses. A
5
+ * Session-scoped address keeps the path as the caller wrote it (workspace
6
+ * relative or absolute); the absolute form drops the leading slash. The host
7
+ * only reads files inside the vault, so an address has to be mapped back to a
8
+ * vault-relative ref before the panel can open it.
9
+ */
10
+ /** A file address split into its scope and decoded path. */
11
+ export type FileResource = {
12
+ readonly scope: 'session';
13
+ readonly sessionId: string;
14
+ /** Absolute or workspace-relative path; `/`-separated. */
15
+ readonly path: string;
16
+ } | {
17
+ readonly scope: 'absolute';
18
+ /** Absolute path: `/a/b` on POSIX, `C:/a/b` on Windows, `//server/share/a` for UNC. */
19
+ readonly path: string;
20
+ };
21
+ /**
22
+ * Parse one `dsh-resource://file/` address.
23
+ * @param address - candidate address.
24
+ * @returns its scope and decoded path, or undefined for another resource or a malformed address.
25
+ */
26
+ export declare function parseFileResource(address: string): FileResource | undefined;
27
+ /** Whether a path is absolute in either spelling the host accepts. */
28
+ export declare function isAbsolutePath(value: string): boolean;
29
+ /** Join a relative path onto an absolute base, using `/` separators. */
30
+ export declare function joinPath(base: string, relative: string): string;
31
+ /**
32
+ * Strip the vault root from an absolute path.
33
+ * @param vaultPath - configured vault directory.
34
+ * @param absolutePath - absolute path to test.
35
+ * @returns the vault-relative path, or undefined when it lies outside the vault.
36
+ */
37
+ export declare function vaultRelative(vaultPath: string, absolutePath: string): string | undefined;
38
+ /**
39
+ * Final path segment of a file-resource address.
40
+ * @param address - candidate address.
41
+ * @returns the decoded basename, or undefined when the address is not a file resource.
42
+ */
43
+ export declare function resourceName(address: string): string | undefined;
package/lib/client.js CHANGED
@@ -49177,6 +49177,57 @@ function PdfPage(props) {
49177
49177
  );
49178
49178
  }
49179
49179
 
49180
+ // src/client/resource-path.ts
49181
+ var FILE_PREFIX = "dsh-resource://file/";
49182
+ function isDriveSegment(segment) {
49183
+ return segment !== void 0 && /^[A-Za-z]:$/.test(segment);
49184
+ }
49185
+ function parseFileResource(address) {
49186
+ if (!address.startsWith(FILE_PREFIX)) return void 0;
49187
+ try {
49188
+ const end = address.search(/[?#]/);
49189
+ const [scope, ...rest] = address.slice(FILE_PREFIX.length, end === -1 ? void 0 : end).split("/");
49190
+ if (scope === "session") {
49191
+ const [sessionId, ...segments] = rest;
49192
+ if (sessionId === void 0 || sessionId === "" || segments.length === 0) return void 0;
49193
+ return { scope, sessionId: decodeURIComponent(sessionId), path: segments.map(decodeURIComponent).join("/") };
49194
+ }
49195
+ if (scope === "absolute") {
49196
+ const unc = rest[0] === "" && rest.length > 1;
49197
+ const segments = (unc ? rest.slice(1) : rest).map(decodeURIComponent);
49198
+ if (segments.length === 0 || segments[0] === "") return void 0;
49199
+ if (unc) return { scope, path: `//${segments.join("/")}` };
49200
+ return { scope, path: isDriveSegment(segments[0]) ? segments.join("/") : `/${segments.join("/")}` };
49201
+ }
49202
+ return void 0;
49203
+ } catch {
49204
+ return void 0;
49205
+ }
49206
+ }
49207
+ function isAbsolutePath(value) {
49208
+ return value.startsWith("/") || /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
49209
+ }
49210
+ function joinPath(base, relative) {
49211
+ return `${base.replace(/\\/g, "/").replace(/\/+$/, "")}/${relative.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "")}`;
49212
+ }
49213
+ function vaultRelative(vaultPath, absolutePath) {
49214
+ const root = vaultPath.replace(/\\/g, "/").replace(/\/+$/, "");
49215
+ if (root === "") return void 0;
49216
+ const normalized = absolutePath.replace(/\\/g, "/");
49217
+ const windows = /^[A-Za-z]:/.test(root);
49218
+ const prefix = `${root}/`;
49219
+ const candidate = windows ? normalized.toLowerCase() : normalized;
49220
+ const expected = windows ? prefix.toLowerCase() : prefix;
49221
+ if (!candidate.startsWith(expected)) return void 0;
49222
+ return normalized.slice(prefix.length);
49223
+ }
49224
+ function resourceName(address) {
49225
+ const file = parseFileResource(address);
49226
+ if (file === void 0) return void 0;
49227
+ const name2 = file.path.slice(file.path.lastIndexOf("/") + 1);
49228
+ return name2 === "" ? void 0 : name2;
49229
+ }
49230
+
49180
49231
  // src/client/selection-menu.tsx
49181
49232
  var import_react85 = require("react");
49182
49233
 
@@ -57977,13 +58028,15 @@ function paperRef(params) {
57977
58028
  return typeof ref === "string" ? ref : "";
57978
58029
  }
57979
58030
  function PdfTitle({ useTabInfo, t }) {
57980
- const ref = paperRef(useTabInfo().tab.navigation.params);
57981
- const name2 = ref.split("/").filter((part) => part !== "").pop();
58031
+ const navigation = useTabInfo().tab.navigation;
58032
+ const ref = paperRef(navigation.params);
58033
+ const name2 = ref !== "" ? ref.split("/").filter((part) => part !== "").pop() : resourceName(navigation.address);
57982
58034
  return /* @__PURE__ */ (0, import_jsx_runtime37.jsx)("span", { children: name2 === void 0 || name2 === "" ? t("title") : name2 });
57983
58035
  }
57984
58036
  function PdfPanel(props) {
57985
- const { t, sessionId, useTabInfo, loadPdf, loadMarks, addMark, ask, saveBookmarks, translate, loadLayout } = props;
57986
- const paperPath = paperRef(useTabInfo().tab.navigation.params);
58037
+ const { t, sessionId, useTabInfo, loadPdf, loadMarks, addMark, ask, saveBookmarks, translate, loadLayout, resolveAddress } = props;
58038
+ const navigation = useTabInfo().tab.navigation;
58039
+ const paperPath = paperRef(navigation.params) || resolveAddress(navigation.address);
57987
58040
  const [status, setStatus] = (0, import_react86.useState)("empty");
57988
58041
  const [message, setMessage] = (0, import_react86.useState)("");
57989
58042
  const [bytes, setBytes] = (0, import_react86.useState)(void 0);
@@ -58316,18 +58369,37 @@ var inject = ["slots", "locale", "agentero", "sidebarRight", "sidebarRightTabs",
58316
58369
  function apply(ctx) {
58317
58370
  const source = ctx.agentero;
58318
58371
  const request = source.request.bind(source);
58372
+ let vaultPath;
58373
+ void source.getVault().then((info) => {
58374
+ vaultPath = info.path;
58375
+ }).catch(() => {
58376
+ });
58377
+ const vaultRefOf = (address) => {
58378
+ const file = parseFileResource(address);
58379
+ if (file === void 0 || vaultPath === void 0) return void 0;
58380
+ if (file.scope === "absolute") return vaultRelative(vaultPath, file.path);
58381
+ if (file.path.startsWith("papers/")) return vaultRelative(vaultPath, joinPath(vaultPath, file.path));
58382
+ const cwd = ctx.sessions.list.getSnapshot().byId[file.sessionId]?.cwd;
58383
+ if (cwd === void 0 || cwd === "" || !isAbsolutePath(cwd)) return void 0;
58384
+ return vaultRelative(vaultPath, joinPath(cwd, file.path));
58385
+ };
58319
58386
  ctx.effect(() => ctx.locale.register(NS, { zh, en }));
58320
58387
  const t = ctx.locale.bind(NS);
58321
58388
  ctx.effect(() => ctx.sidebarRightTabs.register({
58322
58389
  id: TAB_ID,
58323
58390
  kind: TAB_KIND,
58324
58391
  keepMounted: true,
58325
- title: () => t("title")
58392
+ // Claim vault PDFs at the extension band so the builtin text preview stops
58393
+ // drawing them. Addresses outside the vault stay with the builtin viewer.
58394
+ patterns: ["*.pdf"],
58395
+ canOpen: (address) => vaultRefOf(address) !== void 0,
58396
+ title: (address) => resourceName(address) ?? t("title")
58326
58397
  }));
58327
58398
  ctx.effect(() => ctx.agentero.registerOpener("paper", (id3) => {
58328
58399
  ctx.sidebarRight.openTab(TAB_KIND, { params: { ref: id3 } });
58329
58400
  }));
58330
58401
  const face = {
58402
+ resolveAddress: (address) => vaultRefOf(address) ?? "",
58331
58403
  async loadPdf(ref) {
58332
58404
  const response = await request(`/agentero/api/pdf?ref=${encodeURIComponent(ref)}`, { cache: "no-store" });
58333
58405
  if (!response.ok) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poco-ai/agentero-dsh-pdf",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },