@agimon-ai/po-file-edit 0.0.0-alpha.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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.mjs","names":["isRecord","routes","MediaPreview","MediaPreview","activityGroupFiles","channelFileEdits","fileLinksFiles","fillFiles","leaderFiles"],"sources":["../../src/types/webFiles.ts","../../src/extensions/workspaces/sessions/(frontend)/activity-group/files.web.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesStore.ts","../../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.web.ts","../../src/types/apiRoutes.ts","../../generated/client.ts","../../src/types/fileEditsApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/filesApi.ts","../../src/extensions/workspaces/sessions/(frontend)/_lib/fileView.ts","../../src/extensions/workspaces/sessions/(frontend)/_components/DeleteFileDialog.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/SessionMediaPreview.tsx","../../src/extensions/workspaces/sessions/(frontend)/_components/FilePanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/file-links/_components/FilePreviewPanel.tsx","../../src/extensions/workspaces/sessions/(frontend)/file-links/_lib/fileLinks.ts","../../src/extensions/workspaces/sessions/(frontend)/file-links/files.web.ts","../../src/extensions/workspaces/sessions/(frontend)/fill/_components/FilesBrowser.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/_components/FilesActivitySection.tsx","../../src/extensions/workspaces/sessions/(frontend)/fill/Files.activity.files.web.tsx","../../src/extensions/workspaces/sessions/(frontend)/leader/files.web.ts","../../generated/web.ts"],"sourcesContent":["import type { FileEditTool } from './domain';\n\n/**\n * View types shared by this package's hub channel and its web plugin. Wire\n * JSON only, no node types, so the cockpit bundle can carry them; the\n * cockpit receives them as 'file_edits' channel payloads.\n *\n * The channel carries the list alone. One file's diffs and content are large\n * and only wanted once a reader opens the file, so those come from the session\n * API instead (see fileEditsApi.ts).\n */\n\n/** One file this session changed, as the activity dock lists it. */\nexport interface FilesItemView {\n /** Absolute path, and the identity every API route takes back. */\n path: string;\n /** Path relative to the session's working directory, which is what a row shows. */\n relPath: string;\n /** The tool behind the most recent change. */\n tool: FileEditTool;\n /** When that change happened, epoch milliseconds. */\n at: number;\n /** How many changes this session recorded for the file. */\n count: number;\n /**\n * Whether any change captured a baseline, so a diff can be shown at all. A\n * file found by comparing the tree around a bash call has no \"before\", so it\n * is listed and readable but never diffed.\n */\n diffable: boolean;\n}\n\n/** The frame type the hub publishes and the web plugin claims; declared in the poWeb block too. */\nexport const filesChannelType = 'file_edits';\n\n/** Footer status key whose presence shows the activity group; the extension publishes it. */\nexport const filesStatusKey = 'doom-file-edit-files';\n","import { defineActivityGroup } from '@agimon-ai/po-core/web';\n\nimport { filesStatusKey } from '../../../../../types/webFiles';\nexport default defineActivityGroup({\n keys: 'e f',\n statusKey: filesStatusKey,\n hideWhenEmpty: true,\n marksBackgroundWork: false,\n order: 15,\n});\n","import { defineSessionStore } from '@agimon-ai/po-core/web';\nimport type { ReviewComment } from '@agimon-ai/po-web-components';\n\nimport type { FileEditsDetailView } from '../../../../../types/fileEditsApi';\nimport { filesChannelType, type FilesItemView } from '../../../../../types/webFiles';\n\n/**\n * One session's record: the hub's last report plus whatever this page does with\n * it. Records are immutable values; the reducer and every action return a new one.\n *\n * Detail is cached per file rather than per open tab, because a reader closes a\n * file and comes back to it, and refetching the same history for the same click\n * is work the session already did.\n */\nexport interface FilesSession {\n items: FilesItemView[];\n /** One file's fetched history, keyed by absolute path. */\n detail: Readonly<Record<string, FileEditsDetailView>>;\n /** Paths with a detail request in flight, so a panel can say it is loading. */\n loading: readonly string[];\n /** What a failed fetch or a refused save reported, keyed by absolute path. */\n errors: Readonly<Record<string, string>>;\n /** Review notes waiting to be sent, in the order they were written. */\n comments: readonly ReviewComment[];\n}\n\nconst empty: FilesSession = { items: [], detail: {}, loading: [], errors: {}, comments: [] };\n\nexport const files = defineSessionStore<FilesSession>(empty);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isItem(value: unknown): value is FilesItemView {\n return (\n isRecord(value) &&\n typeof value.path === 'string' &&\n typeof value.relPath === 'string' &&\n typeof value.tool === 'string' &&\n typeof value.at === 'number' &&\n typeof value.count === 'number'\n );\n}\n\nexport interface FilesPayload {\n items: FilesItemView[];\n}\n\n/** The plugin's session data channel: 'file_edits' payloads into the store. */\nexport const filesChannel = files.channel<FilesPayload>({\n channel: filesChannelType,\n parse(input) {\n if (!isRecord(input) || !Array.isArray(input.items)) return null;\n return { items: input.items.filter(isItem) };\n },\n // Only the list is replaced. The cached detail and the pending notes belong\n // to files a reader opened, not to the list: a file the session deletes stops\n // being listed, and pruning here would blank the tab still open on it and\n // then refetch it on the next frame.\n reduce: (current, { items }) => ({ ...current, items }),\n});\n\nexport function markLoading(sessionId: string, filePath: string): void {\n files.update(sessionId, (current) =>\n current.loading.includes(filePath)\n ? current\n : { ...current, loading: [...current.loading, filePath], errors: withoutKey(current.errors, filePath) },\n );\n}\n\nexport function storeDetail(sessionId: string, detail: FileEditsDetailView): void {\n files.update(sessionId, (current) => ({\n ...current,\n detail: { ...current.detail, [detail.path]: detail },\n loading: current.loading.filter((path) => path !== detail.path),\n errors: withoutKey(current.errors, detail.path),\n }));\n}\n\nexport function storeError(sessionId: string, filePath: string, message: string): void {\n files.update(sessionId, (current) => ({\n ...current,\n loading: current.loading.filter((path) => path !== filePath),\n errors: { ...current.errors, [filePath]: message },\n }));\n}\n\nexport function addComment(sessionId: string, comment: ReviewComment): void {\n files.update(sessionId, (current) => ({ ...current, comments: [...current.comments, comment] }));\n}\n\nexport function removeComment(sessionId: string, id: string): void {\n files.update(sessionId, (current) => ({\n ...current,\n comments: current.comments.filter((comment) => comment.id !== id),\n }));\n}\n\nexport function clearComments(sessionId: string, filePath: string): void {\n files.update(sessionId, (current) => ({\n ...current,\n comments: current.comments.filter((comment) => comment.path !== filePath),\n }));\n}\n\nfunction withoutKey(source: Readonly<Record<string, string>>, key: string): Record<string, string> {\n return Object.fromEntries(Object.entries(source).filter(([entry]) => entry !== key));\n}\n","import { defineChannelFile } from '@agimon-ai/po-core/web';\n\nimport { filesChannel } from '../_lib/filesStore';\nexport default defineChannelFile(filesChannel);\n","import { apiResponse, defineApiRoutes } from '@agimon-ai/po-core/web';\n\nimport type { FileEditsDetailView, FileEditsPreviewView, FileEditsSaveView } from './fileEditsApi';\n\n/**\n * This package's routes, as data.\n *\n * No scope and no base path. The build reads both off\n * `src/extensions/workspaces/sessions/(backend)/api/file-edits/`, so the mount\n * is stated once, by the folder that creates it. They used to be written here,\n * in the contract and again inside every URL the page built, and the copies\n * were free to disagree.\n *\n * This is the one file the routes, the Hono app and the browser all read, so a\n * path cannot move on one side alone.\n */\nexport default defineApiRoutes({\n detail: {\n method: 'GET',\n path: '/detail',\n query: ['path'],\n response: apiResponse<FileEditsDetailView>(),\n },\n preview: {\n method: 'GET',\n path: '/preview',\n query: ['path'],\n response: apiResponse<FileEditsPreviewView>(),\n },\n save: {\n method: 'PUT',\n path: '/content',\n response: apiResponse<FileEditsSaveView>(),\n },\n /** The same path as `save`; the method is the whole of the difference. */\n remove: {\n method: 'DELETE',\n path: '/content',\n query: ['path'],\n },\n /**\n * The host's own bytes route, not this package's.\n *\n * `host: true` hangs it off the session root rather than under `/plugins`,\n * which is how the cockpit already serves it. Declaring it here is the first\n * time anything records that this package's previews depend on it.\n */\n file: {\n method: 'GET',\n path: '/file',\n query: ['path'],\n host: true,\n },\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { createApiClient, sessionApiAddress } from '@agimon-ai/po-core/web';\nimport { sealedTransport } from '@agimon-ai/po-web-security/browser';\n\nimport routes from '../src/types/apiRoutes';\n\n/** Scopes the tree mounts 'file-edits' at. */\nexport const apiScopes = ['session'] as const;\n\nexport const api = createApiClient(routes, {\n scopes: apiScopes,\n basePath: 'file-edits',\n transport: (input, init) => sealedTransport.fetch(input, init),\n sessionAddress: sessionApiAddress,\n});\n","import type { FileEditOrigin, FileEditTool } from './domain';\n\n/**\n * The wire vocabulary this package's API shares with whatever calls it.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read: the cockpit plugin builds its URLs from these and the\n * routes answer them, so neither half can drift from the other.\n *\n * Routes live in `./apiRoutes`, which the contract, the Hono app and the\n * generated client all read. This file holds the shapes that travel over them.\n *\n * Opening a changed file wants its whole history at once, so `detail` answers\n * in one round trip rather than making the page stitch three reads together;\n * `content` takes the manual save back; `preview` reads a file the session\n * never changed, which has no history to answer with.\n */\n\n/** Where a host mounts this package's API; the segment after /api/plugins/. */\nexport const API_BASE_PATH = 'file-edits';\n\n/** Query parameter naming the file a request is about. */\nexport const PATH_QUERY_PARAM = 'path';\n\n/**\n * One line of a diff. The number is the new file's for context and additions\n * and the old file's for removals, so it always says where the line lives.\n */\nexport interface FileEditsDiffRow {\n marker: '+' | '-' | ' ';\n line: number;\n content: string;\n}\n\n/**\n * A run of changed lines with its surrounding context. Hunks are sent apart\n * rather than joined by an elision marker, so the page draws the gap between\n * them instead of parsing one out of a string.\n */\nexport interface FileEditsDiffHunk {\n /** The line number the hunk opens at. */\n start: number;\n rows: FileEditsDiffRow[];\n}\n\n/**\n * One change in a file's history, each rendered on its own so a file edited\n * four times shows four distinct diffs rather than one merged blur.\n */\nexport interface FileEditsVersionView {\n /** Position in the file's history, oldest first, starting at 1. */\n index: number;\n tool: FileEditTool;\n at: number;\n origin: FileEditOrigin;\n additions: number;\n removals: number;\n /** The changed lines of this one change; absent when no baseline was captured. */\n hunks?: FileEditsDiffHunk[];\n /** Why there is no diff, shown in its place. */\n note?: string;\n}\n\n/** Everything this session did to the file: its first baseline against what is on disk now. */\nexport interface FileEditsCumulativeView {\n additions: number;\n removals: number;\n hunks?: FileEditsDiffHunk[];\n note?: string;\n}\n\n/** The file as it stands on disk right now, which is what the source view edits. */\nexport interface FileEditsWorkingView {\n content: string;\n /** sha256 of the content; the token a save hands back to prove it is not stale. */\n hash: string;\n /** True when the file is binary, missing, or past the size cap, so `content` is empty. */\n unavailable: boolean;\n reason?: string;\n}\n\n/** What the detail route answers with. */\nexport interface FileEditsDetailView {\n path: string;\n relPath: string;\n versions: FileEditsVersionView[];\n cumulative: FileEditsCumulativeView;\n working: FileEditsWorkingView;\n}\n\n/**\n * What the preview route answers with: the file as it stands, and nothing\n * about how it got there. A file this session never touched has no history and\n * no baseline, so there is no diff to send and no save to take back.\n */\nexport interface FileEditsPreviewView {\n path: string;\n relPath: string;\n working: FileEditsWorkingView;\n}\n\n/** What the save route takes. */\nexport interface FileEditsSaveRequest {\n path: string;\n /** The hash the reader loaded; a mismatch means the file moved underneath them. */\n expectedHash: string;\n content: string;\n}\n\n/** What the save route answers with. */\nexport interface FileEditsSaveView {\n hash: string;\n}\n\n/** What a route reports when it refuses; the page shows `error` verbatim. */\nexport interface FileEditsErrorView {\n error: string;\n /** On a stale save, the hash the file actually has now. */\n hash?: string;\n}\n","import { api } from '../../../../../../generated/client';\nimport {\n type FileEditsDetailView,\n type FileEditsPreviewView,\n PATH_QUERY_PARAM,\n} from '../../../../../types/fileEditsApi';\n\n/**\n * The page's half of this package's session API: one file's history, the\n * manual save, and the deletion.\n *\n * These are adapters now, not a transport. The generated client owns the URL\n * and the sealed transport with it, so nothing here spells a route or reaches\n * for `fetch`. What stays is this package's own vocabulary: which refusals the\n * reader is shown, and the stale-save contract the editor depends on.\n *\n * Sealing still matters and still happens, one layer down. What travels here is\n * file contents in both directions, which is the worst thing in the cockpit to\n * hand a tunnel's relay in the clear.\n */\n\nconst UNREACHABLE = 'The session is unreachable.';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Where a file's raw bytes are served from, for the ones the page shows rather\n * than edits.\n *\n * This is the host's route, not this package's, which is why the route table\n * marks it `host: true` and it lands beside the session rather than under\n * `/plugins`. The cockpit already serves any file under a session's working\n * directory, and that route carries the traversal guard, the size cap and the\n * content-type mapping. A second one here would duplicate a security boundary\n * to avoid duplicating a URL, which is the wrong way round.\n */\nexport function sessionFileUrl(sessionId: string, relPath: string): string {\n return api.session(sessionId).file.url({ query: { [PATH_QUERY_PARAM]: relPath } });\n}\n\n/** The message a reader is shown: the route's own words, or why there were none. */\nfunction messageOf(result: { status: number; error: string }): string {\n if (result.status === 0) return UNREACHABLE;\n return result.error === '' ? `The session answered ${result.status}.` : result.error;\n}\n\nexport type FetchDetailResult = { ok: true; detail: FileEditsDetailView } | { ok: false; error: string };\n\nexport async function fetchFileDetail(sessionId: string, filePath: string): Promise<FetchDetailResult> {\n const result = await api.session(sessionId).detail({ query: { [PATH_QUERY_PARAM]: filePath } });\n if (!result.ok) return { ok: false, error: messageOf(result) };\n if (!isRecord(result.data)) return { ok: false, error: 'The session answered with no detail.' };\n return { ok: true, detail: result.data };\n}\n\nexport type FetchPreviewResult = { ok: true; preview: FileEditsPreviewView } | { ok: false; error: string };\n\n/** One file the session never changed, read only; the route bounds it to the working directory. */\nexport async function fetchFilePreview(sessionId: string, filePath: string): Promise<FetchPreviewResult> {\n const result = await api.session(sessionId).preview({ query: { [PATH_QUERY_PARAM]: filePath } });\n if (!result.ok) return { ok: false, error: messageOf(result) };\n if (!isRecord(result.data)) return { ok: false, error: 'The session answered with no file.' };\n return { ok: true, preview: result.data };\n}\n\nexport type SaveResult =\n | { ok: true; hash: string }\n /** The file moved under the editor; `hash` is what it holds now. */\n | { ok: false; stale: true; error: string; hash?: string }\n | { ok: false; stale: false; error: string };\n\nexport async function saveFileContent(\n sessionId: string,\n filePath: string,\n expectedHash: string,\n content: string,\n): Promise<SaveResult> {\n const result = await api.session(sessionId).save({ body: { path: filePath, expectedHash, content } });\n // The agent runs in the same working directory, so it can rewrite the file\n // while a reader is still editing it. A moved hash is the one refusal the\n // editor recovers from, so it is read before the generic failure.\n if (result.status === 409) {\n const data = result.ok ? undefined : result.data;\n const hash = isRecord(data) && typeof data.hash === 'string' ? data.hash : undefined;\n return {\n ok: false,\n stale: true,\n error: result.ok ? 'The file changed since it was opened.' : messageOf(result),\n ...(hash === undefined ? {} : { hash }),\n };\n }\n if (!result.ok) return { ok: false, stale: false, error: messageOf(result) };\n return { ok: true, hash: isRecord(result.data) && typeof result.data.hash === 'string' ? result.data.hash : '' };\n}\n\nexport type DeleteResult = { ok: true } | { ok: false; error: string };\n\n/**\n * Removes the file from disk.\n *\n * The route takes only a path, with no hash to prove the reader saw what is\n * there now: a deletion discards the file whatever it holds, so a staleness\n * check would guard nothing. The confirmation in front of it is the guard.\n */\nexport async function deleteFile(sessionId: string, filePath: string): Promise<DeleteResult> {\n const result = await api.session(sessionId).remove({ query: { [PATH_QUERY_PARAM]: filePath } });\n return result.ok ? { ok: true } : { ok: false, error: messageOf(result) };\n}\n","import { buildReviewPrompt, grammarKeyOf, mediaKindOf, type ReviewComment } from '@agimon-ai/po-web-components';\n\nimport type { FileEditTool } from '../../../../../types/domain';\nimport type { FilesItemView } from '../../../../../types/webFiles';\n\n/**\n * Pure view logic the file surfaces share: how a change is labelled, how a\n * file is identified, and how a review reaches the agent.\n *\n * None of this touches React or the DOM beyond what it is handed, so the\n * awkward parts are pinned by tests rather than by clicking through a browser.\n * Wording a review and drawing a diff are shared with other review surfaces\n * and live in po-web-components.\n */\n\n/** What a row says about the tool behind a change. */\nexport const TOOL_LABEL: Readonly<Record<FileEditTool, string>> = {\n edit: 'edit',\n write: 'write',\n bash: 'command',\n user: 'you',\n};\n\n/** Filters visible relative paths without changing the timeline's newest-first order. */\nexport function filterFileItems(items: readonly FilesItemView[], query: string): FilesItemView[] {\n const normalized = query.trim().toLocaleLowerCase();\n if (normalized.length === 0) return [...items];\n return items.filter((item) => item.relPath.toLocaleLowerCase().includes(normalized));\n}\n\n/**\n * How the preview shows a file: as the thing it is, wherever the browser can.\n *\n * One value rather than a chain of checks at the call site, because the order\n * carries the decisions. Media comes first, so a PNG the snapshot store\n * refused as binary is still a picture rather than an apology. `unavailable`\n * comes next, since a file with no readable text has nothing for the rest of\n * the list to render. Then the two documents that have a rendering of their\n * own, and then code, which is shown highlighted rather than as flat text.\n */\nexport type PreviewMode = 'media' | 'unavailable' | 'markdown' | 'html' | 'code' | 'text';\n\nexport function previewModeOf(filePath: string, unavailable: boolean): PreviewMode {\n if (mediaKindOf(filePath) !== 'download') return 'media';\n if (unavailable) return 'unavailable';\n const lower = filePath.toLowerCase();\n if (lower.endsWith('.md') || lower.endsWith('.markdown')) return 'markdown';\n if (lower.endsWith('.html') || lower.endsWith('.htm')) return 'html';\n return grammarKeyOf(filePath) === undefined ? 'text' : 'code';\n}\n\n/** A short, stable fingerprint of a string; enough to separate two paths in an id. */\nfunction fingerprint(value: string): string {\n let hash = 2166136261;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n return (hash >>> 0).toString(36);\n}\n\n/** The transient tab id for one file; stable, so reopening focuses rather than duplicates. */\nexport function fileTabId(filePath: string): string {\n // The id reaches a URL, so anything outside the safe set becomes a dash, and\n // the path's own fingerprint keeps two similarly-named files apart.\n const slug = filePath.replaceAll(/[^a-zA-Z0-9]+/gu, '-').replace(/^-|-$/gu, '');\n return `files-file-${fingerprint(filePath)}-${slug.slice(-40)}`;\n}\n\n/**\n * Sends a review as a deferred follow-up and reports whether it left.\n *\n * `follow_up` waits behind a running turn and starts when idle, without\n * treating the review as an explicit Queue action. The sender throws when\n * disconnected, so the caller keeps the comments and reports the reason.\n */\nexport function sendReviewFrame(\n send: (frame: Record<string, unknown>) => void,\n comments: readonly ReviewComment[],\n): string | undefined {\n try {\n send({ type: 'follow_up', message: buildReviewPrompt(comments) });\n return undefined;\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n}\n","import {\n Button,\n Dialog,\n DialogBody,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@agimon-ai/po-web-components';\n\n/**\n * The confirmation that stands between the delete button and the unlink.\n *\n * Deleting is the one action here with nothing behind it: a manual save can be\n * undone from the file's own history, and a comment can be removed before it\n * is sent, but this session's snapshots go when the session does, so a file\n * removed by mistake is not coming back from anything the cockpit holds.\n */\nexport interface DeleteFileDialogProps {\n relPath: string;\n open: boolean;\n onConfirm: () => void;\n onCancel: () => void;\n}\n\nexport function DeleteFileDialog({ relPath, open, onConfirm, onCancel }: DeleteFileDialogProps) {\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) onCancel();\n }}\n >\n <DialogContent width=\"sm\" data-testid=\"files-delete-dialog\" aria-describedby={undefined}>\n <DialogHeader>\n <DialogTitle>delete file</DialogTitle>\n </DialogHeader>\n <DialogBody>\n <DialogDescription>\n this removes <span className=\"font-semibold text-doom-hi\">{relPath}</span> from disk. the session keeps its\n record of the change, but the file itself is gone.\n </DialogDescription>\n <DialogFooter>\n <Button variant=\"outline\" size=\"md\" data-testid=\"files-delete-cancel\" autoFocus onClick={onCancel}>\n cancel\n </Button>\n <Button variant=\"danger\" size=\"md\" data-testid=\"files-delete-confirm\" onClick={onConfirm}>\n delete\n </Button>\n </DialogFooter>\n </DialogBody>\n </DialogContent>\n </Dialog>\n );\n}\n","import { MediaPreview } from '@agimon-ai/po-web-components';\nimport { sealedTransport } from '@agimon-ai/po-web-security/browser';\nimport { useEffect, useState } from 'react';\n\nimport { sessionFileUrl } from '../_lib/filesApi';\n\n/** Media elements cannot issue the encrypted fetch required by remote session files. */\nexport function SessionMediaPreview({\n src,\n path,\n 'data-testid': testId,\n}: {\n src: string;\n path: string;\n 'data-testid'?: string;\n}) {\n const [loaded, setLoaded] = useState<{ source: string; url?: string; contentType?: string; error?: string }>();\n useEffect(() => {\n if (src === '') return;\n const controller = new AbortController();\n let url: string | undefined;\n void (async () => {\n const response = await sealedTransport.fetch(src, { signal: controller.signal, cache: 'no-store' });\n if (!response.ok) throw new Error(`Media could not be loaded (${response.status}).`);\n const blob = await response.blob();\n if (controller.signal.aborted) return;\n url = URL.createObjectURL(blob);\n setLoaded({\n source: src,\n url,\n contentType: (response.headers.get('content-type') ?? blob.type).split(';', 1)[0]?.trim().toLowerCase() ?? '',\n });\n })().catch((error: unknown) => {\n if (!controller.signal.aborted)\n setLoaded({ source: src, error: error instanceof Error ? error.message : String(error) });\n });\n return () => {\n controller.abort();\n if (url !== undefined) URL.revokeObjectURL(url);\n };\n }, [src]);\n const current = loaded?.source === src ? loaded : undefined;\n if (current?.url === undefined)\n return <p role={current?.error ? 'alert' : 'status'}>{current?.error ?? 'Loading media...'}</p>;\n return <MediaPreview src={current.url} path={path} contentType={current.contentType} data-testid={testId} />;\n}\n\n/** Resolves markdown images beside their document, leaving containment to the host. */\nexport async function loadMarkdownImage(sessionId: string | null, documentPath: string, path: string) {\n if (sessionId === null) throw new Error('The session is unavailable.');\n const directory = documentPath.slice(0, documentPath.lastIndexOf('/') + 1);\n const response = await sealedTransport.fetch(\n sessionFileUrl(sessionId, path.startsWith('/') ? path : `${directory}${path}`),\n { cache: 'no-store' },\n );\n if (!response.ok) throw new Error(`Image could not be loaded (${response.status}).`);\n const url = URL.createObjectURL(await response.blob());\n return { url, dispose: () => URL.revokeObjectURL(url) };\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Breadcrumb,\n Button,\n CodeEditor,\n type DiffSide,\n DiffView,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n type EditorSelectionRange,\n KebabIcon,\n Markdown,\n ReviewCommentDraft,\n reviewCommentAnchor,\n} from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport type { FileEditsVersionView } from '../../../../../types/fileEditsApi';\nimport { deleteFile, fetchFileDetail, saveFileContent, sessionFileUrl } from '../_lib/filesApi';\nimport {\n addComment,\n clearComments,\n files,\n markLoading,\n removeComment,\n storeDetail,\n storeError,\n} from '../_lib/filesStore';\nimport { fileTabId, previewModeOf, sendReviewFrame, TOOL_LABEL } from '../_lib/fileView';\nimport { DeleteFileDialog } from './DeleteFileDialog';\nimport { loadMarkdownImage, SessionMediaPreview as MediaPreview } from './SessionMediaPreview';\n\n/**\n * One file's tab: what this session did to it, and what the reader wants done\n * next.\n *\n * Three views over the same file. The diff answers \"what changed\", the history\n * below it answers \"in what order\", and edit is where a reader fixes a line\n * themselves rather than asking for it. Preview exists because a changed\n * README is read, not diffed, and because a changed PDF or screenshot has no\n * text to read at all.\n *\n * The tab is transient: it belongs to the session and the page, and closing it\n * is how it ends. Its cached detail lives in the session store, so reopening\n * the same file does not refetch a history the session already answered.\n */\n\n/**\n * `preview` and `diff` are the two ways of reading the file and sit in the\n * header. `edit` is a mode entered from the menu and left by picking either\n * reading view; the draft survives the trip, so leaving to check a diff does\n * not throw away what was typed.\n */\ntype View = 'preview' | 'diff' | 'edit';\n\ninterface FilePanelProps extends WebPluginSlotProps {\n filePath: string;\n relPath: string;\n}\n\ninterface Draft {\n snippet: string;\n /** Set by a diff selection; absent means the new side, as in the editor and preview. */\n side?: DiffSide;\n startLine?: number;\n endLine?: number;\n}\n\n/** How a version heads its row: what ran, when, and what it moved. */\nfunction versionSummary(version: FileEditsVersionView): string {\n const time = new Date(version.at).toLocaleTimeString();\n const counts = version.hunks === undefined ? '' : ` +${version.additions} -${version.removals}`;\n return `${time} · ${TOOL_LABEL[version.tool] ?? version.tool}${counts}`;\n}\n\nexport function FilePanel({ filePath, relPath, sessionId, sendSessionFrame, closeTransientTab }: FilePanelProps) {\n const session = useStore(files.store, (state) => files.select(state, sessionId));\n const detail = session.detail[filePath];\n const error = session.errors[filePath];\n const loading = session.loading.includes(filePath);\n const comments = session.comments.filter((comment) => comment.path === filePath);\n\n // Preview first: clicking a file is a request to look at the file, and the\n // diff is one question about it. The diff is one click away and the counts\n // beside the toggles already say whether there is one worth opening.\n const [view, setView] = useState<View>('preview');\n const [draft, setDraft] = useState<Draft | undefined>(undefined);\n const [source, setSource] = useState<string | undefined>(undefined);\n const [expanded, setExpanded] = useState<number | undefined>(undefined);\n const [saving, setSaving] = useState(false);\n const [saveNote, setSaveNote] = useState<string | undefined>(undefined);\n const [confirmingDelete, setConfirmingDelete] = useState(false);\n const [reviewError, setReviewError] = useState<string | undefined>(undefined);\n // The editor reports a range on every cursor move, and the comment box takes\n // focus when it opens. Holding the latest range here and raising the box on\n // mouse or key release keeps a drag from stealing focus halfway through it,\n // and a ref rather than state keeps a cursor move from re-rendering the tab.\n const selection = useRef<EditorSelectionRange | undefined>(undefined);\n\n // One fetch per open, and again whenever the session reports the file moved:\n // the count changing is the cheapest signal that the history grew.\n const revision = session.items.find((item) => item.path === filePath)?.count ?? 0;\n useEffect(() => {\n if (sessionId === null) return;\n let cancelled = false;\n markLoading(sessionId, filePath);\n void fetchFileDetail(sessionId, filePath).then((result) => {\n if (cancelled) return;\n if (result.ok) {\n storeDetail(sessionId, result.detail);\n setSource(undefined);\n setSaveNote(undefined);\n } else storeError(sessionId, filePath, result.error);\n });\n return () => {\n cancelled = true;\n };\n }, [sessionId, filePath, revision]);\n\n const working = detail?.working;\n const outside = relPath === '..' || relPath.startsWith('../') || relPath.startsWith('/');\n const content = source ?? working?.content ?? '';\n const previewMode = previewModeOf(relPath, working?.unavailable === true);\n // An image, a video or a PDF is shown rather than read as text, and it is\n // shown from the hub's own bytes route rather than from the detail payload,\n // which carries text only.\n const loadImage = useCallback((path: string) => loadMarkdownImage(sessionId, relPath, path), [sessionId, relPath]);\n const mediaSrc = sessionId === null ? '' : sessionFileUrl(sessionId, relPath);\n\n const openDraft = (selection: Draft): void => {\n setDraft(selection);\n };\n\n /** Raises the comment box on whatever the editor last reported, if that was a selection. */\n const openSelectionDraft = (): void => {\n const range = selection.current;\n if (range === undefined || range.text.trim() === '') return;\n openDraft({ snippet: range.text, startLine: range.startLine, endLine: range.endLine });\n };\n\n const submitDraft = (body: string): void => {\n if (sessionId === null || draft === undefined) return;\n addComment(sessionId, {\n // The anchor plus the note's position is unique within a file, and a\n // comment never outlives the session that holds it.\n id: `${filePath}:${draft.side ?? 'new'}:${draft.startLine ?? 0}:${comments.length}:${body.length}`,\n path: filePath,\n relPath,\n ...(draft.side === undefined ? {} : { side: draft.side }),\n ...(draft.startLine === undefined ? {} : { startLine: draft.startLine }),\n ...(draft.endLine === undefined ? {} : { endLine: draft.endLine }),\n snippet: draft.snippet,\n body,\n });\n setDraft(undefined);\n };\n\n const sendReview = (): void => {\n if (sessionId === null || comments.length === 0) return;\n // The comments go only once the frame has left; a disconnected session\n // keeps them so the reader can send again rather than retype them.\n const failure = sendReviewFrame((frame) => sendSessionFrame(sessionId, frame), comments);\n setReviewError(failure);\n if (failure === undefined) clearComments(sessionId, filePath);\n };\n\n const confirmDelete = async (): Promise<void> => {\n if (sessionId === null) return;\n setConfirmingDelete(false);\n const result = await deleteFile(sessionId, filePath);\n if (!result.ok) {\n storeError(sessionId, filePath, result.error);\n return;\n }\n // The file leaves the dock on the next channel frame, and this tab is now\n // a view of something that is not there, so it goes with it.\n closeTransientTab(fileTabId(filePath));\n };\n\n const save = async (): Promise<void> => {\n if (sessionId === null || working === undefined || source === undefined) return;\n setSaving(true);\n const result = await saveFileContent(sessionId, filePath, working.hash, source);\n setSaving(false);\n if (result.ok) {\n setSaveNote('saved');\n setSource(undefined);\n markLoading(sessionId, filePath);\n const refreshed = await fetchFileDetail(sessionId, filePath);\n if (refreshed.ok) storeDetail(sessionId, refreshed.detail);\n else storeError(sessionId, filePath, refreshed.error);\n return;\n }\n setSaveNote(result.error);\n };\n\n return (\n <div data-testid=\"files-file-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n <header className=\"flex shrink-0 items-center gap-2 border-b border-doom-border px-4 py-2\">\n <Breadcrumb path={relPath} data-testid=\"files-breadcrumb\" className=\"min-w-0 flex-1\" />\n {/* Edit is not one of the header's two toggles, so with neither lit the\n mode needs saying; picking either toggle leaves it. */}\n {view === 'edit' ? (\n <Badge tone=\"neutral\" data-testid=\"files-editing\" className=\"shrink-0 \">\n editing\n </Badge>\n ) : null}\n {detail === undefined ? null : (\n <span className=\"shrink-0 text-sm text-doom-dim\">\n +{detail.cumulative.additions} -{detail.cumulative.removals}\n </span>\n )}\n {/* The two ways of looking at the file sit in the header; the things\n that act on it live behind the kebab, so a destructive one is never\n a mis-click away from a view toggle. */}\n <nav className=\"flex shrink-0 items-center gap-1\">\n {(['preview', 'diff'] as const).map((entry) => (\n <Button\n key={entry}\n variant={view === entry ? 'outline' : 'ghost'}\n size=\"xs\"\n data-testid={`files-view-${entry}`}\n onClick={() => setView(entry)}\n className=\"\"\n >\n {entry}\n </Button>\n ))}\n </nav>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid=\"files-menu\"\n title=\"file actions\"\n className=\"shrink-0 text-doom-faint hover:text-doom-hi\"\n >\n <KebabIcon className=\"h-3 w-3\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent data-testid=\"files-menu-list\">\n <DropdownMenuItem\n data-testid=\"files-edit\"\n disabled={outside || working?.unavailable === true}\n onSelect={() => setView('edit')}\n >\n edit\n </DropdownMenuItem>\n <DropdownMenuItem\n variant=\"destructive\"\n data-testid=\"files-delete\"\n disabled={outside || working?.unavailable === true}\n onSelect={() => setConfirmingDelete(true)}\n >\n delete\n </DropdownMenuItem>\n <DropdownMenuItem data-testid=\"files-close\" onSelect={() => closeTransientTab(fileTabId(filePath))}>\n close\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </header>\n\n <DeleteFileDialog\n relPath={relPath}\n open={confirmingDelete}\n onConfirm={() => void confirmDelete()}\n onCancel={() => setConfirmingDelete(false)}\n />\n\n {loading && detail === undefined ? (\n <p data-testid=\"files-loading\" className=\"px-4 py-3 text-sm text-doom-dim\">\n reading this file's history…\n </p>\n ) : null}\n {error === undefined ? null : (\n <p data-testid=\"files-error\" className=\"px-4 py-3 text-sm text-doom-red\">\n {error}\n </p>\n )}\n\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n {detail === undefined ? null : view === 'diff' ? (\n <div className=\"flex flex-col\">\n {detail.cumulative.note === undefined ? null : (\n <p data-testid=\"files-cumulative-note\" className=\"px-4 py-2 text-sm text-doom-yellow\">\n {detail.cumulative.note}\n </p>\n )}\n {detail.cumulative.hunks === undefined ? null : (\n <DiffView\n hunks={detail.cumulative.hunks}\n testId=\"files-cumulative-diff\"\n onSelect={(selection) => openDraft(selection)}\n />\n )}\n <section className=\"mt-2 border-t border-doom-border\">\n <h2 className=\"px-4 py-2 text-lg font-semibold text-doom-hi \">\n {detail.versions.length} {detail.versions.length === 1 ? 'change' : 'changes'} this session\n </h2>\n {detail.versions.map((version) => (\n <article key={version.index} data-testid={`files-version-${version.index}`}>\n <Button\n variant=\"ghost\"\n size=\"card\"\n data-testid={`files-version-toggle-${version.index}`}\n onClick={() => setExpanded(expanded === version.index ? undefined : version.index)}\n className=\"w-full gap-2 rounded-none px-4 py-1.5 hover:bg-doom-panel\"\n >\n <span className=\"flex w-full min-w-0 items-center gap-2\">\n <span className=\"shrink-0 text-sm text-doom-dim\">#{version.index}</span>\n <span className=\"min-w-0 flex-1 truncate text-left text-base text-doom-text\">\n {versionSummary(version)}\n </span>\n {version.origin === 'scan' ? (\n <Badge tone=\"neutral\" className=\"shrink-0 \">\n no baseline\n </Badge>\n ) : null}\n <span className=\"shrink-0 text-sm text-doom-dim\">{expanded === version.index ? '▾' : '▸'}</span>\n </span>\n </Button>\n {expanded !== version.index ? null : version.hunks === undefined ? (\n <p className=\"px-4 py-1.5 text-sm text-doom-dim\">{version.note ?? 'nothing to show'}</p>\n ) : (\n <DiffView\n hunks={version.hunks}\n testId={`files-version-diff-${version.index}`}\n onSelect={(selection) => openDraft(selection)}\n />\n )}\n </article>\n ))}\n </section>\n </div>\n ) : view === 'edit' ? (\n <div className=\"flex h-full flex-col gap-2 p-3\">\n {working?.unavailable === true ? (\n <p data-testid=\"files-source-unavailable\" className=\"text-sm text-doom-dim\">\n {working.reason ?? 'this file cannot be shown'}\n </p>\n ) : (\n <>\n {/* The release, not the selection, is what raises the comment\n box: the editor reports a range continuously through a drag\n and the box takes focus when it opens. */}\n <div\n className=\"min-h-[24rem] flex-1\"\n onMouseUp={openSelectionDraft}\n onKeyUp={(event) => {\n if (event.shiftKey) openSelectionDraft();\n }}\n >\n <CodeEditor\n data-testid=\"files-source\"\n value={content}\n path={relPath}\n onChange={setSource}\n onSelect={(range) => {\n selection.current = range;\n }}\n className=\"h-full rounded border border-doom-border\"\n />\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button\n variant=\"outline\"\n size=\"xs\"\n data-testid=\"files-save\"\n loading={saving}\n disabled={source === undefined || saving}\n onClick={() => void save()}\n className=\"\"\n >\n save to disk\n </Button>\n {source === undefined ? null : (\n <Button\n variant=\"ghost\"\n size=\"xs\"\n data-testid=\"files-revert\"\n onClick={() => setSource(undefined)}\n className=\"\"\n >\n discard edits\n </Button>\n )}\n {saveNote === undefined ? null : (\n <span\n data-testid=\"files-save-note\"\n className={saveNote === 'saved' ? 'text-sm text-doom-green' : 'text-sm text-doom-red'}\n >\n {saveNote}\n </span>\n )}\n </div>\n </>\n )}\n </div>\n ) : (\n <div\n data-testid=\"files-preview\"\n data-mode={previewMode}\n className={\n previewMode === 'code'\n ? 'flex h-full flex-col gap-2 p-4 text-base text-doom-text'\n : 'flex flex-col gap-2 p-4 text-base text-doom-text'\n }\n onMouseUp={() => {\n // Highlighted source knows which lines were picked, so a note on\n // it is anchored. A rendered document has no honest mapping back\n // to source lines, and a note there carries its quotation and\n // says it has none.\n if (previewMode === 'code') {\n openSelectionDraft();\n return;\n }\n const text = globalThis.getSelection?.()?.toString() ?? '';\n if (text.trim() !== '') openDraft({ snippet: text });\n }}\n >\n {/* A file the editor cannot hold is still a file the reader came\n to see, so the reason is said and the bytes are shown anyway. */}\n {previewMode === 'unavailable' ? (\n <p data-testid=\"files-preview-unavailable\" className=\"text-sm text-doom-dim\">\n {working?.reason ?? 'this file cannot be shown'}\n </p>\n ) : null}\n {previewMode === 'media' || previewMode === 'unavailable' ? (\n <MediaPreview src={mediaSrc} path={relPath} data-testid=\"files-preview-media\" />\n ) : previewMode === 'markdown' ? (\n <Markdown text={content} loadImage={loadImage} />\n ) : previewMode === 'html' ? (\n <iframe\n data-testid=\"files-preview-html\"\n title={relPath}\n sandbox=\"\"\n srcDoc={content}\n className=\"h-[36rem] w-full rounded border border-doom-border bg-doom-deep\"\n />\n ) : previewMode === 'code' ? (\n // The same editor the edit view uses, held open. Reading code as\n // flat text is the one thing a cockpit should never make anyone\n // do, and read-only keeps the two views honestly apart.\n <CodeEditor\n data-testid=\"files-preview-code\"\n value={content}\n path={relPath}\n readOnly\n onSelect={(range) => {\n selection.current = range;\n }}\n className=\"min-h-[24rem] flex-1 rounded border border-doom-border\"\n />\n ) : (\n // Everything else is shown as it is written. Preview is the file\n // as it stands, so a file with nothing to render still has a\n // reading view rather than an empty pane.\n <pre\n data-testid=\"files-preview-text\"\n className=\"whitespace-pre-wrap break-words font-mono text-sm leading-normal text-doom-text\"\n >\n {content}\n </pre>\n )}\n </div>\n )}\n </div>\n\n {draft === undefined ? null : (\n <ReviewCommentDraft\n testIdPrefix=\"files-comment\"\n snippet={draft.snippet}\n {...(draft.side === undefined ? {} : { side: draft.side })}\n {...(draft.startLine === undefined ? {} : { startLine: draft.startLine })}\n {...(draft.endLine === undefined ? {} : { endLine: draft.endLine })}\n onSubmit={submitDraft}\n onCancel={() => setDraft(undefined)}\n />\n )}\n\n {comments.length === 0 ? null : (\n <footer data-testid=\"files-review\" className=\"shrink-0 border-t border-doom-border px-3 py-2\">\n <div className=\"flex items-center gap-2 pb-1\">\n <span className=\"flex-1 text-base font-semibold text-doom-hi\">\n {comments.length} {comments.length === 1 ? 'comment' : 'comments'}\n </span>\n <Button variant=\"outline\" size=\"xs\" data-testid=\"files-send-review\" onClick={sendReview} className=\"\">\n send review\n </Button>\n </div>\n {reviewError === undefined ? null : (\n <p role=\"alert\" data-testid=\"files-review-error\" className=\"pb-1 text-sm text-doom-red\">\n review not sent: {reviewError}\n </p>\n )}\n {comments.map((comment) => (\n <div key={comment.id} data-testid=\"files-review-comment\" className=\"flex items-start gap-2 py-0.5\">\n <span className=\"shrink-0 text-sm text-doom-dim\">{reviewCommentAnchor(comment)}</span>\n <span className=\"min-w-0 flex-1 truncate text-base text-doom-text\">{comment.body}</span>\n <Button\n variant=\"ghost\"\n size=\"xs\"\n data-testid=\"files-review-remove\"\n onClick={() => {\n if (sessionId !== null) removeComment(sessionId, comment.id);\n }}\n className=\"shrink-0 \"\n >\n remove\n </Button>\n </div>\n ))}\n </footer>\n )}\n </div>\n );\n}\n\n/** The transient tab one file opens in; the same file reopens rather than duplicating. */\nexport function fileTab(filePath: string, relPath: string): TransientTab {\n return {\n id: fileTabId(filePath),\n label: relPath.split('/').at(-1) ?? relPath,\n panel: (props) => <FilePanel {...props} filePath={filePath} relPath={relPath} />,\n };\n}\n","import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/po-core/web';\nimport { Breadcrumb, Button, CodeEditor, Markdown } from '@agimon-ai/po-web-components';\nimport { useCallback, useEffect, useState } from 'react';\n\nimport type { FileEditsPreviewView } from '../../../../../../types/fileEditsApi';\nimport { loadMarkdownImage, SessionMediaPreview as MediaPreview } from '../../_components/SessionMediaPreview';\nimport { fetchFilePreview, sessionFileUrl } from '../../_lib/filesApi';\nimport { fileTabId, previewModeOf } from '../../_lib/fileView';\n\n/**\n * A file the session never changed, opened read-only.\n *\n * The tab a changed file opens is built around its history: a diff, a version\n * list, an editor, a delete. None of that exists for a file the session only\n * read, and offering an edit view over a file this package holds no baseline\n * for would put a save behind a reader who cannot see what they are about to\n * overwrite. So this is one view, the file as it stands, and nothing else.\n *\n * Its content comes from the preview route, which is bounded by the session's\n * working directory rather than by the timeline.\n */\n\ninterface FilePreviewPanelProps extends WebPluginSlotProps {\n filePath: string;\n}\n\n/** What one fetch settled on, tagged with the file it was about. */\ninterface Loaded {\n sessionId: string;\n path: string;\n preview?: FileEditsPreviewView;\n error?: string;\n}\n\nexport function FilePreviewPanel({ filePath, sessionId, closeTransientTab }: FilePreviewPanelProps) {\n // The answer carries the path it answered for, so switching files is a state\n // that does not match rather than a reset the effect has to perform.\n const [loaded, setLoaded] = useState<Loaded | undefined>(undefined);\n\n useEffect(() => {\n if (sessionId === null) return;\n let cancelled = false;\n void fetchFilePreview(sessionId, filePath).then((result) => {\n if (cancelled) return;\n setLoaded(\n result.ok\n ? { sessionId, path: filePath, preview: result.preview }\n : { sessionId, path: filePath, error: result.error },\n );\n });\n return () => {\n cancelled = true;\n };\n }, [sessionId, filePath]);\n\n const current = loaded?.path === filePath && loaded.sessionId === sessionId ? loaded : undefined;\n const preview = current?.preview;\n const error = current?.error;\n // Before the answer arrives the path is all there is to name the file by,\n // and the route's own relative path replaces it once it does.\n const relPath = preview?.relPath ?? filePath;\n const working = preview?.working;\n const content = working?.content ?? '';\n const previewMode = previewModeOf(relPath, working?.unavailable === true);\n const loadImage = useCallback((path: string) => loadMarkdownImage(sessionId, relPath, path), [sessionId, relPath]);\n const mediaSrc = sessionId === null ? '' : sessionFileUrl(sessionId, relPath);\n\n return (\n <div data-testid=\"files-preview-panel\" className=\"flex min-h-0 flex-1 flex-col\">\n <header className=\"flex shrink-0 items-center gap-2 border-b border-doom-border px-4 py-2\">\n <Breadcrumb path={relPath} data-testid=\"files-preview-breadcrumb\" className=\"min-w-0 flex-1\" />\n <span className=\"shrink-0 text-sm text-doom-dim\">unchanged</span>\n <Button\n variant=\"ghost\"\n size=\"xs\"\n data-testid=\"files-preview-close\"\n onClick={() => closeTransientTab(filePreviewTabId(filePath))}\n className=\"shrink-0 \"\n >\n close\n </Button>\n </header>\n\n {error === undefined ? null : (\n <p data-testid=\"files-preview-error\" className=\"px-4 py-3 text-sm text-doom-red\">\n {error}\n </p>\n )}\n {preview === undefined && error === undefined ? (\n <p data-testid=\"files-preview-loading\" className=\"px-4 py-3 text-sm text-doom-dim\">\n reading this file…\n </p>\n ) : null}\n\n {preview === undefined ? null : (\n <div className=\"min-h-0 flex-1 overflow-y-auto\">\n <div\n data-testid=\"files-preview\"\n data-mode={previewMode}\n className=\"flex h-full flex-col gap-2 p-4 text-base text-doom-text\"\n >\n {previewMode === 'unavailable' ? (\n <p data-testid=\"files-preview-unavailable\" className=\"text-sm text-doom-dim\">\n {working?.reason ?? 'this file cannot be shown'}\n </p>\n ) : null}\n {previewMode === 'media' || previewMode === 'unavailable' ? (\n <MediaPreview src={mediaSrc} path={relPath} data-testid=\"files-preview-media\" />\n ) : previewMode === 'markdown' ? (\n <Markdown text={content} loadImage={loadImage} />\n ) : previewMode === 'html' ? (\n <iframe\n data-testid=\"files-preview-html\"\n title={relPath}\n sandbox=\"\"\n srcDoc={content}\n className=\"h-[36rem] w-full rounded border border-doom-border bg-doom-deep\"\n />\n ) : previewMode === 'code' ? (\n <CodeEditor\n data-testid=\"files-preview-code\"\n value={content}\n path={relPath}\n readOnly\n className=\"min-h-[24rem] flex-1 rounded border border-doom-border\"\n />\n ) : (\n <pre\n data-testid=\"files-preview-text\"\n className=\"whitespace-pre-wrap break-words font-mono text-sm leading-normal text-doom-text\"\n >\n {content}\n </pre>\n )}\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * The id of the read-only tab, kept apart from the changed-file tab's own id:\n * the same file can be read now and changed later, and the two tabs are not\n * the same view of it.\n */\nexport function filePreviewTabId(filePath: string): string {\n return `${fileTabId(filePath)}-preview`;\n}\n\n/** The transient tab an unchanged file opens in; the same file reopens rather than duplicating. */\nexport function filePreviewTab(filePath: string): TransientTab {\n return {\n id: filePreviewTabId(filePath),\n label: filePath.split('/').at(-1) ?? filePath,\n panel: (props) => <FilePreviewPanel {...props} filePath={filePath} />,\n };\n}\n","import type { FileLinkSource, TransientTab } from '@agimon-ai/po-core/web';\n\nimport type { FilesItemView } from '../../../../../../types/webFiles';\nimport { fileTab } from '../../_components/FilePanel';\nimport { files } from '../../_lib/filesStore';\nimport { filePreviewTab } from '../_components/FilePreviewPanel';\n\n/**\n * The files a message names, as links into the same tab the activity dock\n * opens.\n *\n * Only files this session recorded a change to are claimed. A message quotes a\n * great deal that is shaped like nothing in particular, class names and globs\n * and flags among it, and guessing at what is a path produces links that open\n * an error. The recorded set is the one thing the page knows for certain, and\n * it is also the set a reader is most likely to want open.\n */\n\n/** A reference the way a message writes one: 'src/app.ts:42' or 'src/app.ts:42:9'. */\nconst LINE_SUFFIX = /:\\d+(?::\\d+)?$/;\n\nfunction itemsFor(sessionId: string | null): readonly FilesItemView[] {\n return files.select(files.store.state, sessionId).items;\n}\n\nfunction match(sessionId: string | null, path: string): FilesItemView | undefined {\n const candidate = path.trim().replace(LINE_SUFFIX, '');\n if (candidate === '') return undefined;\n return itemsFor(sessionId).find((item) => item.relPath === candidate || item.path === candidate);\n}\n\nexport const fileLinks: FileLinkSource = {\n subscribe(listener: () => void) {\n const subscription = files.store.subscribe(listener);\n return () => subscription.unsubscribe();\n },\n // The paths themselves, not their count: a file that leaves the list while\n // another arrives changes what resolves without changing how many do.\n fingerprint: (sessionId) =>\n itemsFor(sessionId)\n .map((item) => item.relPath)\n .join('\\n'),\n resolve: (sessionId, path): TransientTab | undefined => {\n const item = match(sessionId, path);\n return item === undefined ? undefined : fileTab(item.path, item.relPath);\n },\n // A tool argument is a path on purpose, so nothing here has to be guessed.\n // A file the session changed opens on its history; one it only read opens\n // read-only, and the route behind that tab is what decides whether a path\n // outside the working directory may be shown at all.\n openPath: (sessionId, path): TransientTab | undefined => {\n const item = match(sessionId, path);\n if (item !== undefined) return fileTab(item.path, item.relPath);\n // A call with no path argument at all hands this an empty string, and\n // there is no file to open behind it.\n const candidate = path.trim();\n return candidate === '' ? undefined : filePreviewTab(candidate);\n },\n};\n","import { defineFileLinks } from '@agimon-ai/po-core/web';\n\nimport { fileLinks } from './_lib/fileLinks';\nexport default defineFileLinks(fileLinks);\n","import { Button, CloseIcon, groupByDirectory, groupRowLabel, Input } from '@agimon-ai/po-web-components';\nimport type { ChangeEvent, KeyboardEvent as ReactKeyboardEvent } from 'react';\nimport { Fragment, useEffect, useRef, useState } from 'react';\n\nimport type { FilesItemView } from '../../../../../../types/webFiles';\nimport { filterFileItems, TOOL_LABEL } from '../../_lib/fileView';\n\nfunction FileMetadata({ item, label, recent = false }: { item: FilesItemView; label?: string; recent?: boolean }) {\n return (\n <span className=\"flex min-w-0 w-full items-center gap-1.5\">\n {/* The dock passes no label and keeps the whole path; the drawer's tree\n passes what its group header has not already said. */}\n <span className=\"min-w-0 flex-1 truncate text-left text-base font-semibold text-doom-hi\">\n {label ?? item.relPath}\n </span>\n {recent ? (\n <span data-recent=\"true\" title=\"most recent change\" className=\"shrink-0 text-2xs text-doom-blue\">\n ●\n </span>\n ) : null}\n {item.count > 1 ? <span className=\"shrink-0 text-2xs text-doom-faint\">{item.count}×</span> : null}\n <span className={`shrink-0 text-sm ${item.diffable ? 'text-doom-dim' : 'text-doom-yellow'}`}>\n {TOOL_LABEL[item.tool] ?? item.tool}\n </span>\n </span>\n );\n}\n\nexport function FileActivityRow({ item, onOpen }: { item: FilesItemView; onOpen: () => void }) {\n return (\n <Button\n variant=\"ghost\"\n size=\"card\"\n data-testid={`activity-file-${item.relPath}`}\n data-file-diffable={item.diffable}\n title={item.diffable ? item.path : `${item.path} (changed by a command, so no diff was captured)`}\n onClick={onOpen}\n className=\"min-w-0 gap-0.5 rounded-md px-1 py-1 hover:bg-doom-panel\"\n >\n <FileMetadata item={item} />\n </Button>\n );\n}\n\n/**\n * The directory a run of rows shares.\n *\n * Deliberately not an option: the cursor counts files, and a header is a label\n * rather than something to open. Keeping it out of the listbox is what lets\n * arrow keys and Enter stay exactly what they were before the tree existed.\n */\nfunction FileGroupHeader({ prefix }: { prefix: string }) {\n return (\n <div\n data-testid={`files-browser-group-${prefix}`}\n className=\"truncate px-4 pt-2 pb-1 text-sm font-semibold text-doom-dim\"\n >\n {prefix}\n </div>\n );\n}\n\nfunction BrowserFileRow({\n item,\n label,\n indented = false,\n recent = false,\n selected,\n onSelect,\n onOpen,\n}: {\n item: FilesItemView;\n label: string;\n indented?: boolean;\n recent?: boolean;\n selected: boolean;\n onSelect: () => void;\n onOpen: () => void;\n}) {\n return (\n <div\n role=\"option\"\n aria-selected={selected}\n tabIndex={-1}\n data-testid={`files-browser-file-${item.relPath}`}\n data-file-diffable={item.diffable}\n data-selected={selected}\n title={item.diffable ? item.path : `${item.path} (changed by a command, so no diff was captured)`}\n onMouseEnter={onSelect}\n onClick={onOpen}\n className={`flex min-w-0 cursor-pointer border-l-2 py-2 ${indented ? 'pr-4 pl-8' : 'px-4'} ${\n selected ? 'border-doom-blue bg-doom-panel' : 'border-transparent hover:bg-doom-panel/60'\n }`}\n >\n <FileMetadata item={item} label={label} recent={recent} />\n </div>\n );\n}\n\n/** The complete session file picker, ordered exactly as the file timeline supplies it. */\nexport function FilesBrowser({\n items,\n onClose,\n onOpen,\n}: {\n items: readonly FilesItemView[];\n onClose: () => void;\n onOpen: (item: FilesItemView) => void;\n}) {\n const [query, setQuery] = useState('');\n const [selectedIndex, setSelectedIndex] = useState(0);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const shown = filterFileItems(items, query);\n // Grouping only earns its headers with the whole list in view. While a query\n // is active the reader is pinpointing one path, so the flat list answers that\n // better than a shape wrapped around two matches. Groups are ordered by path,\n // not recency, so a header's files sit together under it.\n const grouped = query.trim() === '' ? groupByDirectory(shown, (item) => item.relPath) : undefined;\n // The order rows actually appear in, which is what the cursor counts through.\n const ordered = grouped === undefined ? shown : grouped.flatMap((group) => group.items);\n const positions = new Map(ordered.map((item, index) => [item.path, index]));\n const selected = ordered[selectedIndex];\n // The timeline hands the newest change first, so the head of the unfiltered\n // list is the one row worth marking once the tree has reordered by path.\n const recentPath = items[0]?.path;\n\n // A shrinking filter can strand the cursor past the end, so the clamp happens\n // while rendering the shorter list rather than in a pass after it paints.\n const [lastShownLength, setLastShownLength] = useState(shown.length);\n if (lastShownLength !== shown.length) {\n setLastShownLength(shown.length);\n setSelectedIndex((current) => Math.min(current, Math.max(0, shown.length - 1)));\n }\n\n useEffect(() => {\n listRef.current?.querySelector('[aria-selected=\"true\"]')?.scrollIntoView({ block: 'nearest' });\n }, [selectedIndex]);\n\n const move = (delta: number): void => {\n if (ordered.length === 0) return;\n setSelectedIndex((current) => (current + delta + ordered.length) % ordered.length);\n };\n\n const onKeyDown = (event: ReactKeyboardEvent<HTMLElement>): void => {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n move(event.key === 'ArrowDown' ? 1 : -1);\n return;\n }\n if (event.key === 'Escape') {\n event.preventDefault();\n onClose();\n return;\n }\n if (\n event.key === 'Enter' &&\n selected !== undefined &&\n (!(event.target instanceof Element) || event.target.closest('button') === null)\n ) {\n event.preventDefault();\n onOpen(selected);\n }\n };\n\n return (\n <aside\n data-testid=\"files-browser\"\n aria-label=\"changed files\"\n tabIndex={-1}\n onKeyDown={onKeyDown}\n className=\"fixed inset-y-0 right-0 z-50 flex w-[min(440px,calc(100vw-24px))] flex-col overflow-hidden border-l border-doom-border bg-doom-rail outline-none\"\n >\n <div className=\"flex h-11 shrink-0 items-center gap-2.5 border-b border-doom-border px-4\">\n <span className=\"text-lg font-semibold text-doom-hi\"># files</span>\n <span data-testid=\"files-browser-total\" className=\"text-sm text-doom-dim\">\n {items.length} changed\n </span>\n <span className=\"min-w-0 flex-1\" />\n <Button\n variant=\"ghost\"\n size=\"icon\"\n data-testid=\"files-browser-close\"\n aria-label=\"close changed files\"\n title=\"close changed files\"\n onClick={onClose}\n >\n <CloseIcon className=\"h-3 w-3\" />\n </Button>\n </div>\n <div className=\"flex items-center gap-2 px-4 pt-2.5 pb-1.5\">\n <Input\n ref={inputRef}\n data-testid=\"files-browser-search\"\n aria-label=\"search changed file paths\"\n value={query}\n placeholder=\"search paths…\"\n autoFocus\n onChange={(event: ChangeEvent<HTMLInputElement>) => {\n setQuery(event.target.value);\n setSelectedIndex(0);\n }}\n />\n {query.length > 0 ? (\n <Button\n variant=\"ghost\"\n size=\"xs\"\n data-testid=\"files-browser-clear\"\n aria-label=\"clear path search\"\n onClick={() => {\n setQuery('');\n setSelectedIndex(0);\n inputRef.current?.focus();\n }}\n >\n clear\n </Button>\n ) : null}\n </div>\n <p data-testid=\"files-browser-matches\" className=\"shrink-0 px-4 pb-2 text-sm text-doom-dim\">\n {shown.length} matches · {grouped === undefined ? 'newest change first' : 'by path'}\n </p>\n <div\n ref={listRef}\n role=\"listbox\"\n aria-label=\"changed files\"\n className=\"flex min-h-0 flex-1 flex-col overflow-y-auto pb-2\"\n >\n {shown.length === 0 ? <p className=\"px-4 py-3 text-sm text-doom-dim\">nothing matches the path</p> : null}\n {grouped === undefined\n ? shown.map((item) => (\n <BrowserFileRow\n key={item.path}\n item={item}\n label={item.relPath}\n recent={item.path === recentPath}\n selected={positions.get(item.path) === selectedIndex}\n onSelect={() => setSelectedIndex(positions.get(item.path) ?? 0)}\n onOpen={() => onOpen(item)}\n />\n ))\n : grouped.map((group) => (\n <Fragment key={group.prefix}>\n {group.prefix === '' ? null : <FileGroupHeader prefix={group.prefix} />}\n {group.items.map((item) => (\n <BrowserFileRow\n key={item.path}\n item={item}\n label={groupRowLabel(group.prefix, item.relPath)}\n indented\n recent={item.path === recentPath}\n selected={positions.get(item.path) === selectedIndex}\n onSelect={() => setSelectedIndex(positions.get(item.path) ?? 0)}\n onOpen={() => onOpen(item)}\n />\n ))}\n </Fragment>\n ))}\n </div>\n <div className=\"flex h-8 shrink-0 items-center border-t border-doom-border-soft bg-doom-deep px-4\">\n <span className=\"text-sm text-doom-dim\">↑↓ choose · enter open · esc close</span>\n </div>\n </aside>\n );\n}\n","import type { WebPluginSlotProps } from '@agimon-ai/po-core/web';\nimport { Button } from '@agimon-ai/po-web-components';\nimport { useStore } from '@tanstack/react-store';\nimport { useState } from 'react';\n\nimport type { FilesItemView } from '../../../../../../types/webFiles';\nimport { fileTab } from '../../_components/FilePanel';\nimport { files } from '../../_lib/filesStore';\nimport { FileActivityRow, FilesBrowser } from './FilesBrowser';\n\n/**\n * The files group's body in the activity dock: what this session has changed.\n *\n * Every recorded file is listed, not only the ones the agent named. A file\n * found by comparing the tree around a bash call is marked, because it is\n * openable and editable but has no baseline to diff against, and a row that\n * hid that distinction would promise a diff the tab cannot show.\n */\n\n/** The most recent changes first, which is the order the timeline already folds to. */\nconst VISIBLE_ROWS = 5;\n\nexport function FilesActivitySection({ sessionId, openTransientTab }: WebPluginSlotProps) {\n const { items } = useStore(files.store, (state) => files.select(state, sessionId));\n const [browserOpen, setBrowserOpen] = useState(false);\n\n const openFile = (item: FilesItemView): void => {\n if (sessionId === null) return;\n openTransientTab(fileTab(item.path, item.relPath));\n setBrowserOpen(false);\n };\n if (items.length === 0) {\n return (\n <p data-testid=\"activity-summary-files\" className=\"px-1 text-sm text-doom-dim\">\n nothing changed yet\n </p>\n );\n }\n\n const visible = items.slice(0, VISIBLE_ROWS);\n\n return (\n <div data-testid=\"activity-file-edits\" className=\"flex flex-col gap-0.5\">\n {visible.map((item: FilesItemView) => (\n <FileActivityRow key={item.path} item={item} onOpen={() => openFile(item)} />\n ))}\n {items.length > visible.length ? (\n <Button\n variant=\"link\"\n size=\"xs\"\n data-testid=\"activity-files-show-all\"\n aria-label={`show all ${items.length} changed files`}\n className=\"self-start px-1 pt-0.5 text-doom-faint\"\n onClick={() => setBrowserOpen(true)}\n >\n show all {items.length} files\n </Button>\n ) : null}\n {browserOpen ? <FilesBrowser items={items} onClose={() => setBrowserOpen(false)} onOpen={openFile} /> : null}\n </div>\n );\n}\n","import { defineFill } from '@agimon-ai/po-core/web';\n\nimport { FilesActivitySection } from './_components/FilesActivitySection';\nexport default defineFill({ component: FilesActivitySection });\n","import { defineLeaderBinding } from '@agimon-ai/po-core/web';\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config' };\nexport default defineLeaderBinding({\n path: [EXTENSION_GROUP, { key: 'f', label: 'files', detail: 'files this session changed' }],\n command: 'file-edits',\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/po-core/web';\n\nimport activityGroupFiles from '../src/extensions/workspaces/sessions/(frontend)/activity-group/files.web';\nimport channelFileEdits from '../src/extensions/workspaces/sessions/(frontend)/channel/file-edits.web';\nimport fileLinksFiles from '../src/extensions/workspaces/sessions/(frontend)/file-links/files.web';\nimport fillFiles from '../src/extensions/workspaces/sessions/(frontend)/fill/Files.activity.files.web';\nimport leaderFiles from '../src/extensions/workspaces/sessions/(frontend)/leader/files.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'files',\n session: {\n get activityGroups(): WebPluginContributions['activityGroups'] { return [via({ name: 'files' }, activityGroupFiles)]; },\n get channels(): WebPluginContributions['channels'] { return [via({ channel: 'file_edits' }, channelFileEdits)]; },\n get fileLinks(): WebPluginContributions['fileLinks'] { return fileLinksFiles; },\n get fills(): WebPluginContributions['fills'] { return [via({ slot: 'activity.files', id: 'files' }, fillFiles)]; },\n get leaderBindings(): WebPluginContributions['leaderBindings'] { return [via({ id: 'files.files' }, leaderFiles)]; },\n },\n});\n"],"mappings":"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"}
package/dist/index.cjs ADDED
File without changes
@@ -0,0 +1,91 @@
1
+ //#region src/types/domain.d.ts
2
+ export type FileEditTool = 'edit' | 'write' | 'bash' | 'user';
3
+ export type FileEditState = 'modified' | 'added' | 'deleted' | 'unchanged' | 'binary' | 'external';
4
+ /**
5
+ * How a change was discovered, which is what decides whether it can be diffed.
6
+ *
7
+ * A `tool` change names its path in the call arguments, so the content is read
8
+ * before the tool runs and both sides of the change are known. A `scan` change
9
+ * is found by comparing a tree manifest taken around a bash call: the path is
10
+ * only known afterwards, so there is no "before" to diff against.
11
+ */
12
+ export type FileEditOrigin = 'tool' | 'scan';
13
+ /**
14
+ * One recorded change, appended to the session's timeline.
15
+ *
16
+ * Version 1 events carried the path alone. Version 2 adds the blob hashes that
17
+ * make a diff possible; both are still read, because a session already running
18
+ * when the package updates keeps appending to the file it opened.
19
+ */
20
+ export interface TimelineEvent {
21
+ version: 2;
22
+ path: string;
23
+ tool: FileEditTool;
24
+ at: number;
25
+ origin: FileEditOrigin;
26
+ /** Snapshot hash of the content before the change; absent for a scan-found path. */
27
+ before?: string;
28
+ /** Snapshot hash of the content after the change; absent when it could not be stored. */
29
+ after?: string;
30
+ additions?: number;
31
+ removals?: number;
32
+ /**
33
+ * Set on a scan change whose content was proven to have moved, rather than
34
+ * only its modification time. Absent on a scan recorded by an older build,
35
+ * which could not tell an edit from a file a command merely touched.
36
+ */
37
+ verified?: boolean;
38
+ /**
39
+ * Set on a tool change whose file did not exist when the call began.
40
+ *
41
+ * A missing `before` is ambiguous on its own: the file may have been created,
42
+ * or it may have existed and been too large or too binary to capture. Only
43
+ * the first has a diff, and it is the whole file against nothing, so the two
44
+ * are told apart here rather than guessed at by a reader.
45
+ */
46
+ created?: boolean;
47
+ }
48
+ /** A version 1 event, still accepted from a timeline opened by an older build. */
49
+ export interface LegacyTimelineEvent {
50
+ version: 1;
51
+ path: string;
52
+ tool: 'edit' | 'write' | 'bash';
53
+ at: number;
54
+ }
55
+ export interface FileEditEntry {
56
+ path: string;
57
+ tool: FileEditTool;
58
+ at: number;
59
+ count: number;
60
+ }
61
+ /** One change in a file's history, as the cockpit lists it. */
62
+ export interface FileEditVersion {
63
+ /** Position in the file's own history, oldest first, starting at 1. */
64
+ index: number;
65
+ tool: FileEditTool;
66
+ at: number;
67
+ origin: FileEditOrigin;
68
+ before?: string;
69
+ after?: string;
70
+ additions?: number;
71
+ removals?: number;
72
+ verified?: boolean;
73
+ /** Set when the file did not exist before this change, so its baseline is empty. */
74
+ created?: boolean;
75
+ }
76
+ export interface FileDiff {
77
+ path: string;
78
+ state: FileEditState;
79
+ lines: string[];
80
+ additions: number;
81
+ removals: number;
82
+ tracked: boolean;
83
+ truncated: boolean;
84
+ suggestedLine: number;
85
+ }
86
+ export interface ResolvedEditor {
87
+ template: string;
88
+ source: 'configured' | 'VISUAL' | 'EDITOR' | 'fallback';
89
+ }
90
+ //#endregion
91
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/domain.ts"],"mappings":";YAAY;YACA;;;;;;;;;YAUA;;;;;;;;iBASK;EACf;EACA;EACA,MAAM;EACN;EACA,QAAQ;;EAER;;EAEA;EACA;EACA;;;;;;EAMA;;;;;;;;;EASA;;;iBAIe;EACf;EACA;EACA;EACA;;iBAGe;EACf;EACA,MAAM;EACN;EACA;;;iBAIe;;EAEf;EACA,MAAM;EACN;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;;EAEA;;iBAGe;EACf;EACA,OAAO;EACP;EACA;EACA;EACA;EACA;EACA;;iBAGe;EACf;EACA"}
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,129 @@
1
+ {
2
+ "name": "@agimon-ai/po-file-edit",
3
+ "version": "0.0.0-alpha.0",
4
+ "description": "Native Po session-scoped file-change timelines and review APIs.",
5
+ "keywords": [
6
+ "ai",
7
+ "coding-agent",
8
+ "developer-tools",
9
+ "external-editor",
10
+ "file-editing",
11
+ "pi-coding-agent",
12
+ "pi-extension",
13
+ "pi-package",
14
+ "po",
15
+ "session-timeline"
16
+ ],
17
+ "homepage": "https://agimon.ai",
18
+ "bugs": {
19
+ "url": "https://github.com/AgiFlow/po/issues"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Vuong Ngo",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/AgiFlow/po.git",
26
+ "directory": "packages/default/po-file-edit"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "LICENSE"
31
+ ],
32
+ "type": "module",
33
+ "main": "./dist/index.cjs",
34
+ "module": "./dist/index.mjs",
35
+ "types": "./dist/index.d.mts",
36
+ "jsnext:main": "./dist/index.mjs",
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/index.d.mts",
40
+ "import": "./dist/index.mjs",
41
+ "require": "./dist/index.cjs"
42
+ },
43
+ "./apiContracts": {
44
+ "types": "./dist/apiContracts.d.mts",
45
+ "import": "./dist/apiContracts.mjs",
46
+ "require": "./dist/apiContracts.cjs"
47
+ },
48
+ "./extensions/server": {
49
+ "types": "./dist/extensions/server.d.mts",
50
+ "import": "./dist/extensions/server.mjs",
51
+ "require": "./dist/extensions/server.cjs"
52
+ },
53
+ "./extensions/web": {
54
+ "import": "./dist/extensions/web.mjs"
55
+ },
56
+ "./package.json": "./package.json"
57
+ },
58
+ "publishConfig": {
59
+ "access": "public",
60
+ "registry": "https://registry.npmjs.org/"
61
+ },
62
+ "dependencies": {
63
+ "@agimon-ai/po-config": "0.0.0-alpha.0",
64
+ "@agimon-ai/po-core": "0.0.0-alpha.0",
65
+ "@agimon-ai/po-telemetry": "0.0.0-alpha.0",
66
+ "@agimon-ai/po-web-components": "0.0.0-alpha.0",
67
+ "@agimon-ai/po-web-security": "0.0.0-alpha.0",
68
+ "@deepseek-ai/cordis": "4.0.4",
69
+ "hono": "4.13.12",
70
+ "ignore": "7.0.11",
71
+ "typebox": "1.3.34"
72
+ },
73
+ "devDependencies": {
74
+ "@agimon-ai/po-build": "0.0.0-alpha.0",
75
+ "@earendil-works/pi-coding-agent": "1.0.2",
76
+ "@tanstack/react-store": "0.11.2",
77
+ "@tanstack/store": "0.11.2",
78
+ "@types/node": "26.6.4",
79
+ "@types/react": "19.3.0",
80
+ "@types/react-dom": "19.3.0",
81
+ "@vitest/coverage-v8": "5.0.3",
82
+ "react": "19.3.0",
83
+ "react-dom": "19.3.0",
84
+ "tsdown": "0.23.0",
85
+ "typescript": "7.0.2",
86
+ "vitest": "5.0.3"
87
+ },
88
+ "peerDependencies": {
89
+ "@earendil-works/pi-coding-agent": "1.0.2"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@earendil-works/pi-coding-agent": {
93
+ "optional": true
94
+ }
95
+ },
96
+ "engines": {
97
+ "node": ">=24"
98
+ },
99
+ "poServer": {
100
+ "entry": "./generated/server.ts",
101
+ "dist": "./dist/extensions/server.mjs",
102
+ "scopes": [
103
+ "global",
104
+ "workspace",
105
+ "session"
106
+ ],
107
+ "contracts": {
108
+ "entry": "./src/exports/apiContracts.ts",
109
+ "dist": "./dist/apiContracts.mjs"
110
+ }
111
+ },
112
+ "poWeb": {
113
+ "pluginId": "files",
114
+ "channels": [
115
+ "file_edits"
116
+ ],
117
+ "client": "./dist/extensions/web.mjs",
118
+ "scopes": [
119
+ "session"
120
+ ]
121
+ },
122
+ "scripts": {
123
+ "build": "tsdown",
124
+ "test": "vitest --run",
125
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
126
+ "lint": "oxlint . && oxfmt . --check",
127
+ "fixcode": "oxlint . --fix && oxfmt ."
128
+ }
129
+ }