{"version":3,"file":"server.cjs","names":["defineApiRoutes","apiResponse","createHash","fs","path","lineDiffFromEmpty","lineDiff","baselineOf","TimelineStore","NodeSnapshotStoreAdapter","FileEditPaths","Hono","routes","fs","confirmedChanges","parseTimeline","foldEntries","path","isDiffable","foldVersions","filterDoomIgnoredFiles","filesChannelType","FileEditPaths","createDoomIgnoreMatcher","fs","path","FileEditPaths","TimelineStore","NodeSnapshotStoreAdapter","EditTracker","NodeTreeManifestAdapter","NodeGitStatusAdapter","filesChannelType","filesStatusKey","defineRoot","defineChannel","defineRoute","defineHook","defineHook","defineServerPlugin","channelFileEdits","rootSession","composeRootHooks","apiRoute","hookToolExecutionEnd","hookToolExecutionStart"],"sources":["../../src/types/apiRoutes.ts","../../src/types/fileEditsApi.ts","../../src/services/fileEditsApi/index.ts","../../src/services/webFilesChannel/index.ts","../../src/services/fileEditSession/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/serverRoot.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/(backend)/channel/fileEdits.server.ts","../../src/extensions/workspaces/sessions/(backend)/api/file-edits/route.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server.ts","../../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server.ts","../../generated/server.ts"],"sourcesContent":["import { apiResponse, defineApiRoutes } from '@agimon-ai/doompi-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","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 { createHash } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { DoomApi, DoomApiContext, DoomApiHandler } from '@agimon-ai/doompi-core/packageApi';\nimport { Hono } from 'hono';\n\nimport { baselineOf } from '../../services/fileChanges';\nimport { FileEditPaths } from '../../services/fileEditPaths';\nimport { lineDiff, lineDiffFromEmpty } from '../../services/lineDiff';\nimport { NodeSnapshotStoreAdapter } from '../../services/snapshotStore';\nimport { TimelineStore } from '../../services/timelineStore';\nimport routes from '../../types/apiRoutes';\nimport type { FileEditVersion } from '../../types/domain';\nimport type { IFileEditPaths } from '../../types/fileEditPaths';\nimport {\n  API_BASE_PATH,\n  type FileEditsCumulativeView,\n  type FileEditsDetailView,\n  type FileEditsPreviewView,\n  type FileEditsSaveRequest,\n  type FileEditsSaveView,\n  type FileEditsVersionView,\n  type FileEditsWorkingView,\n  PATH_QUERY_PARAM,\n} from '../../types/fileEditsApi';\nimport type { SnapshotStorePort } from '../../types/snapshotStore';\nimport type { ITimelineStore } from '../../types/timelineStore';\n\n/**\n * This package's HTTP surface: one file's history, and the manual save.\n *\n * Routes are relative to the mount its host gives it, so nothing here repeats\n * where it was mounted. The host is one session's own server, which is what\n * makes the session id and working directory available without the page\n * supplying either: a page names a file, never a session's private paths.\n *\n * The detail route answers everything a reader needs to open a changed file,\n * because splitting it would make the page stitch three responses together for\n * one click. The save route is the only mutation, and it refuses unless the\n * reader proves they were looking at the content that is still on disk. The\n * preview route is the read-only way in to a file the session never changed,\n * bounded by the working directory instead of by the timeline.\n */\n\n/** Past this, the working view reports the file rather than carrying it. */\nexport const MAX_WORKING_BYTES = 1024 * 1024;\n\nconst BINARY_SAMPLE_BYTES = 8192;\nconst NO_BASELINE_NOTE = 'changed by a command, so no baseline was captured; showing the file as it stands';\n/**\n * A change the tool layer named but never captured content for. That is what\n * every record written before this package learned to snapshot looks like, and\n * calling those \"changed by a command\" would be wrong: the tool is right there\n * in the record.\n */\nconst NO_SNAPSHOT_NOTE = 'recorded before this session began capturing content, so there is no diff for it';\nconst NO_AFTER_NOTE = 'the new content could not be captured: the file is binary or past the size cap';\nconst NO_BEFORE_NOTE = 'the previous content could not be captured: it was binary or past the size cap';\nconst APPROXIMATE_NOTE = 'the changed region was too large to match line by line';\n\nexport interface FileEditsApiOptions {\n  /** The session these routes answer for; absent when the host is the hub. */\n  sessionId?: string;\n  /** The session's working directory, which relative paths are shown against. */\n  cwd?: string;\n  timeline?: ITimelineStore;\n  snapshots?: SnapshotStorePort;\n  paths?: IFileEditPaths;\n}\n\nfunction hashOf(content: string): string {\n  return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n\nfunction hasCode(error: unknown, code: string): boolean {\n  return error instanceof Error && 'code' in error && error.code === code;\n}\n\nfunction describe(error: unknown): string {\n  return error instanceof Error ? error.message : String(error);\n}\n\n/** Reads the file as the source view will show it, or says why it cannot. */\nasync function readWorking(filePath: string): Promise<FileEditsWorkingView> {\n  let raw: Buffer;\n  try {\n    raw = await fs.readFile(filePath);\n  } catch {\n    return { content: '', hash: '', unavailable: true, reason: 'the file no longer exists' };\n  }\n  if (raw.byteLength > MAX_WORKING_BYTES) {\n    return { content: '', hash: '', unavailable: true, reason: `the file is larger than ${MAX_WORKING_BYTES} bytes` };\n  }\n  if (raw.subarray(0, BINARY_SAMPLE_BYTES).includes(0)) {\n    return { content: '', hash: '', unavailable: true, reason: 'the file is binary' };\n  }\n  const content = raw.toString('utf8');\n  return { content, hash: hashOf(content), unavailable: false };\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n  return candidate === root || candidate.startsWith(`${root}${path.sep}`);\n}\n\n/**\n * The same containment check after symlinks are followed. A path that does not\n * exist yet resolves to nothing, and there is nothing to hand out, so it\n * passes and the read below reports it missing.\n */\nasync function isRealPathInside(root: string, candidate: string): Promise<boolean> {\n  let realRoot: string;\n  let real: string;\n  try {\n    realRoot = await fs.realpath(root);\n    real = await fs.realpath(candidate);\n  } catch {\n    return true;\n  }\n  return isInside(realRoot, real);\n}\n\n/**\n * One recorded change as the page renders it. A change with both sides captured\n * carries its own diff; one without says why, rather than showing nothing.\n *\n * A file this session created counts as captured on both sides. Its baseline is\n * empty rather than missing, so the whole file reads as arriving at once, which\n * is exactly what happened.\n */\nasync function toVersionView(version: FileEditVersion, snapshots: SnapshotStorePort): Promise<FileEditsVersionView> {\n  const view: FileEditsVersionView = {\n    index: version.index,\n    tool: version.tool,\n    at: version.at,\n    origin: version.origin,\n    additions: version.additions ?? 0,\n    removals: version.removals ?? 0,\n  };\n  const createdHere = version.before === undefined && version.created === true;\n  if (version.after === undefined || (version.before === undefined && !createdHere)) {\n    // A scan found the path only after it changed, so no baseline exists. A\n    // tool named the path but content was not stored: either it did not fit, or\n    // the record predates content capture, which the tool tells apart.\n    if (version.origin === 'tool') {\n      return { ...view, note: version.after === undefined ? NO_AFTER_NOTE : NO_BEFORE_NOTE };\n    }\n    return { ...view, note: version.tool === 'bash' ? NO_BASELINE_NOTE : NO_SNAPSHOT_NOTE };\n  }\n  const after = await snapshots.read(version.after);\n  if (after === undefined) {\n    return { ...view, note: 'the content behind this change is no longer held' };\n  }\n  if (version.before === undefined) {\n    const fresh = lineDiffFromEmpty(after);\n    return {\n      ...view,\n      additions: fresh.additions,\n      removals: fresh.removals,\n      hunks: fresh.hunks,\n      ...(fresh.approximate ? { note: APPROXIMATE_NOTE } : {}),\n    };\n  }\n  const before = await snapshots.read(version.before);\n  if (before === undefined) {\n    return { ...view, note: 'the content behind this change is no longer held' };\n  }\n  const diff = lineDiff(before, after);\n  return {\n    ...view,\n    additions: diff.additions,\n    removals: diff.removals,\n    hunks: diff.hunks,\n    ...(diff.approximate ? { note: APPROXIMATE_NOTE } : {}),\n  };\n}\n\n/** Everything the session did to the file: its oldest baseline against what is on disk now. */\nasync function toCumulativeView(\n  versions: readonly FileEditVersion[],\n  working: FileEditsWorkingView,\n  snapshots: SnapshotStorePort,\n): Promise<FileEditsCumulativeView> {\n  // A file this session created starts from nothing, and nothing is a baseline\n  // the snapshot store never has to hold.\n  const createdHere = versions[0]?.created === true;\n  const baseline = baselineOf(versions);\n  if (!createdHere && baseline === undefined) {\n    return {\n      additions: 0,\n      removals: 0,\n      note: versions[0]?.origin === 'tool' ? NO_BEFORE_NOTE : NO_BASELINE_NOTE,\n    };\n  }\n  if (working.unavailable) {\n    return { additions: 0, removals: 0, note: working.reason ?? 'the file cannot be read' };\n  }\n  if (createdHere || baseline === undefined) {\n    const fresh = lineDiffFromEmpty(working.content);\n    return {\n      additions: fresh.additions,\n      removals: fresh.removals,\n      hunks: fresh.hunks,\n      ...(fresh.approximate ? { note: APPROXIMATE_NOTE } : {}),\n    };\n  }\n  const before = await snapshots.read(baseline);\n  if (before === undefined) {\n    return { additions: 0, removals: 0, note: 'the content this session started from is no longer held' };\n  }\n  const diff = lineDiff(before, working.content);\n  return {\n    additions: diff.additions,\n    removals: diff.removals,\n    hunks: diff.hunks,\n    ...(diff.approximate ? { note: APPROXIMATE_NOTE } : {}),\n  };\n}\n\nexport function createFileEditsApi(options: FileEditsApiOptions = {}): Hono {\n  const timeline = options.timeline ?? new TimelineStore();\n  const snapshots = options.snapshots ?? new NodeSnapshotStoreAdapter();\n  const paths = options.paths ?? new FileEditPaths();\n  const cwd = options.cwd ?? '';\n  const app = new Hono();\n\n  // The routes and the extension run in different processes, so this half\n  // opens the same two locations from the same inputs rather than sharing an\n  // instance with the half that writes them.\n  //\n  // Only what this function built gets pointed at them. A store handed in\n  // through the options arrives already configured, and repointing it would\n  // make the option a lie: the caller would be injecting a store the routes\n  // then read somewhere else entirely.\n  if (options.sessionId !== undefined && cwd !== '') {\n    const sessionKey = paths.sessionKey(options.sessionId);\n    if (options.timeline === undefined) timeline.initialize(paths.timelinePath(cwd, sessionKey));\n    if (options.snapshots === undefined) snapshots.initialize(paths.snapshotsPath(cwd, sessionKey));\n  }\n\n  /**\n   * The file a request names, rejected unless this session actually recorded a\n   * change to it. That check is the authorization: these routes read and write\n   * whatever path they are handed, so the timeline is what bounds them to files\n   * the session already touched.\n   */\n  const recordedVersions = async (filePath: string | undefined): Promise<FileEditVersion[] | undefined> => {\n    if (filePath === undefined || filePath === '') return undefined;\n    const versions = await timeline.versions(path.resolve(cwd, filePath));\n    return versions.length === 0 ? undefined : versions;\n  };\n\n  app.get(routes.detail.path, async (context) => {\n    const requested = context.req.query(PATH_QUERY_PARAM);\n    const versions = await recordedVersions(requested);\n    if (versions === undefined || requested === undefined) {\n      return context.json({ error: 'This session recorded no changes to that file.' }, 404);\n    }\n    const filePath = path.resolve(cwd, requested);\n    const working = await readWorking(filePath);\n    const body: FileEditsDetailView = {\n      path: filePath,\n      relPath: cwd === '' ? filePath : path.relative(cwd, filePath),\n      versions: await Promise.all(versions.map((version) => toVersionView(version, snapshots))),\n      cumulative: await toCumulativeView(versions, working, snapshots),\n      working,\n    };\n    return context.json(body);\n  });\n\n  /**\n   * The file a request names, read only, for a file the timeline knows nothing\n   * about: a path the agent read but never changed is still a path the reader\n   * clicked, and refusing it would leave the link dead.\n   *\n   * The working directory is the boundary here, since the timeline is not.\n   * Containment is checked lexically and again on the real path, so a symlink\n   * inside the tree cannot hand out something beyond it, and a host that gave\n   * these routes no working directory has no boundary to enforce and so serves\n   * nothing.\n   */\n  app.get(routes.preview.path, async (context) => {\n    const requested = context.req.query(PATH_QUERY_PARAM);\n    if (requested === undefined || requested === '' || requested.includes('\\0')) {\n      return context.json({ error: 'A preview names a path.' }, 400);\n    }\n    if (cwd === '') return context.json({ error: 'This session has no working directory to read from.' }, 403);\n    const root = path.resolve(cwd);\n    const filePath = path.resolve(root, requested);\n    if (!isInside(root, filePath) || !(await isRealPathInside(root, filePath))) {\n      return context.json({ error: 'The path leaves the session directory.' }, 403);\n    }\n    const body: FileEditsPreviewView = {\n      path: filePath,\n      relPath: path.relative(root, filePath),\n      working: await readWorking(filePath),\n    };\n    return context.json(body);\n  });\n\n  app.put(routes.save.path, async (context) => {\n    let request: FileEditsSaveRequest;\n    try {\n      request = (await context.req.json()) as FileEditsSaveRequest;\n    } catch {\n      return context.json({ error: 'The save body is not JSON.' }, 400);\n    }\n    if (typeof request.path !== 'string' || typeof request.content !== 'string') {\n      return context.json({ error: 'A save names a path and the content to write.' }, 400);\n    }\n    if ((await recordedVersions(request.path)) === undefined) {\n      return context.json({ error: 'This session recorded no changes to that file.' }, 404);\n    }\n    const filePath = path.resolve(cwd, request.path);\n    const current = await readWorking(filePath);\n    if (current.unavailable) {\n      return context.json({ error: current.reason ?? 'The file cannot be read.' }, 409);\n    }\n    // The agent runs in the same working directory, so it can rewrite the file\n    // while a reader is still editing it. Refusing on a moved hash is what\n    // stops the save from silently discarding whatever it did.\n    if (current.hash !== request.expectedHash) {\n      return context.json({ error: 'The file changed since it was opened.', hash: current.hash }, 409);\n    }\n    await fs.writeFile(filePath, request.content, 'utf8');\n    const body: FileEditsSaveView = { hash: hashOf(request.content) };\n    return context.json(body);\n  });\n\n  app.delete(routes.remove.path, async (context) => {\n    const requested = context.req.query(PATH_QUERY_PARAM);\n    if ((await recordedVersions(requested)) === undefined || requested === undefined) {\n      return context.json({ error: 'This session recorded no changes to that file.' }, 404);\n    }\n    const filePath = path.resolve(cwd, requested);\n    try {\n      await fs.unlink(filePath);\n    } catch (error) {\n      // Already gone is the outcome the caller wanted, so it is not a failure.\n      if (!hasCode(error, 'ENOENT')) {\n        return context.json({ error: `The file could not be deleted: ${describe(error)}` }, 409);\n      }\n    }\n    return context.body(null, 204);\n  });\n\n  return app;\n}\n\n/** The named export a host imports from this package's built entry. */\nexport const api: DoomApi = {\n  basePath: API_BASE_PATH,\n  start(context: DoomApiContext): DoomApiHandler {\n    const app = createFileEditsApi({\n      ...(context.sessionId === undefined ? {} : { sessionId: context.sessionId }),\n      ...(context.cwd === undefined ? {} : { cwd: context.cwd }),\n    });\n    return {\n      fetch: (request) => app.fetch(request),\n      // Nothing outlives a request here: every route reads, answers and closes.\n      close: () => undefined,\n    };\n  },\n};\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { DoomHubChannel, DoomHubChannelSource } from '@agimon-ai/doompi-core/hubChannel';\n\nimport { filterDoomIgnoredFiles } from '../../services/doomIgnore';\nimport { confirmedChanges, foldEntries, foldVersions, isDiffable, parseTimeline } from '../../services/fileChanges';\nimport { FileEditPaths } from '../../services/fileEditPaths';\nimport { filesChannelType, type FilesItemView } from '../../types/webFiles';\n\n/**\n * The changed-files channel consumes snapshots emitted by the owning session\n * facet. Durable timeline reads seed a channel that starts after session startup.\n */\n\n/** Whether the file is still there to be opened. */\nfunction stillExists(filePath: string): boolean {\n  try {\n    return fs.statSync(filePath).isFile();\n  } catch {\n    return false;\n  }\n}\n\n/**\n * Reads one session's timeline and presents it as the rows the dock lists.\n *\n * A file the session changed and then removed is left out, and so is a path a\n * bash call only touched: a command that moves a modification time without\n * changing a byte is not an edit, and listing it buries the ones that are. The\n * dock is a list of things to open, and a row that can only ever answer \"this\n * is gone\" is a dead end; the timeline still holds the change, so a tab already\n * open on the file keeps working and says so.\n */\nexport function readSessionFiles(timelinePath: string, cwd: string): FilesItemView[] {\n  let content: string;\n  try {\n    content = fs.readFileSync(timelinePath, 'utf8');\n  } catch {\n    return [];\n  }\n  const events = confirmedChanges(parseTimeline(content));\n  const items = foldEntries(events)\n    .filter((entry) => stillExists(entry.path))\n    .map((entry) => ({\n      path: entry.path,\n      relPath: path.relative(cwd, entry.path) || entry.path,\n      tool: entry.tool,\n      at: entry.at,\n      count: entry.count,\n      diffable: isDiffable(foldVersions(events, entry.path)),\n    }));\n\n  try {\n    const doomIgnore = fs.readFileSync(path.join(cwd, '.doomignore'), 'utf8');\n    return filterDoomIgnoredFiles(items, doomIgnore);\n  } catch {\n    return items;\n  }\n}\n\nfunction filesPayload(value: unknown): value is { items: FilesItemView[] } {\n  return typeof value === 'object' && value !== null && Array.isArray((value as { items?: unknown }).items);\n}\n\n/**\n * The files data channel: one direct-event subscription per managed session,\n * published as { items } payloads under the 'file_edits' frame type.\n */\nexport function createFilesChannel(): DoomHubChannel {\n  return {\n    frameType: filesChannelType,\n    start(host) {\n      const latest = new Map<string, FilesItemView[]>();\n      const subscriptions = new Map<string, () => void>();\n      const paths = new FileEditPaths();\n      const channelSource: DoomHubChannelSource = {\n        payloadFor(scope) {\n          const items = latest.get(scope.sessionId);\n          return items === undefined ? undefined : { items };\n        },\n        sessionAdded(scope) {\n          subscriptions.get(scope.sessionId)?.();\n          let timelinePath: string | undefined;\n          try {\n            timelinePath = paths.timelinePath(scope.cwd, paths.sessionKey(scope.sessionId));\n          } catch {\n            // The session has no readable state path yet.\n          }\n          const items = timelinePath === undefined ? [] : readSessionFiles(timelinePath, scope.cwd);\n          latest.set(scope.sessionId, items);\n          host.publish(scope.sessionId, { items });\n          const unsubscribe = host.directEvents.subscribe(\n            filesChannelType,\n            scope.sessionId,\n            (payload) => {\n              if (!filesPayload(payload)) return;\n              latest.set(scope.sessionId, payload.items);\n              host.publish(scope.sessionId, payload);\n            },\n            { replayLatest: true },\n          );\n          subscriptions.set(scope.sessionId, unsubscribe);\n        },\n        sessionRemoved(sessionId) {\n          subscriptions.get(sessionId)?.();\n          subscriptions.delete(sessionId);\n          latest.delete(sessionId);\n        },\n        close() {\n          for (const unsubscribe of subscriptions.values()) unsubscribe();\n          subscriptions.clear();\n          latest.clear();\n        },\n      };\n      return channelSource;\n    },\n  };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n  type DoomHeadlessExecutionContext,\n  type DoomHeadlessActivity,\n  type DoomHeadlessHook,\n} from '@agimon-ai/doompi-core/headless';\nimport type { DoomServerPluginContext, DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\n\nimport { createDoomIgnoreMatcher } from '../../services/doomIgnore';\nimport { EditTracker } from '../../services/editTracker';\nimport { FileEditPaths } from '../../services/fileEditPaths';\nimport { NodeGitStatusAdapter } from '../../services/gitStatus';\nimport { NodeSnapshotStoreAdapter } from '../../services/snapshotStore';\nimport { TimelineStore } from '../../services/timelineStore';\nimport { NodeTreeManifestAdapter } from '../../services/treeManifest';\nimport { filesChannelType, filesStatusKey } from '../../types/webFiles';\nimport { api } from '../fileEditsApi';\nimport { readSessionFiles } from '../webFilesChannel';\n\nconst DOOM_IGNORE_FILE = '.doomignore';\n\nfunction readDoomIgnore(cwd: string): ((filePath: string) => boolean) | undefined {\n  try {\n    const matcher = createDoomIgnoreMatcher(fs.readFileSync(path.join(cwd, DOOM_IGNORE_FILE), 'utf8'));\n    return matcher === undefined ? undefined : (filePath: string) => matcher(path.relative(cwd, filePath));\n  } catch {\n    return undefined;\n  }\n}\n\nfunction stringValue(value: Readonly<Record<string, unknown>>, key: string): string | undefined {\n  const candidate = value[key];\n  return typeof candidate === 'string' && candidate.length > 0 ? candidate : undefined;\n}\n\nexport function createFileEditSession({ agent, host }: DoomServerPluginContext): DoomServerSessionPlugin {\n  if (!agent) return { api: [api] };\n  const paths = new FileEditPaths();\n  const timeline = new TimelineStore();\n  const snapshots = new NodeSnapshotStoreAdapter();\n  const editTracker = new EditTracker(timeline, snapshots, new NodeTreeManifestAdapter(), {\n    git: new NodeGitStatusAdapter(),\n  });\n  if (host.context.directEvents === undefined)\n    throw new Error('File-edit headless facet requires the session direct event bus.');\n  const directEvents = host.context.directEvents;\n  let active:\n    | {\n        readonly context: DoomHeadlessExecutionContext;\n        readonly timelinePath: string;\n        readonly snapshotsPath: string;\n      }\n    | undefined;\n  /**\n   * The channel carries the rows; the status decides whether the group exists.\n   *\n   * A cockpit drives this facet and never the Pi runtime, so without the status\n   * the dock had the file list on the wire and no frame to draw it in. The empty\n   * string is deliberate: the group declares `hideWhenEmpty`, so a session that\n   * has edited nothing publishes a key with no content and stays hidden.\n   */\n  const publish = (runtime: NonNullable<typeof active>): void => {\n    const items = readSessionFiles(runtime.timelinePath, runtime.context.cwd);\n    directEvents.publish(filesChannelType, runtime.context.sessionId, { items });\n    runtime.context.client.setStatus(\n      filesStatusKey,\n      items.length === 0 ? '' : `${items.length} ${items.length === 1 ? 'file' : 'files'}`,\n    );\n  };\n  const activity: DoomHeadlessActivity = {\n    name: 'file-edits',\n    start: async (executionContext) => {\n      const sessionKey = paths.sessionKey(executionContext.sessionId);\n      const timelinePath = paths.timelinePath(executionContext.cwd, sessionKey);\n      const snapshotsPath = paths.snapshotsPath(executionContext.cwd, sessionKey);\n      timeline.initialize(timelinePath);\n      snapshots.initialize(snapshotsPath);\n      const isIgnored = readDoomIgnore(executionContext.cwd);\n      editTracker.reset({\n        exclude: [timelinePath, `${timelinePath}.lock`, snapshotsPath],\n        ...(isIgnored === undefined ? {} : { isIgnored }),\n      });\n      const runtime = { context: executionContext, timelinePath, snapshotsPath };\n      active = runtime;\n      publish(runtime);\n      return async () => {\n        if (active !== runtime) return;\n        active = undefined;\n        executionContext.client.setStatus(filesStatusKey, undefined);\n        editTracker.reset();\n      };\n    },\n  };\n  const startHook: DoomHeadlessHook = {\n    event: 'tool_execution_start',\n    handle: async (event, executionContext) => {\n      const runtime = active;\n      const toolCallId = stringValue(event, 'toolCallId');\n      const toolName = stringValue(event, 'toolName');\n      if (runtime === undefined || runtime.context.sessionId !== executionContext.sessionId || !toolCallId || !toolName)\n        return;\n      await editTracker.start(toolCallId, toolName, event.args, executionContext.cwd);\n    },\n  };\n  const endHook: DoomHeadlessHook = {\n    event: 'tool_execution_end',\n    handle: async (event, executionContext) => {\n      const runtime = active;\n      const toolCallId = stringValue(event, 'toolCallId');\n      if (runtime === undefined || runtime.context.sessionId !== executionContext.sessionId || !toolCallId) return;\n      await editTracker.end(toolCallId, event.isError === true, executionContext.cwd);\n      if (active === runtime) publish(runtime);\n    },\n  };\n  return { api: [api], activities: [activity], hooks: [startHook, endHook] };\n}\n","import type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport { createFileEditSession } from '../../../../../services/fileEditSession';\nexport const createFileEditServerRoot = (context: DoomServerPluginContext) => {\n  const runtime = createFileEditSession(context);\n  return { value: runtime, activities: runtime.activities };\n};\nexport type FileEditServerScope = Awaited<ReturnType<typeof createFileEditServerRoot>>['value'];\n","import { defineRoot } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { createFileEditServerRoot } from './_lib/serverRoot';\n\nexport default defineRoot(createFileEditServerRoot);\n","import { defineChannel } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomHubChannel } from '@agimon-ai/doompi-core/hubChannel';\n\nimport { createFilesChannel } from '../../../services/webFilesChannel';\nexport default defineChannel(function fileEditsChannel(): DoomHubChannel {\n  return createFilesChannel();\n});\n","import { defineRoute } from '@agimon-ai/doompi-core/extensionFile';\n\nimport { api } from '../../../../../../services/fileEditsApi';\n\n/**\n * Mounts this package's HTTP API at `file-edits`, which the folder names.\n *\n * Until this file existed the API was never mounted at all. The session root\n * returns a runtime carrying `api`, but a root's `api` is not folded into the\n * generated entry the way its services and activities are, so the registration\n * was dropped in silence and the cockpit called routes that answered 404.\n */\nexport default defineRoute(api);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { FileEditServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, FileEditServerScope>) => context.root.hooks![1]!);\n","import { defineHook, type WithRoot } from '@agimon-ai/doompi-core/extensionFile';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/serverFacet';\n\nimport type { FileEditServerScope } from '../_lib/serverRoot';\nexport default defineHook((context: WithRoot<DoomServerPluginContext, FileEditServerScope>) => context.root.hooks![0]!);\n","// Generated by @agimon-ai/doompi-build. Do not edit by hand.\nimport { defineServerPlugin, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/serverFacet';\nimport { composeRootHooks } from '@agimon-ai/doompi-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport channelFileEdits from '../src/extensions/(backend)/channel/fileEdits.server';\nimport apiRoute from '../src/extensions/workspaces/sessions/(backend)/api/file-edits/route.server';\nimport hookToolExecutionEnd from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-end.server';\nimport hookToolExecutionStart from '../src/extensions/workspaces/sessions/(backend)/hook/tool-execution-start.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n  typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n  (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const facet = defineServerPlugin({\n  name: '@agimon-ai/doompi-file-edit',\n  global: () => ({\n    get channels(): DoomServerSessionPlugin['channels'] { return [() => via({ frameType: 'file_edits' }, channelFileEdits())]; },\n  }),\n  workspace: () => ({\n    get channels(): DoomServerSessionPlugin['channels'] { return [() => via({ frameType: 'file_edits' }, channelFileEdits())]; },\n  }),\n  session: async (context) => {\n    const scopeSession = await rootSession(context);\n    const contextSession = { ...context, root: scopeSession.value };\n    return {\n      ...composeRootHooks(scopeSession),\n      get api(): DoomServerSessionPlugin['api'] { return [at(apiRoute, contextSession)]; },\n      get channels(): DoomServerSessionPlugin['channels'] { return [() => via({ frameType: 'file_edits' }, channelFileEdits())]; },\n      get hooks(): DoomServerSessionPlugin['hooks'] { return [via({ event: 'tool_execution_end' }, at(hookToolExecutionEnd, contextSession)), via({ event: 'tool_execution_start' }, at(hookToolExecutionStart, contextSession))]; },\n      services: [...(scopeSession.services ?? [])],\n      get activities(): DoomServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n    };\n  },\n});\n\nexport default facet;\n"],"mappings":"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"}