Преглед изворни кода

fix(lsp): address codex review round 1

Lifecycle and safety fixes from the external review:
- Observe abort while awaiting the initialize handshake, so a server that never
  replies can't defeat the tool-timeout signal.
- On an aborted request the server won't cancel, tear the instance down after a
  bounded grace instead of releasing the serialized queue with work still live
  (prevents overlapping document lifecycles).
- Re-check provider disposal after the canonicalize/read awaits so a query can't
  spawn an unowned server after disposeAll().
- Read the source through one open handle (stat + read on the same fd) to close
  the realpath-vs-read TOCTOU; decode with a fatal UTF-8 decoder so a legitimate
  U+FFFD is not misclassified as invalid.
- Validate and read the source BEFORE spawning a server (pre-start rejection).
- Require an explicit openClose for option-form textDocumentSync.
- Reject nonpositive teardown budgets and non-executable absolute commands at
  load; surface unsupported operations as structured LSP_UNSUPPORTED_OPERATION.
- Retain the stderr tail (fatal diagnostics land at exit), not the prefix.
- Catalog the seam vocabulary in docs/core-data-structures/lsp.md.
Dudu-0223 пре 1 месец
родитељ
комит
8e8f90e235

+ 1 - 0
docs/core-data-structures/core.md

@@ -28,6 +28,7 @@ Everything else is documented on a **sub-page**, not here. The rule that draws t
 | [sandbox.md](sandbox.md) | the process-confinement seam: file-effect modes, `SandboxPolicy`, `ConfinedArgv`, enforcement and fail-closed errors |
 | [code-runtime.md](code-runtime.md) | the code-execution seam: `CodeRunRequest`/`Result`, binding namespaces, captured logs, the `CodeRunFailure` taxonomy |
 | [filesystem.md](filesystem.md) | the filesystem seam: `FsTarget`, read/write/edit outcomes, observed-file state, `FsErrorCode` |
+| [lsp.md](lsp.md) | the LSP navigation seam: `LspQueryRequest`/`Result`, `LspProvider`/`Service`, four operations, `LspError` |
 | [skills.md](skills.md) | the skill service: discovery priority, `SkillSummary`/`SkillDefinition`, session-prefix catalog, model-facing `skill` loading |
 | [compaction.md](compaction.md) | the compaction seam: the `compact/*` session events, `CompactionResult`, the `CompactService` interface |
 | [subagent.md](subagent.md) | the subagent seam: the named-provider registry, `SubagentStartRequest`/`Result`/`Run`, the start-time-vs-runtime capability split |

+ 124 - 0
docs/core-data-structures/lsp.md

@@ -0,0 +1,124 @@
+# LSP navigation
+
+The LSP seam — a [capability seam](../rfc/implemented/architecture/2026-07-15-lsp-capability-seam.md) exposing semantic code navigation on one `ctx.lsp` service, split across packages: interface ([dsh-lsp](../../packages/lsp/lsp), `ctx.lsp` + the provider registry), a generic implementation ([dsh-lsp-local](../../packages/lsp/lsp-local), a configured stdio language-server host), and consumer ([dsh-tool-lsp](../../packages/lsp/tool-lsp), the `lsp` tool schema). LSP is **one optional capability**, not part of the agent-loop spine — so its vocabulary lives here, not in [core.md](core.md). A provider swap does not change how the model asks for navigation.
+
+Source: [`packages/lsp/lsp/src/types.ts`](../../packages/lsp/lsp/src/types.ts)
+
+## Operations and coordinates
+
+The seam and model expose exactly four semantic queries; the union is closed, so adding one is a compile-enforced change across the seam, providers, and the tool. Positions and ranges are zero-based UTF-16, matching the protocol; the model-facing tool owns the one-based cursor convention and converts on the way in and out.
+
+```ts type-equiv
+type LspOperation = 'definition' | 'references' | 'implementation' | 'hover'
+```
+
+```ts type-equiv
+interface LspPosition {
+  /** Zero-based line. */
+  readonly line: number
+  /** Zero-based UTF-16 code-unit offset within the line. */
+  readonly character: number
+}
+```
+
+```ts type-equiv
+interface LspRange {
+  readonly start: LspPosition
+  readonly end: LspPosition
+}
+```
+
+## Request
+
+Every field is required: `workspaceRoot` is caller-supplied, `languageId` comes from the provider's registration (not the request), and consumers own timeouts and result limits — so no field needs implementation defaulting and there is no `resolve()` step. The provider receives the caller's request plus the derived `languageId`, which only synchronizes the transient document and never participates in selection.
+
+```ts type-equiv
+interface LspQueryRequest {
+  /** Which semantic query to run. */
+  readonly operation: LspOperation
+  /** The source file to query (relative to `workspaceRoot` or absolute; the provider canonicalizes). */
+  readonly filePath: string
+  /** The zero-based UTF-16 cursor position to query at. */
+  readonly position: LspPosition
+  /** The workspace root the provider resolves against and indexes; required, never defaulted. */
+  readonly workspaceRoot: string
+}
+```
+
+```ts type-equiv
+interface LspProviderQuery extends LspQueryRequest {
+  /** The LSP language id for `filePath`, from this provider's extension mapping. */
+  readonly languageId: string
+}
+```
+
+## Result
+
+A CLOSED discriminated union: navigation operations normalize to `locations`, `hover` to content or `null`. Consumers `switch` on `kind` to exhaustiveness so a new arm breaks compilation until handled. `references` always includes declarations — the provider enforces this internally, so callers get no flag.
+
+```ts type-equiv
+interface LspLocation {
+  /** The target document URI (`file:` or otherwise), verbatim from the server. */
+  readonly uri: string
+  /** The range within the target document. */
+  readonly range: LspRange
+}
+```
+
+```ts type-equiv
+interface LspHover {
+  /** The normalized hover text (markdown or plaintext, provider-joined). */
+  readonly contents: string
+  /** The range the hover applies to, when the server supplied one. */
+  readonly range?: LspRange
+}
+```
+
+```ts type-equiv
+type LspQueryResult =
+  | { readonly kind: 'locations'; readonly locations: readonly LspLocation[] }
+  | { readonly kind: 'hover'; readonly hover: LspHover | null }
+```
+
+## Provider and service
+
+A provider owns a stable branded `id` and an exclusive lowercase leading-dot extension map. `registerProvider` reserves the id and every extension atomically — an invalid or conflicting registration publishes nothing — and its disposer releases all reservations. Selection is per query and order-independent; no match throws `LspError` `LSP_UNAVAILABLE`. The seam exposes no protocol types, process/document controls, or generic JSON-RPC escape hatch.
+
+```ts type-equiv
+interface LspProvider {
+  /** Stable provider identity, reserved atomically with the extension mappings. */
+  readonly id: LspProviderId
+  /** Lowercase leading-dot extension → LSP language id (e.g. `{ '.ts': 'typescript' }`). */
+  readonly extensionToLanguage: Readonly<Record<string, string>>
+  /**
+   * Run one query. The seam has already selected this provider and derived `languageId`.
+   * @param request - the resolved provider query (caller request + derived language id).
+   * @param signal - optional cancellation; the provider stops its own work when it aborts.
+   * @returns the normalized, closed-union result.
+   */
+  query(request: LspProviderQuery, signal?: AbortSignal): Promise<LspQueryResult>
+}
+```
+
+```ts type-equiv
+interface LspService {
+  /**
+   * Register a provider, atomically reserving its id and every normalized extension. Any conflict
+   * or invalid input publishes nothing and throws `LspError`; the returned disposer releases all
+   * reservations. Disposed with the calling fiber.
+   * @param provider - the backend to register.
+   * @returns a synchronous disposer releasing the id and all extension reservations.
+   */
+  registerProvider(provider: LspProvider): () => void
+  /**
+   * Select a provider by the file's extension and run one query. Selection is per-query and
+   * order-independent; no match throws `LspError` `LSP_UNAVAILABLE`.
+   * @param request - the normalized query.
+   * @param signal - optional cancellation forwarded to the selected provider.
+   * @returns the normalized, closed-union result.
+   */
+  query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>
+}
+```
+
+`LspProviderId` is the seam's branded id (`Branded<'LspProviderId'>` from [dsh-brand](../../packages/util/brand)); `LspError` extends `HarnessError` with a stable `code` (`LSP_INVALID_PROVIDER`, `LSP_CONFLICT`, `LSP_UNAVAILABLE`, `LSP_UNSUPPORTED_OPERATION`) callers route on instead of parsing `message`.

