mulmoclaude 0.3.0 → 0.4.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/bin/mulmoclaude.js +7 -24
- package/client/assets/html2canvas-Cx501zZr-Cv5snK9D.js +5 -0
- package/client/assets/index-CubzmCVK.css +2 -0
- package/client/assets/{index-eHWB79u5.js → index-DtcyExH9.js} +80 -61
- package/client/assets/{index.es-D4YyL_Dg-BfRHLTZV.js → index.es-D4YyL_Dg-DnizuhIY.js} +5 -5
- package/client/index.html +2 -4
- package/package.json +13 -13
- package/server/agent/attachmentConverter.ts +2 -2
- package/server/agent/index.ts +9 -3
- package/server/agent/mcp-tools/index.ts +6 -6
- package/server/agent/mcp-tools/x.ts +2 -1
- package/server/agent/prompt.ts +187 -26
- package/server/agent/resumeFailover.ts +5 -5
- package/server/agent/sandboxMounts.ts +3 -3
- package/server/api/auth/bearerAuth.ts +3 -3
- package/server/api/auth/token.ts +2 -2
- package/server/api/routes/agent.ts +21 -3
- package/server/api/routes/config.ts +1 -1
- package/server/api/routes/files.ts +13 -12
- package/server/api/routes/html.ts +2 -2
- package/server/api/routes/image.ts +7 -7
- package/server/api/routes/mulmo-script.ts +33 -31
- package/server/api/routes/pdf.ts +2 -2
- package/server/api/routes/plugins.ts +16 -6
- package/server/api/routes/roles.ts +2 -2
- package/server/api/routes/scheduler.ts +8 -6
- package/server/api/routes/schedulerTasks.ts +5 -3
- package/server/api/routes/sessions.ts +2 -2
- package/server/api/routes/sessionsCursor.ts +4 -4
- package/server/api/routes/skills.ts +5 -5
- package/server/api/routes/sources.ts +3 -3
- package/server/api/routes/todosHandlers.ts +1 -1
- package/server/api/routes/todosItemsHandlers.ts +14 -14
- package/server/api/routes/wiki.ts +22 -8
- package/server/api/sandboxStatus.ts +1 -1
- package/server/events/notifications.ts +6 -6
- package/server/events/pub-sub/index.ts +3 -3
- package/server/events/relay-client.ts +17 -16
- package/server/index.ts +40 -46
- package/server/system/config.ts +5 -5
- package/server/system/credentials.ts +7 -5
- package/server/system/env.ts +5 -5
- package/server/utils/files/atomic.ts +11 -11
- package/server/utils/files/image-store.ts +17 -6
- package/server/utils/files/journal-io.ts +2 -2
- package/server/utils/files/json.ts +5 -5
- package/server/utils/files/markdown-store.ts +4 -4
- package/server/utils/files/reference-dirs-io.ts +3 -3
- package/server/utils/files/roles-io.ts +4 -4
- package/server/utils/files/safe.ts +14 -14
- package/server/utils/files/scheduler-overrides-io.ts +2 -2
- package/server/utils/files/spreadsheet-store.ts +5 -5
- package/server/utils/files/workspace-io.ts +12 -12
- package/server/utils/gemini.ts +2 -2
- package/server/utils/gitignore.ts +9 -9
- package/server/utils/json.ts +5 -5
- package/server/utils/logBackgroundError.ts +12 -3
- package/server/utils/markdown.ts +5 -5
- package/server/utils/port.d.mts +6 -0
- package/server/utils/port.mjs +48 -0
- package/server/utils/request.ts +12 -6
- package/server/utils/spawn.ts +1 -1
- package/server/utils/types.ts +2 -2
- package/server/workspace/chat-index/summarizer.ts +4 -4
- package/server/workspace/custom-dirs.ts +5 -5
- package/server/workspace/journal/diff.ts +2 -2
- package/server/workspace/journal/index.ts +4 -4
- package/server/workspace/journal/optimizationPass.ts +2 -2
- package/server/workspace/journal/state.ts +6 -6
- package/server/workspace/paths.ts +3 -3
- package/server/workspace/reference-dirs.ts +3 -3
- package/server/workspace/skills/parser.ts +6 -6
- package/server/workspace/skills/scheduler.ts +3 -3
- package/server/workspace/skills/writer.ts +3 -3
- package/server/workspace/sources/arxivDiscovery.ts +2 -2
- package/server/workspace/sources/fetchers/rss.ts +5 -5
- package/server/workspace/sources/fetchers/rssParser.ts +4 -4
- package/server/workspace/sources/interests.ts +3 -3
- package/server/workspace/sources/paths.ts +6 -6
- package/server/workspace/sources/pipeline/fetch.ts +36 -13
- package/server/workspace/sources/pipeline/index.ts +2 -7
- package/server/workspace/sources/pipeline/notify.ts +3 -3
- package/server/workspace/sources/pipeline/plan.ts +11 -9
- package/server/workspace/sources/pipeline/write.ts +5 -5
- package/server/workspace/sources/rateLimiter.ts +1 -1
- package/server/workspace/sources/sourceState.ts +9 -4
- package/server/workspace/sources/types.ts +9 -0
- package/server/workspace/sources/urls.ts +1 -1
- package/server/workspace/tool-trace/classify.ts +4 -4
- package/server/workspace/workspace.ts +7 -7
- package/src/App.vue +286 -112
- package/src/components/CanvasViewToggle.vue +10 -7
- package/src/components/ChatInput.vue +60 -26
- package/src/components/FileContentHeader.vue +7 -4
- package/src/components/FileContentRenderer.vue +20 -6
- package/src/components/FileTree.vue +6 -3
- package/src/components/FileTreePane.vue +11 -8
- package/src/components/FilesView.vue +5 -3
- package/src/components/LockStatusPopup.vue +15 -12
- package/src/components/NotificationBell.vue +14 -5
- package/src/components/NotificationToast.vue +4 -1
- package/src/components/PluginLauncher.vue +19 -56
- package/src/components/RightSidebar.vue +13 -10
- package/src/components/SessionHistoryPanel.vue +33 -29
- package/src/components/SessionTabBar.vue +8 -10
- package/src/components/SettingsMcpTab.vue +43 -30
- package/src/components/SettingsModal.vue +21 -19
- package/src/components/SettingsReferenceDirsTab.vue +29 -24
- package/src/components/SettingsWorkspaceDirsTab.vue +32 -22
- package/src/components/SidebarHeader.vue +25 -4
- package/src/components/StackView.vue +4 -1
- package/src/components/SuggestionsPanel.vue +5 -2
- package/src/components/TodoExplorer.vue +26 -15
- package/src/components/ToolResultsPanel.vue +27 -13
- package/src/components/todo/TodoAddDialog.vue +17 -12
- package/src/components/todo/TodoEditDialog.vue +7 -2
- package/src/components/todo/TodoEditPanel.vue +15 -10
- package/src/components/todo/TodoKanbanView.vue +10 -5
- package/src/components/todo/TodoListView.vue +5 -2
- package/src/components/todo/TodoTableView.vue +5 -2
- package/src/composables/useAppApi.ts +9 -0
- package/src/composables/useDynamicFavicon.ts +172 -37
- package/src/composables/useEventListeners.ts +7 -8
- package/src/composables/useFaviconState.ts +13 -2
- package/src/composables/useFileSelection.ts +24 -6
- package/src/composables/useLayoutMode.ts +32 -0
- package/src/composables/useSessionHistory.ts +7 -17
- package/src/composables/useViewLayout.ts +20 -34
- package/src/lang/de.ts +536 -0
- package/src/lang/en.ts +558 -0
- package/src/lang/es.ts +543 -0
- package/src/lang/fr.ts +536 -0
- package/src/lang/ja.ts +536 -0
- package/src/lang/ko.ts +540 -0
- package/src/lang/pt-BR.ts +534 -0
- package/src/lang/zh.ts +537 -0
- package/src/lib/vue-i18n.ts +97 -0
- package/src/main.ts +2 -0
- package/src/plugins/canvas/View.vue +102 -186
- package/src/plugins/canvas/definition.ts +0 -8
- package/src/plugins/chart/Preview.vue +1 -1
- package/src/plugins/chart/View.vue +9 -4
- package/src/plugins/manageRoles/Preview.vue +4 -1
- package/src/plugins/manageRoles/View.vue +59 -43
- package/src/plugins/manageSkills/Preview.vue +8 -3
- package/src/plugins/manageSkills/View.vue +26 -22
- package/src/plugins/manageSource/Preview.vue +1 -1
- package/src/plugins/manageSource/View.vue +73 -52
- package/src/plugins/markdown/Preview.vue +1 -1
- package/src/plugins/markdown/View.vue +24 -34
- package/src/plugins/presentHtml/Preview.vue +1 -1
- package/src/plugins/presentHtml/View.vue +7 -4
- package/src/plugins/presentMulmoScript/Preview.vue +1 -1
- package/src/plugins/presentMulmoScript/View.vue +36 -26
- package/src/plugins/scheduler/Preview.vue +7 -4
- package/src/plugins/scheduler/TasksTab.vue +53 -24
- package/src/plugins/scheduler/View.vue +28 -19
- package/src/plugins/scheduler/formatSchedule.ts +93 -0
- package/src/plugins/spreadsheet/Preview.vue +8 -3
- package/src/plugins/spreadsheet/View.vue +21 -12
- package/src/plugins/textResponse/Preview.vue +15 -58
- package/src/plugins/textResponse/View.vue +27 -7
- package/src/plugins/todo/Preview.vue +11 -6
- package/src/plugins/todo/View.vue +27 -13
- package/src/plugins/ui-image/ImagePreview.vue +6 -3
- package/src/plugins/ui-image/ImageView.vue +7 -4
- package/src/plugins/wiki/Preview.vue +5 -2
- package/src/plugins/wiki/View.vue +202 -81
- package/src/plugins/wiki/route.ts +112 -0
- package/src/router/guards.ts +42 -24
- package/src/router/index.ts +41 -26
- package/src/types/vue-i18n.d.ts +20 -0
- package/src/utils/agent/request.ts +19 -0
- package/src/utils/canvas/layoutMode.ts +26 -0
- package/src/utils/image/cacheBust.ts +16 -0
- package/src/utils/image/resolve.ts +16 -0
- package/src/utils/path/workspaceLinkRouter.ts +81 -0
- package/src/vite-env.d.ts +9 -0
- package/client/assets/chunk-vKJrgz-R-C_I3GbVV.js +0 -1
- package/client/assets/html2canvas-Cx501zZr-BF5dYYkY.js +0 -5
- package/client/assets/index-Bm70FDU2.css +0 -1
- package/client/assets/typeof-DBp4T-Ny-BC0P-2DM.js +0 -1
- package/src/composables/useCanvasViewMode.ts +0 -121
- package/src/utils/canvas/viewMode.ts +0 -46
- /package/client/assets/{purify.es-Fx1Nqyry-PeS5RUhs.js → purify.es-Fx1Nqyry-BwJECkqS.js} +0 -0
package/src/router/index.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
// Vue-router setup (history mode — clean URLs without #).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// The "/" → "/chat" redirect ensures a fresh browser tab always lands
|
|
8
|
-
// on the chat view with the default (new) session, matching the
|
|
9
|
-
// current pre-router behaviour.
|
|
3
|
+
// Each page has its own route: /chat, /files, /todos, /scheduler,
|
|
4
|
+
// /wiki, /skills, /roles. Layout preference (single vs. stack) is a
|
|
5
|
+
// separate concern persisted in localStorage — it is not part of the
|
|
6
|
+
// URL.
|
|
10
7
|
//
|
|
11
8
|
// History mode requires the server to serve index.html for any path
|
|
12
9
|
// that doesn't match an API route or static file. In production the
|
|
@@ -18,28 +15,46 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from "vue-router"
|
|
|
18
15
|
|
|
19
16
|
// Stub component that renders nothing. Required by vue-router (every
|
|
20
17
|
// route needs a component) but never actually mounted because App.vue
|
|
21
|
-
//
|
|
18
|
+
// renders based on `route.name` rather than using <router-view>.
|
|
22
19
|
const Stub = defineComponent({ render: () => h("div") });
|
|
23
20
|
|
|
21
|
+
export const PAGE_ROUTES = {
|
|
22
|
+
chat: "chat",
|
|
23
|
+
files: "files",
|
|
24
|
+
todos: "todos",
|
|
25
|
+
scheduler: "scheduler",
|
|
26
|
+
wiki: "wiki",
|
|
27
|
+
skills: "skills",
|
|
28
|
+
roles: "roles",
|
|
29
|
+
history: "history",
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
export type PageRouteName = (typeof PAGE_ROUTES)[keyof typeof PAGE_ROUTES];
|
|
33
|
+
|
|
24
34
|
const routes: RouteRecordRaw[] = [
|
|
25
|
-
{
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
{ path: "/", redirect: "/chat" },
|
|
36
|
+
{ path: "/chat/:sessionId?", name: PAGE_ROUTES.chat, component: Stub },
|
|
37
|
+
// Files view uses a repeatable catch-all so `/files/a/b/c.md` maps
|
|
38
|
+
// to `params.pathMatch = ["a", "b", "c.md"]`. Joining on `/` at read
|
|
39
|
+
// time keeps each segment URL-encoded independently — passing a
|
|
40
|
+
// string-form catch-all (`:pathMatch(.*)`) would collapse slashes
|
|
41
|
+
// to `%2F` at push time and mangle deep paths. An empty segment
|
|
42
|
+
// (`/files`) yields an empty array, which we treat as "no file
|
|
43
|
+
// selected". See plans/feat-files-path-url.md.
|
|
44
|
+
{ path: "/files/:pathMatch(.*)*", name: PAGE_ROUTES.files, component: Stub },
|
|
45
|
+
{ path: "/todos", name: PAGE_ROUTES.todos, component: Stub },
|
|
46
|
+
{ path: "/scheduler", name: PAGE_ROUTES.scheduler, component: Stub },
|
|
47
|
+
// Wiki sub-views live on the path rather than in query params so
|
|
48
|
+
// URLs mirror the filesystem layout (`data/wiki/pages/<slug>.md`)
|
|
49
|
+
// and stay sibling-safe (no query-key bleed from other routes).
|
|
50
|
+
// `section` is a closed enum; unknown sections fall through to the
|
|
51
|
+
// catch-all redirect below. `slug` only applies when `section ===
|
|
52
|
+
// "pages"`. See plans/feat-wiki-path-urls.md.
|
|
53
|
+
{ path: "/wiki/:section(pages|log|lint-report)?/:slug?", name: PAGE_ROUTES.wiki, component: Stub },
|
|
54
|
+
{ path: "/skills", name: PAGE_ROUTES.skills, component: Stub },
|
|
55
|
+
{ path: "/roles", name: PAGE_ROUTES.roles, component: Stub },
|
|
56
|
+
{ path: "/history", name: PAGE_ROUTES.history, component: Stub },
|
|
57
|
+
{ path: "/:pathMatch(.*)*", redirect: "/chat" },
|
|
43
58
|
];
|
|
44
59
|
|
|
45
60
|
const router = createRouter({
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Module augmentation for vue-i18n. Surfaces the English dictionary
|
|
2
|
+
// shape as `DefineLocaleMessage` so IDEs autocomplete key paths on
|
|
3
|
+
// `$t("…")` and `i18n.global.t("…")`. The schema generic on
|
|
4
|
+
// createI18n (src/lib/vue-i18n.ts) handles the `useI18n()` side.
|
|
5
|
+
//
|
|
6
|
+
// Caveat: vue-i18n v11's `t()` overload has a `Key extends string`
|
|
7
|
+
// fallback, so a typo in `useI18n().t("…")` at a deeply-typed call
|
|
8
|
+
// site may still compile. Autocomplete is the main value; strict
|
|
9
|
+
// rejection of unknown keys would require a bespoke wrapper.
|
|
10
|
+
|
|
11
|
+
import enMessages from "../lang/en";
|
|
12
|
+
|
|
13
|
+
// Alias so `extends` has an interface-shaped base (typeof in extends
|
|
14
|
+
// position is a syntax error).
|
|
15
|
+
type EnMessages = typeof enMessages;
|
|
16
|
+
|
|
17
|
+
declare module "vue-i18n" {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
19
|
+
export interface DefineLocaleMessage extends EnMessages {}
|
|
20
|
+
}
|
|
@@ -16,6 +16,24 @@ export interface AgentRequestBody {
|
|
|
16
16
|
roleId: string;
|
|
17
17
|
chatSessionId: string;
|
|
18
18
|
selectedImageData: string | undefined;
|
|
19
|
+
// IANA identifier (e.g. "Asia/Tokyo", "America/New_York"). The
|
|
20
|
+
// server uses this to interpret bare time expressions in the user's
|
|
21
|
+
// message without asking for clarification every turn. Undefined if
|
|
22
|
+
// the browser can't resolve a timezone — the server then falls back
|
|
23
|
+
// to its own local time and asks as before.
|
|
24
|
+
userTimezone: string | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// `Intl.DateTimeFormat().resolvedOptions().timeZone` can, in theory,
|
|
28
|
+
// throw in some locked-down environments; wrap so a broken Intl
|
|
29
|
+
// doesn't take down the send path.
|
|
30
|
+
function resolveBrowserTimezone(): string | undefined {
|
|
31
|
+
try {
|
|
32
|
+
const zoneId = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
33
|
+
return typeof zoneId === "string" && zoneId.length > 0 ? zoneId : undefined;
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
19
37
|
}
|
|
20
38
|
|
|
21
39
|
export function buildAgentRequestBody(params: AgentRequestBodyParams): AgentRequestBody {
|
|
@@ -24,6 +42,7 @@ export function buildAgentRequestBody(params: AgentRequestBodyParams): AgentRequ
|
|
|
24
42
|
roleId: params.role.id,
|
|
25
43
|
chatSessionId: params.chatSessionId,
|
|
26
44
|
selectedImageData: params.selectedImageData,
|
|
45
|
+
userTimezone: resolveBrowserTimezone(),
|
|
27
46
|
};
|
|
28
47
|
}
|
|
29
48
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Pure helpers for the canvas layout mode.
|
|
2
|
+
//
|
|
3
|
+
// Layout is a user preference — single (sidebar + canvas) vs. stack
|
|
4
|
+
// (no sidebar, full-width canvas). It applies only to the /chat page
|
|
5
|
+
// and persists in localStorage.
|
|
6
|
+
//
|
|
7
|
+
// Pages like /files, /todos, /wiki, /skills, /roles, /scheduler are
|
|
8
|
+
// distinct routes, not layout variants. They live in the router, not
|
|
9
|
+
// here.
|
|
10
|
+
|
|
11
|
+
export const LAYOUT_MODES = {
|
|
12
|
+
single: "single",
|
|
13
|
+
stack: "stack",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
export type LayoutMode = (typeof LAYOUT_MODES)[keyof typeof LAYOUT_MODES];
|
|
17
|
+
|
|
18
|
+
export const LAYOUT_MODE_STORAGE_KEY = "canvas_layout_mode";
|
|
19
|
+
|
|
20
|
+
// Legacy key from before layout/page were split. Deleted on first
|
|
21
|
+
// read of useLayoutMode — not migrated (fresh start).
|
|
22
|
+
export const LEGACY_VIEW_MODE_STORAGE_KEY = "canvas_view_mode";
|
|
23
|
+
|
|
24
|
+
export function parseStoredLayoutMode(stored: string | null): LayoutMode {
|
|
25
|
+
return stored === LAYOUT_MODES.stack ? LAYOUT_MODES.stack : LAYOUT_MODES.single;
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { reactive } from "vue";
|
|
2
|
+
|
|
3
|
+
// Keyed by workspace-relative image path (e.g. "artifacts/images/abc.png").
|
|
4
|
+
// `resolveImageSrc` reads this to append `?v=<bump>` to the URL so consumers
|
|
5
|
+
// (View, Preview) re-fetch when the file on disk has been overwritten in
|
|
6
|
+
// place. The canvas plugin is the current producer — it bumps after each
|
|
7
|
+
// autosave PUT.
|
|
8
|
+
const imageBumps = reactive<Record<string, number>>({});
|
|
9
|
+
|
|
10
|
+
export function getImageBump(imagePath: string): number {
|
|
11
|
+
return imageBumps[imagePath] ?? 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function bumpImage(imagePath: string): void {
|
|
15
|
+
imageBumps[imagePath] = Date.now();
|
|
16
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { API_ROUTES } from "../../config/apiRoutes";
|
|
2
|
+
import { getImageBump } from "./cacheBust";
|
|
2
3
|
|
|
3
4
|
/** Convert an imageData value to a displayable URL.
|
|
4
5
|
* Handles both legacy data URIs and workspace-relative file paths. */
|
|
@@ -6,3 +7,18 @@ export function resolveImageSrc(imageData: string): string {
|
|
|
6
7
|
if (imageData.startsWith("data:")) return imageData;
|
|
7
8
|
return `${API_ROUTES.files.raw}?path=${encodeURIComponent(imageData)}`;
|
|
8
9
|
}
|
|
10
|
+
|
|
11
|
+
/** Same as `resolveImageSrc` but appends the current cache-bust token
|
|
12
|
+
* so the browser re-fetches when the file has been overwritten in
|
|
13
|
+
* place (e.g. the canvas plugin rewrote it).
|
|
14
|
+
*
|
|
15
|
+
* Use this from display-only consumers (Preview, thumbnail list).
|
|
16
|
+
* Avoid inside the canvas View's own `backgroundImage` — changing
|
|
17
|
+
* that URL mid-session makes `vue-drawing-canvas` re-fetch on every
|
|
18
|
+
* redraw, which races with stroke painting and blanks the canvas. */
|
|
19
|
+
export function resolveImageSrcFresh(imageData: string): string {
|
|
20
|
+
if (imageData.startsWith("data:")) return imageData;
|
|
21
|
+
const base = `${API_ROUTES.files.raw}?path=${encodeURIComponent(imageData)}`;
|
|
22
|
+
const bump = getImageBump(imageData);
|
|
23
|
+
return bump > 0 ? `${base}&v=${bump}` : base;
|
|
24
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Classify a workspace-relative link from agent Markdown into a
|
|
2
|
+
// navigation target. Used by TextResponse's click handler to route
|
|
3
|
+
// internal links to the appropriate view (Wiki, Files, Session)
|
|
4
|
+
// instead of letting them fall through to the SPA router.
|
|
5
|
+
//
|
|
6
|
+
// Pure function — no DOM or Vue dependencies, fully unit-testable.
|
|
7
|
+
|
|
8
|
+
import { isExternalHref, extractSessionIdFromPath } from "./relativeLink";
|
|
9
|
+
|
|
10
|
+
export type WorkspaceLinkTarget = { kind: "wiki"; slug: string } | { kind: "file"; path: string } | { kind: "session"; sessionId: string };
|
|
11
|
+
|
|
12
|
+
// Match `data/wiki/pages/<slug>.md` or `wiki/pages/<slug>.md`.
|
|
13
|
+
const WIKI_PAGE_PATTERN = /(?:data\/)?wiki\/pages\/([^/]+)\.md$/;
|
|
14
|
+
|
|
15
|
+
// Match `conversations/chat/<id>.jsonl` (delegates to extractSessionIdFromPath).
|
|
16
|
+
const CHAT_LOG_PREFIX = "conversations/";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Given a raw href attribute from agent Markdown, return a typed
|
|
20
|
+
* navigation target, or null if the link is external, anchor-only,
|
|
21
|
+
* or unresolvable.
|
|
22
|
+
*
|
|
23
|
+
* Agent links are typically workspace-root-relative (e.g.
|
|
24
|
+
* `data/wiki/pages/foo.md`). Relative paths with `../` that escape
|
|
25
|
+
* the workspace root return null.
|
|
26
|
+
*/
|
|
27
|
+
export function classifyWorkspacePath(href: string): WorkspaceLinkTarget | null {
|
|
28
|
+
if (!href || isExternalHref(href)) return null;
|
|
29
|
+
if (href.startsWith("#")) return null;
|
|
30
|
+
|
|
31
|
+
// Strip fragment and query
|
|
32
|
+
const cleaned = stripFragmentAndQuery(href);
|
|
33
|
+
if (cleaned.length === 0) return null;
|
|
34
|
+
|
|
35
|
+
// Normalize path (collapse ./ and ../, reject root-escape)
|
|
36
|
+
const normalized = normalizePath(cleaned);
|
|
37
|
+
if (!normalized) return null;
|
|
38
|
+
|
|
39
|
+
// Wiki page: data/wiki/pages/<slug>.md
|
|
40
|
+
const wikiMatch = normalized.match(WIKI_PAGE_PATTERN);
|
|
41
|
+
if (wikiMatch) {
|
|
42
|
+
return { kind: "wiki", slug: wikiMatch[1] };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Chat session log: conversations/chat/<id>.jsonl
|
|
46
|
+
if (normalized.startsWith(CHAT_LOG_PREFIX)) {
|
|
47
|
+
const chatPath = normalized.slice(CHAT_LOG_PREFIX.length);
|
|
48
|
+
const sessionId = extractSessionIdFromPath(chatPath);
|
|
49
|
+
if (sessionId) {
|
|
50
|
+
return { kind: "session", sessionId };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Everything else: open in Files view
|
|
55
|
+
return { kind: "file", path: normalized };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function stripFragmentAndQuery(str: string): string {
|
|
59
|
+
const hashIdx = str.indexOf("#");
|
|
60
|
+
const queryIdx = str.indexOf("?");
|
|
61
|
+
let end = str.length;
|
|
62
|
+
if (hashIdx !== -1 && hashIdx < end) end = hashIdx;
|
|
63
|
+
if (queryIdx !== -1 && queryIdx < end) end = queryIdx;
|
|
64
|
+
return str.slice(0, end);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizePath(raw: string): string | null {
|
|
68
|
+
if (raw.length === 0) return null;
|
|
69
|
+
const parts = raw.split("/");
|
|
70
|
+
const stack: string[] = [];
|
|
71
|
+
for (const part of parts) {
|
|
72
|
+
if (part === "" || part === ".") continue;
|
|
73
|
+
if (part === "..") {
|
|
74
|
+
if (stack.length === 0) return null;
|
|
75
|
+
stack.pop();
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
stack.push(part);
|
|
79
|
+
}
|
|
80
|
+
return stack.length === 0 ? null : stack.join("/");
|
|
81
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(e&&(t=e(e=0)),t),s=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),c=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},l=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},u=(n,r,a)=>(a=n==null?{}:e(i(n)),l(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),d=e=>a.call(e,`module.exports`)?e[`module.exports`]:l(t({},`__esModule`,{value:!0}),e),f=Object.create,p=Object.defineProperty,m=Object.getOwnPropertyDescriptor,h=Object.getOwnPropertyNames,g=Object.getPrototypeOf,_=Object.prototype.hasOwnProperty,v=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),y=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=h(t),a=0,o=i.length,s;a<o;a++)s=i[a],!_.call(e,s)&&s!==n&&p(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=m(t,s))||r.enumerable});return e},b=(e,t,n)=>(n=e==null?{}:f(g(e)),y(t||!e||!e.__esModule?p(n,`default`,{value:e,enumerable:!0}):n,e));export{c as a,o as i,v as n,d as o,s as r,u as s,b as t};
|