+ 3 - 2
packages/lsp/lsp-local/src/connection.ts

@@ -174,8 +174,9 @@ export class LspConnection {
   }
 
   private onStderr(chunk: Buffer): void {
-    if (this.stderr.length >= this.spec.maxStderrBytes) return
-    this.stderr = (this.stderr + chunk.toString('utf8')).slice(0, this.spec.maxStderrBytes)
+    // Retain the TAIL, not the prefix: a language server's fatal diagnostic usually appears just
+    // before it exits, so the final bounded segment is the useful one.
+    this.stderr = (this.stderr + chunk.toString('utf8')).slice(-this.spec.maxStderrBytes)
   }
 
   private dispatch(message: unknown): void {

+ 22 - 14
packages/lsp/lsp-local/src/host.ts

@@ -9,7 +9,7 @@
  * @module @deepseek-ai/dsh-lsp-local/host
  */
 
-import { readFile, realpath, stat } from 'node:fs/promises'
+import { open, realpath, stat } from 'node:fs/promises'
 import { isAbsolute, resolve as resolvePath, sep } from 'node:path'
 
 /** A validated source: its canonical absolute path and current UTF-8 text. */
@@ -68,16 +68,24 @@ export async function readHostSource(
   if (!isInside(canonicalWorkspace, canonicalPath)) {
     throw new Error(`source "${filePath}" resolves outside the workspace`)
   }
-  const info = await stat(canonicalPath)
-  if (!info.isFile()) {
-    throw new Error(`source "${filePath}" is not a regular file`)
-  }
-  if (info.size > maxDocumentBytes) {
-    throw new Error(`source "${filePath}" is ${info.size} bytes, over the ${maxDocumentBytes}-byte limit`)
+  // Open ONE handle after containment, then stat and read through it: a concurrent replace between
+  // realpath and read cannot swap the target, so the regular-file and size checks bind the bytes we
+  // actually read (no path-based TOCTOU).
+  const handle = await open(canonicalPath, 'r')
+  try {
+    const info = await handle.stat()
+    if (!info.isFile()) {
+      throw new Error(`source "${filePath}" is not a regular file`)
+    }
+    if (info.size > maxDocumentBytes) {
+      throw new Error(`source "${filePath}" is ${info.size} bytes, over the ${maxDocumentBytes}-byte limit`)
+    }
+    const buffer = await handle.readFile()
+    const text = decodeUtf8Strict(buffer, filePath)
+    return { canonicalPath, text }
+  } finally {
+    await handle.close()
   }
-  const buffer = await readFile(canonicalPath)
-  const text = decodeUtf8Strict(buffer, filePath)
-  return { canonicalPath, text }
 }
 
 /** Whether `child` is the workspace itself or a descendant of it (both already canonical). */
@@ -88,13 +96,13 @@ function isInside(workspace: string, child: string): boolean {
   return child.startsWith(base)
 }
 
-/** Decode UTF-8 strictly (a replacement char means the source was not valid UTF-8 text). */
+/** Decode strictly as UTF-8: a fatal decoder rejects only malformed bytes, keeping a legitimate U+FFFD. */
 function decodeUtf8Strict(buffer: Buffer, filePath: string): string {
-  const text = buffer.toString('utf8')
-  if (text.includes('�')) {
+  try {
+    return new TextDecoder('utf-8', { fatal: true }).decode(buffer)
+  } catch {
     throw new Error(`source "${filePath}" is not valid UTF-8 text`)
   }
-  return text
 }
 
 /** Extract a message from an unknown thrown value without leaking `any`. */

+ 32 - 5
packages/lsp/lsp-local/src/index.ts

@@ -23,7 +23,7 @@ import type {
 } from '@deepseek-ai/dsh-lsp'
 // Side-effect type import: declaration-merges `ctx.lsp` onto Context.
 import type {} from '@deepseek-ai/dsh-lsp'
-import { canonicalizeWorkspace } from './host.ts'
+import { canonicalizeWorkspace, readHostSource } from './host.ts'
 import { LspInstance } from './instance.ts'
 import type { InstanceSpec } from './instance.ts'
 
@@ -110,6 +110,10 @@ export const Config: z<Config> = z.object({
  */
 export function apply(ctx: Context, config: Config): void {
   const resolved = config as ResolvedConfig
+  // Teardown budgets feed `deadline()`, whose `<= 0` is the internal no-timeout sentinel; a
+  // nonpositive value would let a server that ignores shutdown hang disposal forever. Fail at load.
+  assertPositiveInteger('shutdownTimeoutMs', resolved.shutdownTimeoutMs)
+  assertPositiveInteger('killGraceMs', resolved.killGraceMs)
   const childEnv = buildChildEnv(resolved.env)
   // Resolve the executable eagerly so a misconfigured command fails at load, not on first query.
   const executable = resolveExecutable(resolved.command, childEnv)
@@ -124,6 +128,13 @@ export function apply(ctx: Context, config: Config): void {
   }, 'lsp-local.registerProvider')
 }
 
+/** Reject a nonpositive or non-integer config value at load, so misconfiguration fails loud. */
+function assertPositiveInteger(name: string, value: number): void {
+  if (!Number.isInteger(value) || value < 1) {
+    throw new Error(`lsp-local: ${name} must be a positive integer`)
+  }
+}
+
 /** A pooled generic provider: one server process per canonical workspace, created on demand. */
 class LocalLspProvider implements LspProvider {
   readonly id: LspProviderId
@@ -141,13 +152,26 @@ class LocalLspProvider implements LspProvider {
     this.extensionToLanguage = config.extensionToLanguage
   }
 
+  /** Read the disposed flag through a method so a `query()` await cannot narrow it to a literal. */
+  private isDisposed(): boolean {
+    return this.disposed
+  }
+
   async query(request: LspProviderQuery, signal?: AbortSignal): Promise<LspQueryResult> {
-    /* v8 ignore next -- the seam unregisters this provider on dispose, so a query never reaches a disposed provider; defensive. */
-    if (this.disposed) throw new Error('lsp-local provider is disposed')
+    /* v8 ignore next -- the seam unregisters this provider on dispose, so a query never reaches it disposed; defensive. */
+    if (this.isDisposed()) throw new Error('lsp-local provider is disposed')
     const workspace = await canonicalizeWorkspace(request.workspaceRoot)
+    // Validate and read the source BEFORE spawning a server: a missing/external/non-regular/oversized
+    // source must fail without leaving an idle process pooled (the pre-start rejection contract), and
+    // the single-handle read preserves the containment/size checks against a mid-read swap.
+    const source = await readHostSource(request.filePath, workspace, this.config.maxDocumentBytes)
+    // Re-check disposal after the awaits: disposeAll() may have snapshotted the instance map while we
+    // were canonicalizing/reading, so creating a server now would leave it unowned by teardown.
+    /* v8 ignore next -- guards a dispose landing during the canonicalize/read await; not a reproducible unit race. */
+    if (this.isDisposed()) throw new Error('lsp-local provider is disposed')
     const instance = await this.instanceFor(workspace)
     try {
-      return await instance.query(request, signal)
+      return await instance.query(request, source, signal)
     } finally {
       // A crashed/closed process must not be reused: drop its slot so the next query starts fresh,
       // but only if the slot still holds THIS instance (a concurrent replacement must survive).
@@ -185,7 +209,6 @@ class LocalLspProvider implements LspProvider {
       initializationOptions: this.config.initializationOptions,
       maxMessageBytes: this.config.maxMessageBytes,
       maxStderrBytes: this.config.maxStderrBytes,
-      maxDocumentBytes: this.config.maxDocumentBytes,
       shutdownTimeoutMs: this.config.shutdownTimeoutMs,
       killGraceMs: this.config.killGraceMs,
     }
@@ -232,6 +255,10 @@ function buildChildEnv(extra: Record<string, string>): Record<string, string> {
  */
 function resolveExecutable(command: string, childEnv: Record<string, string>): string {
   if (isAbsolute(command)) {
+    // Verify an absolute command too, so an unavailable one fails at load, not on the first query.
+    if (!isExecutableSync(command)) {
+      throw new Error(`lsp-local: command "${command}" is not an executable file`)
+    }
     return command
   }
   /* v8 ignore next -- buildChildEnv always sets PATH from the ambient env; the further fallbacks are defensive. */

+ 60 - 23
packages/lsp/lsp-local/src/instance.ts

@@ -8,6 +8,7 @@
  */
 
 import { pathToFileURL } from 'node:url'
+import { LspError } from '@deepseek-ai/dsh-lsp'
 import type {
   LspOperation,
   LspProviderQuery,
@@ -16,7 +17,7 @@ import type {
 import { deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
 import { LspConnection } from './connection.ts'
 import type { ConnectionSpec } from './connection.ts'
-import { readHostSource } from './host.ts'
+import type { HostSource } from './host.ts'
 import type { WireInitializeResult, WireServerCapabilities } from './protocol.ts'
 import {
   negotiatePositionEncoding,
@@ -31,8 +32,6 @@ import {
 export interface InstanceSpec extends ConnectionSpec {
   /** Static `initialize` options forwarded to the server. */
   readonly initializationOptions: unknown
-  /** Largest source file this host will open (bytes). */
-  readonly maxDocumentBytes: number
   /** Graceful `shutdown`/`exit` budget before escalation (ms). */
   readonly shutdownTimeoutMs: number
   /** SIGTERM→SIGKILL grace after graceful shutdown fails (ms). */
@@ -74,11 +73,12 @@ export class LspInstance {
   /**
    * Run one query through the serialized queue.
    * @param request - the resolved provider query.
+   * @param source - the pre-validated, already-read host source (the provider reads before spawning).
    * @param signal - optional cancellation for this query's full lifecycle.
    * @returns the normalized result.
    */
-  query(request: LspProviderQuery, signal?: AbortSignal): Promise<LspQueryResult> {
-    const run = this.queue.then(() => this.runQuery(request, signal))
+  query(request: LspProviderQuery, source: HostSource, signal?: AbortSignal): Promise<LspQueryResult> {
+    const run = this.queue.then(() => this.runQuery(request, source, signal))
     // Keep the tail alive regardless of this query's outcome so the next caller still serializes.
     this.queue = run.then(() => undefined, () => undefined)
     return run
@@ -99,24 +99,26 @@ export class LspInstance {
     this.connection.notify('initialized', {})
   }
 
-  private async runQuery(request: LspProviderQuery, signal?: AbortSignal): Promise<LspQueryResult> {
+  private async runQuery(request: LspProviderQuery, source: HostSource, signal?: AbortSignal): Promise<LspQueryResult> {
     if (this.disposed) throw new Error('LSP instance was disposed')
     if (signal?.aborted) throw abortError(signal)
-    await this.ready
+    // Observe abort during the handshake wait: a server that never answers `initialize` must not
+    // block the tool-timeout signal here (the timeout policy awaits our quiescence, not the promise).
+    await this.abortable(this.ready, signal)
     const capabilities = this.capabilities
     /* v8 ignore next -- `ready` resolves only after capabilities are set, else it rejects above; defensive. */
     if (capabilities === undefined) throw new Error('LSP instance is not initialized')
     if (!supportsOperation(capabilities, request.operation)) {
-      throw new Error(`server does not support ${request.operation}`)
+      throw new LspError(`server does not support ${request.operation}`, 'LSP_UNSUPPORTED_OPERATION')
     }
     if (!supportsTransientOpen(capabilities.textDocumentSync)) {
-      throw new Error('server does not support the transient textDocument/didOpen this host requires')
+      throw new LspError('server does not support the transient textDocument/didOpen this host requires', 'LSP_UNSUPPORTED_OPERATION')
     }
 
-    const source = await readHostSource(request.filePath, this.spec.cwd, this.spec.maxDocumentBytes)
     const uri = pathToFileURL(source.canonicalPath).href
     let opened = false
     try {
+      /* v8 ignore next -- guards an abort landing between the ready wait and didOpen; not deterministically reproducible. */
       if (signal?.aborted) throw abortError(signal)
       this.connection.notify('textDocument/didOpen', {
         textDocument: { uri, languageId: request.languageId, version: 1, text: source.text },
@@ -125,7 +127,10 @@ export class LspInstance {
       const payload = await this.sendRequest(request.operation, uri, request.position, signal)
       return this.normalize(request.operation, payload)
     } finally {
-      if (opened) {
+      // A disposed or closed instance (e.g. an aborted request whose server ignored
+      // `$/cancelRequest`) is already tearing down; sending didClose would race that teardown and let
+      // the next queued query's document lifecycle overlap the still-active request.
+      if (opened && !this.dead) {
         try {
           this.connection.notify('textDocument/didClose', { textDocument: { uri } })
         } catch (error) {
@@ -141,6 +146,21 @@ export class LspInstance {
     }
   }
 
+  /**
+   * Await `work`, but reject as soon as `signal` aborts. The underlying `work` promise keeps its own
+   * handlers, so an orphaned rejection after abort is not unhandled.
+   */
+  private abortable<T>(work: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
+    if (signal === undefined) return work
+    /* v8 ignore next -- runQuery checks signal.aborted before each abortable() call, so it is not already aborted here; defensive. */
+    if (signal.aborted) return Promise.reject(abortError(signal))
+    return new Promise<T>((resolve, reject) => {
+      const onAbort = (): void => { reject(abortError(signal)) }
+      signal.addEventListener('abort', onAbort, { once: true })
+      work.then(resolve, reject).finally(() => { signal.removeEventListener('abort', onAbort) })
+    })
+  }
+
   private async sendRequest(
     operation: LspOperation,
     uri: string,
@@ -160,21 +180,33 @@ export class LspInstance {
     return this.raceAbort(send, requestId, signal)
   }
 
-  /** Race a pending request against abort; on abort, send `$/cancelRequest` and reject. */
+  /**
+   * Race a pending request against abort. On abort, send `$/cancelRequest` and give the server a
+   * bounded grace to acknowledge; if it does not settle in time, invalidate and tear down the
+   * instance so the still-active request cannot overlap the next queued query's document lifecycle.
+   */
   private async raceAbort(send: Promise<unknown>, requestId: number, signal: AbortSignal): Promise<unknown> {
-    const abort = new Promise<never>((_, reject) => {
-      const onAbort = (): void => { reject(abortError(signal)) }
-      /* v8 ignore next -- runQuery checks signal.aborted before sending, so it is not yet aborted here; defensive. */
-      if (signal.aborted) { onAbort(); return }
-      signal.addEventListener('abort', onAbort, { once: true })
-      // Remove the abort listener once the request settles either way; the finally-promise inherits
-      // send's rejection, so catch it to avoid an unhandled rejection when abort already won.
-      send.finally(() => { signal.removeEventListener('abort', onAbort) }).catch(() => {})
-    })
     try {
-      return await Promise.race([send, abort])
+      return await this.abortable(send, signal)
     } catch (error) {
-      if (signal.aborted) this.connection.cancel(requestId)
+      if (!signal.aborted) throw error
+      this.connection.cancel(requestId)
+      // Wait, bounded, for the server to honor the cancellation. If it does not, the request is still
+      // running: terminate the instance (disposal awaits process close) so nothing outlives the query.
+      using grace = deadline(undefined, this.spec.killGraceMs, 'LSP_CANCEL_GRACE')
+      // `settled` is true if the request finished (either outcome) before the grace elapsed.
+      const settled = await Promise.race([
+        send.then(markSettled, markSettled),
+        new Promise<boolean>((resolve) => {
+          /* v8 ignore next -- the cancel-grace deadline signal is freshly armed and not yet aborted here; defensive. */
+          if (grace.signal.aborted) { resolve(false); return }
+          grace.signal.addEventListener('abort', () => { resolve(false) }, { once: true })
+        }),
+      ])
+      if (!settled && !this.disposed) {
+        this.disposed = true
+        await this.tearDown(abortError(signal))
+      }
       throw error
     }
   }
@@ -266,6 +298,11 @@ const LIFECYCLE_NOOP_METHODS = new Set([
   'client/unregisterCapability',
 ])
 
+/** Mark a settled request in the cancel-grace race (either outcome means the request finished). */
+function markSettled(): boolean {
+  return true
+}
+
 /** Build an abort Error carrying the signal's reason (preserving a timeout classification). */
 function abortError(signal: AbortSignal): Error {
   const timeout = timeoutOf(signal)

+ 3 - 7
packages/lsp/lsp-local/src/translate.ts

@@ -21,7 +21,6 @@ import type {
   WireRange,
   WireServerCapabilities,
   WireTextDocumentSyncKind,
-  WireTextDocumentSyncOptions,
 } from './protocol.ts'
 
 /**
@@ -71,13 +70,15 @@ export function supportsOperation(capabilities: WireServerCapabilities, operatio
 
 /**
  * Whether a `textDocumentSync` value permits the transient `didOpen`/`didClose` this host relies on.
+ * The legacy enum form implies open/close for `Full`/`Incremental`; the options form requires an
+ * explicit `openClose: true`, because the protocol defaults an omitted `openClose` to false.
  * @param sync - the server's advertised `textDocumentSync` capability.
  * @returns true when transient open/close is supported.
  */
 export function supportsTransientOpen(sync: WireServerCapabilities['textDocumentSync']): boolean {
   if (sync === undefined) return false
   if (typeof sync === 'number') return isOpenCloseKind(sync)
-  return sync.openClose === true || (sync.openClose === undefined && changeAllowsOpenClose(sync))
+  return sync.openClose === true
 }
 
 /** Legacy enum: `Full` (1) or `Incremental` (2) imply open/close support; `None` (0) does not. */
@@ -85,11 +86,6 @@ function isOpenCloseKind(kind: WireTextDocumentSyncKind): boolean {
   return kind === 1 || kind === 2
 }
 
-/** Options without an explicit `openClose` fall back to the legacy `change` enum's implication. */
-function changeAllowsOpenClose(sync: WireTextDocumentSyncOptions): boolean {
-  return sync.change !== undefined && isOpenCloseKind(sync.change)
-}
-
 /**
  * Normalize the negotiated position encoding. An omitted encoding defaults to `utf-16`; any value
  * other than `utf-16` is a protocol error this host does not support.

+ 8 - 0
packages/lsp/lsp-local/tests/host.spec.ts

@@ -102,4 +102,12 @@ describe('readHostSource', () => {
     await writeFile(join(ws, 'bin.ts'), Buffer.from([0xff, 0xfe, 0x00]))
     await expect(readHostSource('bin.ts', ws, BIG)).rejects.toThrow(/not valid UTF-8/)
   })
+
+  it('keeps a valid U+FFFD replacement character in otherwise-valid UTF-8', async () => {
+    // The literal replacement char is valid UTF-8; a fatal decoder must accept it (only malformed
+    // byte sequences are rejected).
+    await writeFile(join(ws, 'repl.ts'), 'const s = "�"\n')
+    const source = await readHostSource('repl.ts', ws, BIG)
+    expect(source.text).toBe('const s = "�"\n')
+  })
 })

+ 70 - 19
packages/lsp/lsp-local/tests/instance.spec.ts

@@ -3,9 +3,9 @@ import { mkdtemp, mkdir, rm, writeFile, realpath } from 'node:fs/promises'
 import { tmpdir } from 'node:os'
 import { join } from 'node:path'
 import { pathToFileURL, fileURLToPath } from 'node:url'
-import { LspInstance } from '@deepseek-ai/dsh-lsp-local'
+import { LspInstance, readHostSource } from '@deepseek-ai/dsh-lsp-local'
 import type { InstanceSpec } from '@deepseek-ai/dsh-lsp-local/src/instance.ts'
-import type { LspProviderQuery } from '@deepseek-ai/dsh-lsp'
+import type { LspProviderQuery, LspQueryResult } from '@deepseek-ai/dsh-lsp'
 
 const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
 const fixtureServer = fileURLToPath(new URL('./fixture-server.ts', import.meta.url))
@@ -38,7 +38,6 @@ function makeInstance(env: Record<string, string> = {}, overrides: Partial<Insta
     initializationOptions: { init: true },
     maxMessageBytes: 16_000_000,
     maxStderrBytes: 100_000,
-    maxDocumentBytes: 4_000_000,
     shutdownTimeoutMs: 200,
     killGraceMs: 200,
     ...overrides,
@@ -51,6 +50,12 @@ function query(operation: LspProviderQuery['operation'] = 'definition'): LspProv
   return { operation, filePath: 'a.ts', position: { line: 0, character: 6 }, workspaceRoot: ws, languageId: 'typescript' }
 }
 
+/** Run a query against an instance, reading the source first the way the provider does. */
+async function run(instance: LspInstance, operation: LspProviderQuery['operation'] = 'definition', signal?: AbortSignal): Promise<LspQueryResult> {
+  const source = await readHostSource('a.ts', ws, 4_000_000)
+  return instance.query(query(operation), source, signal)
+}
+
 /** Build an instance whose "server" is an inline node script (for teardown-escalation control). */
 function scriptInstance(script: string, overrides: Partial<InstanceSpec> = {}): LspInstance {
   const instance = new LspInstance({
@@ -62,7 +67,6 @@ function scriptInstance(script: string, overrides: Partial<InstanceSpec> = {}):
     initializationOptions: null,
     maxMessageBytes: 16_000_000,
     maxStderrBytes: 100_000,
-    maxDocumentBytes: 4_000_000,
     shutdownTimeoutMs: 150,
     killGraceMs: 150,
     ...overrides,
@@ -87,51 +91,98 @@ describe('LspInstance server-request handling', () => {
     const instance = makeInstance({ LSP_FAKE_ON_OPEN: 'configuration', LSP_FAKE_DEF: locJson() })
     // The query drives didOpen, which makes the fake emit workspace/configuration; a healthy answer
     // keeps the query working.
-    await expect(instance.query(query('definition'))).resolves.toMatchObject({ kind: 'locations' })
+    await expect(run(instance, 'definition')).resolves.toMatchObject({ kind: 'locations' })
   })
 
   it('accepts a lifecycle client/registerCapability request', async () => {
     const instance = makeInstance({ LSP_FAKE_ON_OPEN: 'lifecycle', LSP_FAKE_DEF: 'null' })
-    await expect(instance.query(query('definition'))).resolves.toEqual({ kind: 'locations', locations: [] })
+    await expect(run(instance, 'definition')).resolves.toEqual({ kind: 'locations', locations: [] })
   })
 
   it('rejects a workspace/applyEdit request but keeps serving', async () => {
     const instance = makeInstance({ LSP_FAKE_ON_OPEN: 'applyEdit', LSP_FAKE_DEF: 'null' })
-    await expect(instance.query(query('definition'))).resolves.toEqual({ kind: 'locations', locations: [] })
+    await expect(run(instance, 'definition')).resolves.toEqual({ kind: 'locations', locations: [] })
   })
 
   it('rejects an unknown server request but keeps serving', async () => {
     const instance = makeInstance({ LSP_FAKE_ON_OPEN: 'unknown', LSP_FAKE_DEF: 'null' })
-    await expect(instance.query(query('definition'))).resolves.toEqual({ kind: 'locations', locations: [] })
+    await expect(run(instance, 'definition')).resolves.toEqual({ kind: 'locations', locations: [] })
   })
 })
 
 describe('LspInstance query and abort', () => {
   it('sends includeDeclaration for references', async () => {
     const instance = makeInstance({ LSP_FAKE_REFS: JSON.stringify([JSON.parse(locJson())]) })
-    await expect(instance.query(query('references'))).resolves.toMatchObject({ kind: 'locations' })
+    await expect(run(instance, 'references')).resolves.toMatchObject({ kind: 'locations' })
   })
 
   it('rejects a query aborted before it starts', async () => {
     const instance = makeInstance({ LSP_FAKE_DEF: 'null' })
     const controller = new AbortController()
     controller.abort(new Error('pre-abort'))
-    await expect(instance.query(query('definition'), controller.signal)).rejects.toThrow(/pre-abort/)
+    await expect(run(instance, 'definition', controller.signal)).rejects.toThrow(/pre-abort/)
   })
 
   it('cancels an in-flight request on abort and rejects', async () => {
     const instance = makeInstance({ LSP_FAKE_HANG: '1' })
     const controller = new AbortController()
     // Warm the instance first so the abort lands during the hanging request, not during startup.
-    const pending = instance.query(query('definition'), controller.signal)
+    const pending = run(instance, 'definition', controller.signal)
+    await new Promise<void>(resolve => setTimeout(resolve, 300))
+    controller.abort(new Error('mid-flight'))
+    await expect(pending).rejects.toThrow(/mid-flight/)
+  })
+
+  it('terminates the instance when the server ignores $/cancelRequest past the grace', async () => {
+    // The hang server never honors cancellation, so after the bounded grace the instance must be torn
+    // down (its process closed) rather than left with an active request.
+    const instance = makeInstance({ LSP_FAKE_HANG: '1' }, { killGraceMs: 100 })
+    const controller = new AbortController()
+    const pending = run(instance, 'definition', controller.signal)
+    await new Promise<void>(resolve => setTimeout(resolve, 300))
+    controller.abort(new Error('mid-flight'))
+    await expect(pending).rejects.toThrow(/mid-flight/)
+    expect(instance.dead).toBe(true)
+  })
+
+  it('resolves the cancel grace when the server honors $/cancelRequest', async () => {
+    // A server that answers $/cancelRequest by settling the pending request lets the grace race
+    // resolve via the request rather than the timeout, so the instance is NOT force-terminated.
+    const script = 'let b=Buffer.alloc(0),reqId=null;'
+      + 'const fr=(o)=>{const x=Buffer.from(JSON.stringify({jsonrpc:"2.0",...o}));return Buffer.concat([Buffer.from(`Content-Length: ${x.length}\\r\\n\\r\\n`),x]);};'
+      + 'process.stdin.on("data",c=>{b=Buffer.concat([b,c]);for(;;){const s=b.indexOf("\\r\\n\\r\\n");if(s<0)break;const len=Number(/(\\d+)/.exec(b.toString("ascii",0,s))[1]);if(b.length<s+4+len)break;const m=JSON.parse(b.toString("utf8",s+4,s+4+len));b=b.subarray(s+4+len);'
+      + 'if(m.method==="initialize")process.stdout.write(fr({id:m.id,result:{capabilities:{positionEncoding:"utf-16",textDocumentSync:1,definitionProvider:true}}}));'
+      + 'else if(m.method==="textDocument/definition")reqId=m.id;'
+      + 'else if(m.method==="$/cancelRequest"&&reqId!==null)process.stdout.write(fr({id:reqId,error:{code:-32800,message:"request cancelled"}}));'
+      + 'else if(m.method==="shutdown")process.stdout.write(fr({id:m.id,result:null}));'
+      + 'else if(m.method==="exit")process.exit(0);'
+      + '}});'
+    const instance = scriptInstance(script, { killGraceMs: 2_000 })
+    const controller = new AbortController()
+    const pending = run(instance, 'definition', controller.signal)
     await new Promise<void>(resolve => setTimeout(resolve, 300))
     controller.abort(new Error('mid-flight'))
     await expect(pending).rejects.toThrow(/mid-flight/)
+    // The server acknowledged cancellation within grace, so the instance was not force-killed.
+    expect(instance.dead).toBe(false)
+    await instance.dispose()
+  })
+
+  it('observes abort while awaiting a slow initialize handshake', async () => {
+    // A server that answers nothing (not even initialize) leaves `ready` pending; an abort must be
+    // observed during that wait instead of hanging the tool-timeout signal.
+    const instance = scriptInstance('setInterval(()=>{},1000)', { killGraceMs: 100 })
+    const controller = new AbortController()
+    const pending = run(instance, 'definition', controller.signal)
+    await new Promise<void>(resolve => setTimeout(resolve, 150))
+    controller.abort(new Error('handshake-abort'))
+    await expect(pending).rejects.toThrow(/handshake-abort/)
+    await instance.dispose()
   })
 
   it('rejects when the server lacks the operation capability', async () => {
     const instance = makeInstance({ LSP_FAKE_CAPS: JSON.stringify({ definitionProvider: false }), LSP_FAKE_DEF: 'null' })
-    await expect(instance.query(query('definition'))).rejects.toThrow(/does not support definition/)
+    await expect(run(instance, 'definition')).rejects.toThrow(/does not support definition/)
   })
 
   it('propagates a server error response even when a signal is supplied (not an abort)', async () => {
@@ -139,28 +190,28 @@ describe('LspInstance query and abort', () => {
     // without treating it as an abort.
     const instance = makeInstance({ LSP_FAKE_ERROR: '1' })
     const controller = new AbortController()
-    await expect(instance.query(query('definition'), controller.signal)).rejects.toThrow(/server refused/)
+    await expect(run(instance, 'definition', controller.signal)).rejects.toThrow(/server refused/)
   })
 })
 
 describe('LspInstance disposal', () => {
   it('is idempotent — a second dispose awaits close without error', async () => {
     const instance = makeInstance({ LSP_FAKE_DEF: 'null' })
-    await instance.query(query('definition'))
+    await run(instance, 'definition')
     await instance.dispose()
     await expect(instance.dispose()).resolves.toBeUndefined()
   })
 
   it('rejects a query after disposal', async () => {
     const instance = makeInstance({ LSP_FAKE_DEF: 'null' })
-    await instance.query(query('definition'))
+    await run(instance, 'definition')
     await instance.dispose()
-    await expect(instance.query(query('definition'))).rejects.toThrow(/disposed/)
+    await expect(run(instance, 'definition')).rejects.toThrow(/disposed/)
   })
 
   it('reports dead after the process closes', async () => {
     const instance = makeInstance({ LSP_FAKE_DEF: 'null' })
-    await instance.query(query('definition'))
+    await run(instance, 'definition')
     await instance.dispose()
     expect(instance.dead).toBe(true)
   })
@@ -169,14 +220,14 @@ describe('LspInstance disposal', () => {
     // Server answers initialize, ignores shutdown, and traps SIGTERM so only SIGKILL stops it.
     const script = RESPONDING_SERVER + 'process.on("SIGTERM",()=>{});'
     const instance = scriptInstance(script, { shutdownTimeoutMs: 100, killGraceMs: 100 })
-    await instance.query(query('definition'))
+    await run(instance, 'definition')
     await expect(instance.dispose()).resolves.toBeUndefined()
   })
 
   it('carries a non-Error abort reason as a generic aborted error', async () => {
     const instance = makeInstance({ LSP_FAKE_HANG: '1' })
     const controller = new AbortController()
-    const pending = instance.query(query('definition'), controller.signal)
+    const pending = run(instance, 'definition', controller.signal)
     await new Promise<void>(resolve => setTimeout(resolve, 200))
     controller.abort('a string reason, not an Error')
     await expect(pending).rejects.toThrow(/aborted/)

+ 27 - 0
packages/lsp/lsp-local/tests/provider.spec.ts

@@ -75,4 +75,31 @@ describe('lsp-local provider resolution', () => {
     await expect(lsp.query(query())).rejects.toThrow(expect.objectContaining({ code: 'LSP_UNAVAILABLE' }))
     await ctx.fiber.dispose()
   })
+
+  it('rejects a nonpositive teardown budget at load', async () => {
+    const ctx = new Context()
+    await ctx.plugin(Lsp)
+    await expect(ctx.plugin(LspLocal, {
+      providerId: 'bad-budget',
+      command: process.execPath,
+      args: ['-e', ''],
+      extensionToLanguage: { '.ts': 'typescript' },
+      killGraceMs: 0,
+    })).rejects.toThrow(/killGraceMs must be a positive integer/)
+    await ctx.fiber.dispose()
+  })
+
+  it('rejects an absolute command that is not executable at load', async () => {
+    const notExe = join(root, 'not-exe.txt')
+    await writeFile(notExe, 'plain text, not executable')
+    const ctx = new Context()
+    await ctx.plugin(Lsp)
+    await expect(ctx.plugin(LspLocal, {
+      providerId: 'abs-bad',
+      command: notExe,
+      args: [],
+      extensionToLanguage: { '.ts': 'typescript' },
+    })).rejects.toThrow(/is not an executable file/)
+    await ctx.fiber.dispose()
+  })
 })

+ 3 - 3
packages/lsp/lsp-local/tests/translate.spec.ts

@@ -47,9 +47,9 @@ describe('supportsTransientOpen', () => {
     expect(supportsTransientOpen({ openClose: false, change: 2 })).toBe(false)
   })
 
-  it('falls back to the change enum when openClose is omitted', () => {
-    expect(supportsTransientOpen({ change: 1 })).toBe(true)
-    expect(supportsTransientOpen({ change: 0 })).toBe(false)
+  it('requires an explicit openClose for the options form (no change-enum fallback)', () => {
+    expect(supportsTransientOpen({ change: 1 })).toBe(false)
+    expect(supportsTransientOpen({ change: 2 })).toBe(false)
     expect(supportsTransientOpen({})).toBe(false)
   })
 })

+ 685 - 145
scripts/type-equiv.manifest.json

@@ -1,150 +1,690 @@
 {
   "comment": "Maps each ` ```ts type-equiv ` block (by doc + declared symbol) to the source symbol it must match verbatim. verify-type-equiv.ts enforces a 1:1 correspondence: every type-equiv block has exactly one entry here, and every entry resolves to exactly one block. Add an entry when you add a type-equiv block; remove it when you remove the block.",
   "entries": [
-    { "doc": "docs/core-data-structures/core.md", "symbol": "Branded", "source": "packages/util/brand/src/index.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "ContentBlockMap", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "Message", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "MessageSourceMap", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "FinishReasonMap", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "GenerateOptions", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "ToolSchema", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "LlmCallConfig", "source": "packages/llm/llm/src/call-config.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "Agent", "source": "packages/core/agent/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "HookContext", "source": "packages/core/agent/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "PromptDecision", "source": "packages/core/agent/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "ContinuationDecision", "source": "packages/core/agent/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "ContinuationStop", "source": "packages/core/agent/src/types.ts" },
-    { "doc": "docs/core-data-structures/core.md", "symbol": "SessionStartSource", "source": "packages/core/agent/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/scope.md", "symbol": "ScopeKey", "source": "packages/core/scope/src/index.ts" },
-    { "doc": "docs/core-data-structures/scope.md", "symbol": "Scoped", "source": "packages/core/scope/src/index.ts" },
-    { "doc": "docs/core-data-structures/scope.md", "symbol": "Scope", "source": "packages/core/scope/src/index.ts" },
-
-    { "doc": "docs/core-data-structures/system-prompt.md", "symbol": "AssembleContext", "source": "packages/core/system-prompt/src/index.ts" },
-    { "doc": "docs/core-data-structures/system-prompt.md", "symbol": "PromptSection", "source": "packages/core/system-prompt/src/index.ts" },
-    { "doc": "docs/core-data-structures/system-prompt.md", "symbol": "ToolProviderResult", "source": "packages/core/system-prompt/src/index.ts" },
-
-    { "doc": "docs/core-data-structures/llm-streaming.md", "symbol": "StreamChunk", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/llm-streaming.md", "symbol": "TokenUsage", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/llm-streaming.md", "symbol": "ContentBlockMap", "source": "packages/llm/llm/src/types.ts" },
-    { "doc": "docs/core-data-structures/llm-streaming.md", "symbol": "AppIdentity", "source": "packages/llm/llm/src/attribution.ts" },
-
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SessionEventMap", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "EpochHeader", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "TodoItem", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "TurnTriggerMap", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "TurnEndReasonMap", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceEventType", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceOp", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceIntent", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceNode", "source": "packages/core/session/src/surface.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceFoldReplacement", "source": "packages/core/session/src/surface.ts" },
-    { "doc": "docs/core-data-structures/session.md", "symbol": "SurfaceFoldResult", "source": "packages/core/session/src/surface.ts" },
-
-    { "doc": "docs/core-data-structures/persistence.md", "symbol": "SessionHeader", "source": "packages/core/session/src/types.ts" },
-    { "doc": "docs/core-data-structures/persistence.md", "symbol": "CreateSessionOptions", "source": "packages/core/session/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/session-query.md", "symbol": "SessionEventSurface", "source": "packages/session-query/session-query/src/types.ts" },
-    { "doc": "docs/core-data-structures/session-query.md", "symbol": "SessionRecord", "source": "packages/session-query/session-query/src/types.ts" },
-    { "doc": "docs/core-data-structures/session-query.md", "symbol": "SessionEventRecord", "source": "packages/session-query/session-query/src/types.ts" },
-    { "doc": "docs/core-data-structures/session-query.md", "symbol": "SessionQueryErrorCode", "source": "packages/session-query/session-query/src/config.ts" },
-    { "doc": "docs/core-data-structures/session-query.md", "symbol": "SessionEventReadRequest", "source": "packages/session-query/session-query/src/types.ts" },
-    { "doc": "docs/core-data-structures/session-query.md", "symbol": "SessionEventWindow", "source": "packages/session-query/session-query/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolDefinition", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "SchemaProp", "source": "packages/core/tools/src/schema.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "SchemaSpec", "source": "packages/core/tools/src/schema.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "InferArgs", "source": "packages/core/tools/src/schema.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolExecutionToken", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolExecutionInput", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolExecution", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolGuard", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolRestriction", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "ToolExecutionResult", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "PreToolDecision", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "PostToolDecision", "source": "packages/core/tools/src/index.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "StructuredScalar", "source": "packages/core/tools/src/json-schema.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "StructuredSchemaType", "source": "packages/core/tools/src/json-schema.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "StructuredSchemaNode", "source": "packages/core/tools/src/json-schema.ts" },
-    { "doc": "docs/core-data-structures/tools.md", "symbol": "StructuredOutputSchema", "source": "packages/core/tools/src/json-schema.ts" },
-
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionOption", "source": "packages/ui/user-interaction/src/index.ts" },
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionItem", "source": "packages/ui/user-interaction/src/index.ts" },
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionRequest", "source": "packages/ui/user-interaction/src/index.ts" },
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionAnswerItem", "source": "packages/ui/user-interaction/src/index.ts" },
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "AskUserQuestionAnswer", "source": "packages/ui/user-interaction/src/index.ts" },
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "UserInteractionProvider", "source": "packages/ui/user-interaction/src/index.ts" },
-    { "doc": "docs/core-data-structures/user-interaction.md", "symbol": "UserInteractionError", "source": "packages/ui/user-interaction/src/index.ts" },
-
-    { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalRequestId", "source": "packages/ui/user-approval/src/index.ts" },
-    { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalOutcome", "source": "packages/ui/user-approval/src/index.ts" },
-    { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalPolicy", "source": "packages/ui/user-approval/src/index.ts" },
-    { "doc": "docs/core-data-structures/approval.md", "symbol": "ApprovalRequest", "source": "packages/ui/user-approval/src/index.ts" },
-
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "BashExecRequest", "source": "packages/bash/bash/src/types.ts" },
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "BashExecSpec", "source": "packages/bash/bash/src/types.ts" },
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "BashRunResult", "source": "packages/bash/bash/src/types.ts" },
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "BashSandboxInfo", "source": "packages/bash/bash/src/types.ts" },
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "CollectedOutput", "source": "packages/bash/bash/src/types.ts" },
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTask", "source": "packages/bash/bash/src/types.ts" },
-    { "doc": "docs/core-data-structures/bash.md", "symbol": "BashTaskRead", "source": "packages/bash/bash/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/sandbox.md", "symbol": "SandboxMode", "source": "packages/sandbox/sandbox/src/index.ts" },
-    { "doc": "docs/core-data-structures/sandbox.md", "symbol": "ConfinedSandboxMode", "source": "packages/sandbox/sandbox/src/index.ts" },
-    { "doc": "docs/core-data-structures/sandbox.md", "symbol": "SandboxEnforcement", "source": "packages/sandbox/sandbox/src/index.ts" },
-    { "doc": "docs/core-data-structures/sandbox.md", "symbol": "SandboxPolicy", "source": "packages/sandbox/sandbox/src/index.ts" },
-    { "doc": "docs/core-data-structures/sandbox.md", "symbol": "ConfinedArgv", "source": "packages/sandbox/sandbox/src/index.ts" },
-
-    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunRequest", "source": "packages/code-runtime/code-runtime/src/types.ts" },
-    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunResult", "source": "packages/code-runtime/code-runtime/src/types.ts" },
-    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeBindingNamespace", "source": "packages/code-runtime/code-runtime/src/types.ts" },
-    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeBindingFunction", "source": "packages/code-runtime/code-runtime/src/types.ts" },
-    { "doc": "docs/core-data-structures/code-runtime.md", "symbol": "CodeRunFailure", "source": "packages/code-runtime/code-runtime/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTarget", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsTargetKey", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsVersion", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsInfo", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsDirEntry", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsWriteIntent", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsWriteOutcome", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsEditRequest", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsEditOutcome", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsErrorCode", "source": "packages/fs/fs/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FsPolicyExec", "source": "packages/fs/fs-policy/src/types.ts" },
-    { "doc": "docs/core-data-structures/filesystem.md", "symbol": "FileReadOutcome", "source": "packages/fs/tool-fs/src/read-render.ts" },
-
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillSource", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillResourceBase", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillSummary", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillCandidate", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillDefinition", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillRegistration", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillLookupOptions", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "SkillProvider", "source": "packages/skill/skill/src/index.ts" },
-    { "doc": "docs/core-data-structures/skills.md", "symbol": "Config", "source": "packages/skill/skill/src/index.ts" },
-
-    { "doc": "docs/core-data-structures/compaction.md", "symbol": "CompactionResult", "source": "packages/compact/compact/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentCapabilities", "source": "packages/subagent/subagent/src/types.ts" },
-    { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentStartRequest", "source": "packages/subagent/subagent/src/types.ts" },
-    { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentResult", "source": "packages/subagent/subagent/src/types.ts" },
-    { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentStopReasonMap", "source": "packages/subagent/subagent/src/types.ts" },
-    { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentRun", "source": "packages/subagent/subagent/src/types.ts" },
-    { "doc": "docs/core-data-structures/subagent.md", "symbol": "SubagentProvider", "source": "packages/subagent/subagent/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/web.md", "symbol": "WebSearchRequest", "source": "packages/web/web/src/types.ts" },
-    { "doc": "docs/core-data-structures/web.md", "symbol": "WebSearchResult", "source": "packages/web/web/src/types.ts" },
-    { "doc": "docs/core-data-structures/web.md", "symbol": "WebSearchSource", "source": "packages/web/web/src/types.ts" },
-    { "doc": "docs/core-data-structures/web.md", "symbol": "WebFetchRequest", "source": "packages/web/web/src/types.ts" },
-    { "doc": "docs/core-data-structures/web.md", "symbol": "WebFetchResult", "source": "packages/web/web/src/types.ts" },
-    { "doc": "docs/core-data-structures/web.md", "symbol": "WebFetchBody", "source": "packages/web/web/src/types.ts" },
-
-    { "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowStartRequest", "source": "packages/workflow/workflow/src/types.ts" },
-    { "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowMeta", "source": "packages/workflow/workflow/src/types.ts" },
-    { "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowResult", "source": "packages/workflow/workflow/src/types.ts" },
-    { "doc": "docs/core-data-structures/workflow.md", "symbol": "WorkflowRun", "source": "packages/workflow/workflow/src/types.ts" }
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "Branded",
+      "source": "packages/util/brand/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "ContentBlockMap",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "Message",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "MessageSourceMap",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "FinishReasonMap",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "GenerateOptions",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "ToolSchema",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "LlmCallConfig",
+      "source": "packages/llm/llm/src/call-config.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "SessionEvent",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "Agent",
+      "source": "packages/core/agent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "HookContext",
+      "source": "packages/core/agent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "PromptDecision",
+      "source": "packages/core/agent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "ContinuationDecision",
+      "source": "packages/core/agent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "ContinuationStop",
+      "source": "packages/core/agent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/core.md",
+      "symbol": "SessionStartSource",
+      "source": "packages/core/agent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/scope.md",
+      "symbol": "ScopeKey",
+      "source": "packages/core/scope/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/scope.md",
+      "symbol": "Scoped",
+      "source": "packages/core/scope/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/scope.md",
+      "symbol": "Scope",
+      "source": "packages/core/scope/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/system-prompt.md",
+      "symbol": "AssembleContext",
+      "source": "packages/core/system-prompt/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/system-prompt.md",
+      "symbol": "PromptSection",
+      "source": "packages/core/system-prompt/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/system-prompt.md",
+      "symbol": "ToolProviderResult",
+      "source": "packages/core/system-prompt/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/llm-streaming.md",
+      "symbol": "StreamChunk",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/llm-streaming.md",
+      "symbol": "TokenUsage",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/llm-streaming.md",
+      "symbol": "ContentBlockMap",
+      "source": "packages/llm/llm/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/llm-streaming.md",
+      "symbol": "AppIdentity",
+      "source": "packages/llm/llm/src/attribution.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SessionEventMap",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "EpochHeader",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "TodoItem",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SessionEvent",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "TurnTriggerMap",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "TurnEndReasonMap",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SurfaceEventType",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SurfaceOp",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SurfaceIntent",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SurfaceNode",
+      "source": "packages/core/session/src/surface.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SurfaceFoldReplacement",
+      "source": "packages/core/session/src/surface.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session.md",
+      "symbol": "SurfaceFoldResult",
+      "source": "packages/core/session/src/surface.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/persistence.md",
+      "symbol": "SessionHeader",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/persistence.md",
+      "symbol": "CreateSessionOptions",
+      "source": "packages/core/session/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session-query.md",
+      "symbol": "SessionEventSurface",
+      "source": "packages/session-query/session-query/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session-query.md",
+      "symbol": "SessionRecord",
+      "source": "packages/session-query/session-query/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session-query.md",
+      "symbol": "SessionEventRecord",
+      "source": "packages/session-query/session-query/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session-query.md",
+      "symbol": "SessionQueryErrorCode",
+      "source": "packages/session-query/session-query/src/config.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session-query.md",
+      "symbol": "SessionEventReadRequest",
+      "source": "packages/session-query/session-query/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/session-query.md",
+      "symbol": "SessionEventWindow",
+      "source": "packages/session-query/session-query/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolDefinition",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "SchemaProp",
+      "source": "packages/core/tools/src/schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "SchemaSpec",
+      "source": "packages/core/tools/src/schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "InferArgs",
+      "source": "packages/core/tools/src/schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolExecutionToken",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolExecutionInput",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolExecution",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolGuard",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolRestriction",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "ToolExecutionResult",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "PreToolDecision",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "PostToolDecision",
+      "source": "packages/core/tools/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "StructuredScalar",
+      "source": "packages/core/tools/src/json-schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "StructuredSchemaType",
+      "source": "packages/core/tools/src/json-schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "StructuredSchemaNode",
+      "source": "packages/core/tools/src/json-schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/tools.md",
+      "symbol": "StructuredOutputSchema",
+      "source": "packages/core/tools/src/json-schema.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "AskUserQuestionOption",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "AskUserQuestionItem",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "AskUserQuestionRequest",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "AskUserQuestionAnswerItem",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "AskUserQuestionAnswer",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "UserInteractionProvider",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/user-interaction.md",
+      "symbol": "UserInteractionError",
+      "source": "packages/ui/user-interaction/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/approval.md",
+      "symbol": "ApprovalRequestId",
+      "source": "packages/ui/user-approval/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/approval.md",
+      "symbol": "ApprovalOutcome",
+      "source": "packages/ui/user-approval/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/approval.md",
+      "symbol": "ApprovalPolicy",
+      "source": "packages/ui/user-approval/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/approval.md",
+      "symbol": "ApprovalRequest",
+      "source": "packages/ui/user-approval/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "BashExecRequest",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "BashExecSpec",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "BashRunResult",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "BashSandboxInfo",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "CollectedOutput",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "BashTask",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/bash.md",
+      "symbol": "BashTaskRead",
+      "source": "packages/bash/bash/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/sandbox.md",
+      "symbol": "SandboxMode",
+      "source": "packages/sandbox/sandbox/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/sandbox.md",
+      "symbol": "ConfinedSandboxMode",
+      "source": "packages/sandbox/sandbox/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/sandbox.md",
+      "symbol": "SandboxEnforcement",
+      "source": "packages/sandbox/sandbox/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/sandbox.md",
+      "symbol": "SandboxPolicy",
+      "source": "packages/sandbox/sandbox/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/sandbox.md",
+      "symbol": "ConfinedArgv",
+      "source": "packages/sandbox/sandbox/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/code-runtime.md",
+      "symbol": "CodeRunRequest",
+      "source": "packages/code-runtime/code-runtime/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/code-runtime.md",
+      "symbol": "CodeRunResult",
+      "source": "packages/code-runtime/code-runtime/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/code-runtime.md",
+      "symbol": "CodeBindingNamespace",
+      "source": "packages/code-runtime/code-runtime/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/code-runtime.md",
+      "symbol": "CodeBindingFunction",
+      "source": "packages/code-runtime/code-runtime/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/code-runtime.md",
+      "symbol": "CodeRunFailure",
+      "source": "packages/code-runtime/code-runtime/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsTarget",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsTargetKey",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsVersion",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsInfo",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsDirEntry",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsWriteIntent",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsWriteOutcome",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsEditRequest",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsEditOutcome",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsErrorCode",
+      "source": "packages/fs/fs/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FsPolicyExec",
+      "source": "packages/fs/fs-policy/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/filesystem.md",
+      "symbol": "FileReadOutcome",
+      "source": "packages/fs/tool-fs/src/read-render.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillSource",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillResourceBase",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillSummary",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillCandidate",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillDefinition",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillRegistration",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillLookupOptions",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "SkillProvider",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/skills.md",
+      "symbol": "Config",
+      "source": "packages/skill/skill/src/index.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/compaction.md",
+      "symbol": "CompactionResult",
+      "source": "packages/compact/compact/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/subagent.md",
+      "symbol": "SubagentCapabilities",
+      "source": "packages/subagent/subagent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/subagent.md",
+      "symbol": "SubagentStartRequest",
+      "source": "packages/subagent/subagent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/subagent.md",
+      "symbol": "SubagentResult",
+      "source": "packages/subagent/subagent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/subagent.md",
+      "symbol": "SubagentStopReasonMap",
+      "source": "packages/subagent/subagent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/subagent.md",
+      "symbol": "SubagentRun",
+      "source": "packages/subagent/subagent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/subagent.md",
+      "symbol": "SubagentProvider",
+      "source": "packages/subagent/subagent/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/web.md",
+      "symbol": "WebSearchRequest",
+      "source": "packages/web/web/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/web.md",
+      "symbol": "WebSearchResult",
+      "source": "packages/web/web/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/web.md",
+      "symbol": "WebSearchSource",
+      "source": "packages/web/web/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/web.md",
+      "symbol": "WebFetchRequest",
+      "source": "packages/web/web/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/web.md",
+      "symbol": "WebFetchResult",
+      "source": "packages/web/web/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/web.md",
+      "symbol": "WebFetchBody",
+      "source": "packages/web/web/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/workflow.md",
+      "symbol": "WorkflowStartRequest",
+      "source": "packages/workflow/workflow/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/workflow.md",
+      "symbol": "WorkflowMeta",
+      "source": "packages/workflow/workflow/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/workflow.md",
+      "symbol": "WorkflowResult",
+      "source": "packages/workflow/workflow/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/workflow.md",
+      "symbol": "WorkflowRun",
+      "source": "packages/workflow/workflow/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspOperation",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspPosition",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspRange",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspQueryRequest",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspProviderQuery",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspLocation",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspHover",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspQueryResult",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspProvider",
+      "source": "packages/lsp/lsp/src/types.ts"
+    },
+    {
+      "doc": "docs/core-data-structures/lsp.md",
+      "symbol": "LspService",
+      "source": "packages/lsp/lsp/src/types.ts"
+    }
   ]
 }