{"version":3,"file":"web-tools.mjs","names":[],"sources":["../../src/pi-extensions/web-tools.ts"],"sourcesContent":["/**\n * OpenTag web tools Pi extension (trusted built artifact).\n *\n * Loaded explicitly with `pi -e <this file>` for actual execution only; `--no-extensions` stays\n * set so implicit discovery never loads it. The extension registers `web_search`/`web_fetch`,\n * forwards validated calls over the per-execution endpoint named by the nonsecret\n * OPENTAG_WEB_TOOLS_SOCKET descriptor, and saves bounded extracted content under the Session\n * workspace `.opentag/web/<stable-tool-id>/`.\n *\n * This module is intentionally self-contained (Node builtins only, no package imports): it runs\n * inside the Pi process and must never resolve workspace dependencies. The wire authority is\n * `@opentag/shared` web-tools; the gateway and Server re-validate everything authoritatively.\n *\n * Wire/artifact identity: the wire toolCallId is derived deterministically (UUID shape) from the\n * per-execution endpoint descriptor plus the actual Pi runtime tool call ID. Same-execution\n * retries reuse it (stable idempotency); a distinct execution can never collide with or\n * overwrite another execution's artifacts. Failures throw bounded redacted errors so Pi records\n * toolResult.isError=true; a normal return is never used for failed operations.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { constants } from \"node:fs\";\nimport { type FileHandle, lstat, mkdir, open, readdir, realpath, rename, rm, writeFile } from \"node:fs/promises\";\nimport http from \"node:http\";\nimport { join, sep } from \"node:path\";\n\n/** Named export so Client tests can pin these against the shared wire authority. */\nexport const WEB_EXTENSION_LIMITS = {\n  requestMaxBytes: 16 * 1024,\n  searchResponseMaxBytes: 1024 * 1024,\n  fetchResponseMaxBytes: 3 * 1024 * 1024,\n  previewPageMaxBytes: 12 * 1024,\n  toolResultMaxBytes: 48 * 1024,\n  pageBodyMaxBytes: 1024 * 1024,\n  queryMaxCodepoints: 400,\n  searchLimitMax: 10,\n  domainsMax: 10,\n  fetchUrlsMax: 3,\n  urlMaxBytes: 4 * 1024,\n  /** End-to-end operation budgets, equal to the shared hard caps; the chain only decreases them. */\n  searchTimeoutMs: 15_000,\n  fetchTimeoutMs: 45_000,\n  protocolVersion: 1,\n  /** Remaining-budget hop header (hop metadata, never part of the semantic digest). */\n  remainingHeader: \"x-web-remaining-ms\",\n} as const;\n\nconst TRUNCATION_MARKER = \"…[truncated]\";\nconst ENDPOINT_ENV = \"OPENTAG_WEB_TOOLS_SOCKET\";\n/** UUID-shaped stable id derivation namespace; version/variant bits are set below. */\nconst TOOL_ID_NAMESPACE = \"opentag.web.tool.v1\";\n/** Exact UUID-v5 shape produced by `deriveWebToolIdentity`; the only id ever used as a directory. */\nconst TOOL_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n/** The deadline controller aborts with this exact reason so callers keep distinct timeout wording. */\nconst WEB_CALL_TIMED_OUT = \"The web call timed out\";\n\n/** Plain JSON Schema (no TypeBox dependency); the gateway re-validates with the shared Zod authority. */\nexport const WEB_SEARCH_PARAMETERS = {\n  type: \"object\",\n  properties: {\n    query: {\n      type: \"string\",\n      minLength: 1,\n      maxLength: WEB_EXTENSION_LIMITS.queryMaxCodepoints,\n      description: \"Search query (1–400 characters), kept verbatim including case.\",\n    },\n    limit: {\n      type: \"integer\",\n      minimum: 1,\n      maximum: WEB_EXTENSION_LIMITS.searchLimitMax,\n      default: 5,\n      description: \"Maximum number of sources to return (default 5).\",\n    },\n    domains: {\n      type: \"array\",\n      items: { type: \"string\" },\n      maxItems: WEB_EXTENSION_LIMITS.domainsMax,\n      description: \"Optional public domain names to scope the search (max 10).\",\n    },\n    timeRange: {\n      type: \"string\",\n      enum: [\"day\", \"week\", \"month\", \"year\"],\n      description: \"Optional recency window for results.\",\n    },\n    language: {\n      type: \"string\",\n      description: \"Optional language preference (e.g. en, zh-CN, portuguese); not a strict filter.\",\n    },\n    depth: {\n      type: \"string\",\n      enum: [\"basic\", \"advanced\"],\n      default: \"basic\",\n      description: \"Search depth; advanced costs more and must be chosen explicitly.\",\n    },\n  },\n  required: [\"query\"],\n  additionalProperties: false,\n} as const;\n\nexport const WEB_FETCH_PARAMETERS = {\n  type: \"object\",\n  properties: {\n    urls: {\n      type: \"array\",\n      items: { type: \"string\" },\n      minItems: 1,\n      maxItems: WEB_EXTENSION_LIMITS.fetchUrlsMax,\n      description: \"1–3 public http(s) URLs to extract page content from.\",\n    },\n    depth: {\n      type: \"string\",\n      enum: [\"basic\", \"advanced\"],\n      default: \"basic\",\n      description: \"Extraction depth; advanced handles complex pages and must be chosen explicitly.\",\n    },\n  },\n  required: [\"urls\"],\n  additionalProperties: false,\n} as const;\n\n/* --------------------------- stable tool identity --------------------------- */\n\n/**\n * Deterministic UUID-shaped tool identity: SHA-256 over the derivation namespace, the\n * per-execution endpoint descriptor, and the actual Pi runtime tool call ID, formatted with\n * version-5/variant bits. Same endpoint + same call ID ⇒ same id (stable retry); a different\n * execution endpoint ⇒ a different id (no cross-execution artifact collision).\n */\nexport function deriveWebToolIdentity(endpointDescriptor: string, runtimeToolCallId: string): string {\n  if (typeof runtimeToolCallId !== \"string\" || runtimeToolCallId.length === 0) {\n    throw new Error(\"The runtime tool call id is missing\");\n  }\n  const hash = createHash(\"sha256\")\n    .update(TOOL_ID_NAMESPACE, \"utf8\")\n    .update(\"\", \"utf8\")\n    .update(endpointDescriptor, \"utf8\")\n    .update(\"\", \"utf8\")\n    .update(runtimeToolCallId, \"utf8\")\n    .digest();\n  hash[6] = ((hash[6] ?? 0) & 0x0f) | 0x50;\n  hash[8] = ((hash[8] ?? 0) & 0x3f) | 0x80;\n  const hex = hash.subarray(0, 16).toString(\"hex\");\n  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\n/* ------------------------------ byte-safe text ------------------------------ */\n\n/** Truncate to a UTF-8 byte budget on codepoint boundaries, appending a marker when it fits. */\nexport function truncateToByteBudget(text: string, budgetBytes: number): { text: string; truncated: boolean } {\n  const bytes = Buffer.byteLength(text, \"utf8\");\n  if (bytes <= budgetBytes) return { text, truncated: false };\n  if (budgetBytes <= 0) return { text: \"\", truncated: true };\n  const markerBytes = Buffer.byteLength(TRUNCATION_MARKER, \"utf8\");\n  const buffer = Buffer.from(text, \"utf8\");\n  if (budgetBytes < markerBytes) {\n    // The marker itself does not fit: cut content only, still codepoint-safe and bounded.\n    let cut = budgetBytes;\n    while (cut > 0 && ((buffer[cut] ?? 0) & 0xc0) === 0x80) cut -= 1;\n    return { text: buffer.subarray(0, cut).toString(\"utf8\"), truncated: true };\n  }\n  let cut = budgetBytes - markerBytes;\n  while (cut > 0 && ((buffer[cut] ?? 0) & 0xc0) === 0x80) cut -= 1;\n  return { text: `${buffer.subarray(0, cut).toString(\"utf8\")}${TRUNCATION_MARKER}`, truncated: true };\n}\n\n/* ------------------------------ socket client ------------------------------ */\n\ninterface GatewayReply {\n  status: number;\n  body: Buffer;\n}\n\n/**\n * Distinguish the operation deadline from a caller abort while keeping the established wording:\n * `The web call was aborted` (never leaks a caller-provided reason), `The web call timed out` when\n * the end-to-end deadline fired. Everything downstream fails closed on either.\n */\nfunction webCallAbortError(signal?: AbortSignal): Error {\n  const reason = signal?.reason;\n  if (reason instanceof Error && reason.message === WEB_CALL_TIMED_OUT) return new Error(WEB_CALL_TIMED_OUT);\n  return new Error(\"The web call was aborted\");\n}\n\n/**\n * End-to-end operation deadline (original 15s/45s budget): mirrors the caller abort signal and\n * additionally aborts when the remaining budget expires. The returned signal stays live through\n * the HTTP hop AND every artifact write/publication, so filesystem work can never outlive the\n * budget; callers must keep checking it after each await and dispose it in a `finally`.\n */\nfunction startOperationDeadline(\n  timeoutMs: number,\n  externalSignal?: AbortSignal,\n): { signal: AbortSignal; deadlineAtMs: number; dispose(): void } {\n  const deadlineAtMs = Date.now() + timeoutMs;\n  const controller = new AbortController();\n  const onExternalAbort = () => controller.abort(externalSignal?.reason);\n  if (externalSignal?.aborted) controller.abort(externalSignal.reason);\n  else externalSignal?.addEventListener(\"abort\", onExternalAbort, { once: true });\n  const timer = setTimeout(() => controller.abort(new Error(WEB_CALL_TIMED_OUT)), timeoutMs);\n  timer.unref?.();\n  return {\n    signal: controller.signal,\n    deadlineAtMs,\n    dispose() {\n      clearTimeout(timer);\n      externalSignal?.removeEventListener(\"abort\", onExternalAbort);\n    },\n  };\n}\n\nfunction postToGateway(\n  socketPath: string,\n  path: string,\n  payload: unknown,\n  options: { deadlineAtMs: number; signal?: AbortSignal; maxResponseBytes: number },\n): Promise<GatewayReply> {\n  return new Promise((resolvePromise, rejectPromise) => {\n    const fail = (message: string) => {\n      finish(new Error(message));\n    };\n    const remainingAtDispatch = options.deadlineAtMs - Date.now();\n    if (options.signal?.aborted) {\n      rejectPromise(webCallAbortError(options.signal));\n      return;\n    }\n    if (remainingAtDispatch <= 0) {\n      rejectPromise(new Error(\"The web call budget was exhausted before dispatch\"));\n      return;\n    }\n    const body = Buffer.from(JSON.stringify(payload), \"utf8\");\n    if (body.byteLength > WEB_EXTENSION_LIMITS.requestMaxBytes) {\n      rejectPromise(new Error(\"The web request exceeds the 16 KiB request bound\"));\n      return;\n    }\n    let settled = false;\n    let timer: ReturnType<typeof setTimeout> | undefined;\n    let request: http.ClientRequest;\n    const onAbort = () => {\n      finish(webCallAbortError(options.signal));\n      request.destroy();\n    };\n    const finish = (error?: Error, reply?: GatewayReply) => {\n      if (settled) return;\n      settled = true;\n      if (timer) clearTimeout(timer);\n      options.signal?.removeEventListener(\"abort\", onAbort);\n      if (error) rejectPromise(error);\n      else if (reply) resolvePromise(reply);\n    };\n    request = http.request(\n      {\n        socketPath,\n        path,\n        method: \"POST\",\n        headers: {\n          \"content-type\": \"application/json\",\n          \"content-length\": String(body.byteLength),\n          accept: \"application/json\",\n          // Hop metadata only: the remaining end-to-end budget, recomputed per hop.\n          [WEB_EXTENSION_LIMITS.remainingHeader]: String(remainingAtDispatch),\n        },\n      },\n      (response) => {\n        const chunks: Buffer[] = [];\n        let total = 0;\n        response.on(\"data\", (chunk: Buffer) => {\n          total += chunk.byteLength;\n          if (total > options.maxResponseBytes) {\n            fail(\"The web gateway response exceeds the bound\");\n            request.destroy();\n            return;\n          }\n          chunks.push(chunk);\n        });\n        response.on(\"end\", () =>\n          finish(undefined, { status: response.statusCode ?? 500, body: Buffer.concat(chunks) }),\n        );\n        response.on(\"error\", () => fail(\"The web gateway response failed\"));\n      },\n    );\n    timer = setTimeout(() => {\n      fail(\"The web call timed out\");\n      request.destroy();\n    }, remainingAtDispatch);\n    timer.unref?.();\n    request.on(\"error\", (error) => {\n      const code = (error as NodeJS.ErrnoException).code;\n      if (options.signal?.aborted) fail(webCallAbortError(options.signal).message);\n      else if (settled) return;\n      else fail(`The web gateway is unavailable: ${code ?? \"unreachable\"}`);\n    });\n    options.signal?.addEventListener(\"abort\", onAbort, { once: true });\n    request.end(body);\n  });\n}\n\n/* ------------------------------ artifact store ------------------------------ */\n\nexport interface SavedArtifact {\n  readonly relativePath: string;\n  readonly bytes: number;\n  readonly sha256: string;\n  readonly artifactTruncated: boolean;\n}\n\n/**\n * Per-attempt bookkeeping for exactly the filesystem entries this attempt created. Cleanup may\n * remove only entries registered here — anything else on disk belongs to a prior or concurrent\n * attempt and is never touched.\n */\ninterface ArtifactAttemptObserver {\n  /** Called before a temp file is written, with its base name inside the attempt directory. */\n  onTempFile?(name: string): void;\n  /** Called immediately after an attempt file is atomically published (rename completed). */\n  onPublished?(name: string): void;\n}\n\n/** Create-if-missing without following symlinks, then prove the result is a real directory. */\nasync function ensureOwnedDirectory(path: string): Promise<void> {\n  try {\n    await mkdir(path, { mode: 0o700 });\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n  }\n  await assertRealDirectory(path);\n}\n\nasync function assertRealDirectory(path: string): Promise<void> {\n  const stat = await lstat(path);\n  if (stat.isSymbolicLink() || !stat.isDirectory()) throw new Error(\"The web artifact path is not a real directory\");\n}\n\n/**\n * Open (or reuse) the artifact directory for one stable tool call id. Every parent component is\n * validated BEFORE anything is created through it, so a hostile symlink can never cause writes\n * outside the Session store; a second page under the same id reuses the verified directory.\n */\nasync function openArtifactDirectory(cwd: string, toolCallId: string, signal?: AbortSignal): Promise<string> {\n  if (!TOOL_ID_PATTERN.test(toolCallId)) {\n    throw new Error(\"Unsafe web artifact tool id\");\n  }\n  signal?.throwIfAborted();\n  const root = await realpath(cwd);\n  // Validate each component before creating through it (never mkdir -p across an unchecked link).\n  await ensureOwnedDirectory(join(root, \".opentag\"));\n  await ensureOwnedDirectory(join(root, \".opentag\", \"web\"));\n  const baseReal = await realpath(join(root, \".opentag\", \"web\"));\n  if (!baseReal.startsWith(`${root}${sep}`)) throw new Error(\"The web artifact store escapes the workspace\");\n  const directory = join(baseReal, toolCallId);\n  await ensureOwnedDirectory(directory);\n  const resolvedDirectory = await realpath(directory);\n  if (!resolvedDirectory.startsWith(`${baseReal}${sep}`)) {\n    throw new Error(\"The web artifact directory escapes the Session store\");\n  }\n  signal?.throwIfAborted();\n  return resolvedDirectory;\n}\n\n/**\n * Save one page: bounded content, temp file + atomic rename, post-write regular-file proof.\n * The operation deadline stays authoritative after every await (including after publication), so\n * an aborted/timed-out attempt never reports a fabricated success. The observer is notified of the\n * temp name before the first write and of the published name right after the rename, which is the\n * only ownership evidence later cleanup is allowed to trust.\n * Throws on any failure — callers must mark the artifact unsaved and never fabricate a path.\n */\nexport async function saveWebArtifact(input: {\n  cwd: string;\n  toolCallId: string;\n  fileName: string;\n  content: string;\n  signal?: AbortSignal;\n  observer?: ArtifactAttemptObserver;\n}): Promise<SavedArtifact> {\n  if (!/^[a-z0-9][a-z0-9.-]{0,63}$/.test(input.fileName)) throw new Error(\"Unsafe web artifact file name\");\n  input.signal?.throwIfAborted();\n  const directory = await openArtifactDirectory(input.cwd, input.toolCallId, input.signal);\n  const truncated = truncateToByteBudget(input.content, WEB_EXTENSION_LIMITS.pageBodyMaxBytes);\n  const bytes = Buffer.byteLength(truncated.text, \"utf8\");\n  const sha256 = createHash(\"sha256\").update(truncated.text, \"utf8\").digest(\"hex\");\n  const finalPath = join(directory, input.fileName);\n  // An identical finalized page is immutable content: reuse it instead of rewriting, so a retry\n  // can never replace bytes that the existing finalized index already references. Oversize or\n  // unsafe entries are a bounded validation failure, never an unbounded read.\n  const existing = await readBoundedRegularFile(finalPath, WEB_EXTENSION_LIMITS.pageBodyMaxBytes, input.signal);\n  if (existing.kind === \"oversize\" || existing.kind === \"unsafe\") {\n    throw new Error(\"The existing web artifact is not a bounded regular file\");\n  }\n  if (existing.kind === \"regular\" && existing.bytes.length === bytes && sha256Hex(existing.bytes) === sha256) {\n    return {\n      relativePath: [\".opentag\", \"web\", input.toolCallId, input.fileName].join(\"/\"),\n      bytes,\n      sha256,\n      artifactTruncated: truncated.truncated,\n    };\n  }\n  const tempName = `.${input.fileName}.${randomSuffix()}.tmp`;\n  const tempPath = join(directory, tempName);\n  input.observer?.onTempFile?.(tempName);\n  try {\n    // The operation signal is part of the actual write: an abort can stop the write itself, and the\n    // post-await checks below still catch an abort that lands after completion.\n    await writeFile(tempPath, truncated.text, {\n      encoding: \"utf8\",\n      mode: 0o600,\n      flag: \"wx\",\n      signal: input.signal,\n    });\n    input.signal?.throwIfAborted();\n    await rename(tempPath, finalPath);\n    input.observer?.onPublished?.(input.fileName);\n    input.signal?.throwIfAborted();\n    const stat = await lstat(finalPath);\n    if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(\"The web artifact failed verification\");\n    input.signal?.throwIfAborted();\n  } catch (error) {\n    await rm(tempPath, { force: true }).catch(() => undefined);\n    throw error instanceof Error ? error : new Error(\"The web artifact write failed\");\n  }\n  return {\n    relativePath: [\".opentag\", \"web\", input.toolCallId, input.fileName].join(\"/\"),\n    bytes,\n    sha256,\n    artifactTruncated: truncated.truncated,\n  };\n}\n\nfunction randomSuffix(): string {\n  return createHash(\"sha256\").update(`${Date.now()}:${Math.random()}`).digest(\"hex\").slice(0, 16);\n}\n\ninterface FetchMetadataPage {\n  index: number;\n  url: string;\n  file: string;\n  bytes: number;\n  sha256: string;\n  artifactTruncated: boolean;\n  upstreamTruncated: boolean | null;\n  finalUrl: string | null;\n  sourceFetchedAt: string | null;\n  completeness: \"unknown\";\n}\n\ninterface FetchMetadata {\n  version: 1;\n  toolCallId: string;\n  requestId: string;\n  retrievedAt: string;\n  effectiveDepth: string;\n  pages: FetchMetadataPage[];\n  failures: Array<{ url: string; code: string; retryable: boolean }>;\n}\n\nfunction sha256Hex(data: Uint8Array): string {\n  return createHash(\"sha256\").update(data).digest(\"hex\");\n}\n\n/**\n * Read cap for the attempt index: ample for three URLs plus failure records (URLs are bounded at\n * 4 KiB each), far below any allocation risk. Page content gets its own 1 MiB cap.\n */\nconst METADATA_READ_MAX_BYTES = 64 * 1024;\n/** Fixed read chunk: the loop stops as soon as the cap is crossed, never trusting fstat size. */\nconst READ_CHUNK_BYTES = 64 * 1024;\n\ntype BoundedReadResult =\n  | { readonly kind: \"absent\" }\n  | { readonly kind: \"regular\"; readonly bytes: Buffer }\n  | { readonly kind: \"unsafe\" }\n  | { readonly kind: \"oversize\" };\n\n/** Read-only, non-following, non-blocking open flags (POSIX bits fall back to 0 where absent). */\nfunction boundedReadFlags(): number {\n  const noFollow = (constants.O_NOFOLLOW as number | undefined) ?? 0;\n  const nonBlocking = (constants.O_NONBLOCK as number | undefined) ?? 0;\n  return constants.O_RDONLY | noFollow | nonBlocking;\n}\n\n/**\n * Bounded, symlink-safe read of a workspace-controlled entry. The handle is opened with\n * O_NOFOLLOW and O_NONBLOCK so a symlinked entry can never be followed and a FIFO can never block\n * the open waiting for a writer; fstat must prove a regular file, and the read proceeds in fixed\n * chunks until the cap is crossed — a file growing between fstat and read therefore returns\n * `oversize` instead of allocating past the cap. Callers decide whether a non-regular result is a\n * fall-through (cleanup) or a bounded unsaved failure (writes).\n */\nasync function readBoundedRegularFile(\n  path: string,\n  maxBytes: number,\n  signal?: AbortSignal,\n): Promise<BoundedReadResult> {\n  signal?.throwIfAborted();\n  let handle: FileHandle;\n  try {\n    // O_NONBLOCK makes an open of a FIFO return immediately so fstat can reject the non-regular\n    // entry instead of blocking for a writer before the deadline and the type check run; it has\n    // no effect on regular-file reads.\n    handle = await open(path, boundedReadFlags());\n  } catch (error) {\n    const code = (error as NodeJS.ErrnoException).code;\n    if (code === \"ENOENT\") return { kind: \"absent\" };\n    if (code === \"ELOOP\" || code === \"EMLINK\") return { kind: \"unsafe\" };\n    throw error;\n  }\n  try {\n    const stat = await handle.stat();\n    if (!stat.isFile()) return { kind: \"unsafe\" };\n    if (stat.size > maxBytes) return { kind: \"oversize\" };\n    const chunks: Buffer[] = [];\n    let total = 0;\n    while (total <= maxBytes) {\n      signal?.throwIfAborted();\n      const chunk = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, maxBytes + 1 - total));\n      const { bytesRead } = await handle.read(chunk, 0, chunk.length, total);\n      if (bytesRead === 0) break;\n      total += bytesRead;\n      if (total > maxBytes) return { kind: \"oversize\" };\n      chunks.push(chunk.subarray(0, bytesRead));\n    }\n    signal?.throwIfAborted();\n    return { kind: \"regular\", bytes: Buffer.concat(chunks, total) };\n  } finally {\n    await handle.close().catch(() => undefined);\n  }\n}\n\n/** Files a finalized index references; malformed/absent metadata references nothing. */\nfunction finalizedPagesFromBytes(bytes: Buffer): Set<string> {\n  try {\n    const parsed = JSON.parse(bytes.toString(\"utf8\")) as FetchMetadata;\n    if (!parsed || !Array.isArray(parsed.pages)) return new Set();\n    return new Set(parsed.pages.map((page) => page.file).filter((file) => typeof file === \"string\"));\n  } catch {\n    return new Set();\n  }\n}\n\nasync function finalizedFileSet(directory: string): Promise<Set<string>> {\n  const existing = await readBoundedRegularFile(join(directory, \"metadata.json\"), METADATA_READ_MAX_BYTES);\n  if (existing.kind !== \"regular\") return new Set();\n  return finalizedPagesFromBytes(existing.bytes);\n}\n\n/**\n * Restore a prior finalized index atomically, and only over a regular file inside the validated\n * attempt directory. Best-effort: a restore failure never masks the original operation error.\n */\nasync function restoreMetadataBytes(directory: string, content: Buffer): Promise<void> {\n  const target = join(directory, \"metadata.json\");\n  const stat = await lstat(target).catch(() => undefined);\n  if (!stat || stat.isSymbolicLink() || !stat.isFile()) return;\n  const tempPath = join(directory, `.metadata.restore.${randomSuffix()}.tmp`);\n  try {\n    await writeFile(tempPath, content, { mode: 0o600, flag: \"wx\" });\n    await rename(tempPath, target);\n  } catch {\n    await rm(tempPath, { force: true }).catch(() => undefined);\n  }\n}\n\n/**\n * Which page names a surviving index protects. When this attempt published its own index and no\n * prior index is restored, its pages are its own to remove; otherwise pages referenced by the\n * surviving (prior or foreign) index must never be deleted.\n */\nasync function protectionSet(\n  directory: string,\n  input: { publishedMetadata?: boolean; priorMetadata?: Buffer },\n): Promise<Set<string>> {\n  if (input.publishedMetadata === true && input.priorMetadata !== undefined) {\n    return finalizedPagesFromBytes(input.priorMetadata);\n  }\n  if (input.publishedMetadata === true) return new Set();\n  return finalizedFileSet(directory);\n}\n\n/**\n * Decide the fate of the index this attempt published: put the prior finalized bytes back when\n * they are provably the ones we replaced, withdraw a fresh index of our own, and never clobber\n * an index that another attempt published after ours.\n */\nasync function settlePublishedMetadata(\n  directory: string,\n  input: { priorMetadata?: Buffer; publishedMetadataDigest?: string },\n): Promise<void> {\n  const digest = input.publishedMetadataDigest;\n  if (digest === undefined) {\n    await removeOwnedEntry(directory, \"metadata.json\");\n    return;\n  }\n  const current = await readBoundedRegularFile(join(directory, \"metadata.json\"), METADATA_READ_MAX_BYTES);\n  if (current.kind !== \"regular\" || sha256Hex(current.bytes) !== digest) return;\n  if (input.priorMetadata !== undefined) {\n    await restoreMetadataBytes(directory, input.priorMetadata);\n    return;\n  }\n  await removeOwnedEntry(directory, \"metadata.json\");\n}\n\n/** A relative entry name that can only address one file directly inside the attempt directory. */\nfunction isSafeEntryName(name: string): boolean {\n  return name.length > 0 && name !== \".\" && name !== \"..\" && !name.includes(\"/\") && !name.includes(\"\\\\\");\n}\n\n/**\n * Resolve the attempt directory for cleanup without ever traversing a symlink: every component\n * (`.opentag`, `web`, the stable id) must be a real directory. A symlinked parent means this is\n * not the Session store this execution opened, so cleanup does nothing at all.\n */\nasync function resolveCleanupDirectory(cwd: string, toolCallId: string): Promise<string | undefined> {\n  if (!TOOL_ID_PATTERN.test(toolCallId)) return undefined;\n  let root: string;\n  try {\n    root = await realpath(cwd);\n  } catch {\n    return undefined;\n  }\n  const directory = join(root, \".opentag\", \"web\", toolCallId);\n  for (const component of [join(root, \".opentag\"), join(root, \".opentag\", \"web\"), directory]) {\n    try {\n      const stat = await lstat(component);\n      if (stat.isSymbolicLink() || !stat.isDirectory()) return undefined;\n    } catch {\n      return undefined;\n    }\n  }\n  try {\n    const resolved = await realpath(directory);\n    if (!resolved.startsWith(`${root}${sep}`)) return undefined;\n    return resolved;\n  } catch {\n    return undefined;\n  }\n}\n\n/** Remove one explicitly owned entry, and only when it is still a regular file (never a link). */\nasync function removeOwnedEntry(directory: string, name: string): Promise<void> {\n  const target = join(directory, name);\n  try {\n    const stat = await lstat(target);\n    if (stat.isSymbolicLink() || !stat.isFile()) return;\n  } catch {\n    return;\n  }\n  await rm(target, { force: true }).catch(() => undefined);\n}\n\n/**\n * Aborted/failed attempt cleanup: remove ONLY the entries this attempt explicitly registered —\n * the temp file names it reported before writing and the page files it published — and never a\n * wildcard `*.tmp` sweep. Anything else in the directory (another attempt's temp/publication\n * files, earlier successful artifacts, and pages protected by a surviving index) is preserved.\n * When this attempt published an index over a prior finalized one, the prior bytes are restored\n * unless another attempt has since published its own (proven by digest); a fresh index with no\n * prior is withdrawn. The whole path is validated first, so a symlinked intermediate component\n * cannot redirect the deletion outside the Session store.\n */\nexport async function cleanupWebArtifacts(input: {\n  cwd: string;\n  toolCallId: string;\n  preExisting: ReadonlySet<string>;\n  writtenThisAttempt: ReadonlySet<string>;\n  /** Temp base names this attempt created before writing; the only temp names cleanup may remove. */\n  tempFiles?: ReadonlySet<string>;\n  /** True when this attempt published metadata.json: its index and pages are this attempt's. */\n  publishedMetadata?: boolean;\n  /** Finalized index bytes this attempt replaced; restored when the attempt is withdrawn. */\n  priorMetadata?: Buffer;\n  /** sha256 of the index bytes this attempt published, used to prove the on-disk index is ours. */\n  publishedMetadataDigest?: string;\n}): Promise<void> {\n  const directory = await resolveCleanupDirectory(input.cwd, input.toolCallId);\n  if (!directory) return;\n  // Resolve protected page names before removing anything; a restored prior index is the survivor.\n  const finalized = await protectionSet(directory, input);\n  const owned = new Set<string>();\n  for (const name of input.tempFiles ?? []) {\n    if (isSafeEntryName(name)) owned.add(name);\n  }\n  for (const name of input.writtenThisAttempt) {\n    if (!isSafeEntryName(name)) continue;\n    if (input.preExisting.has(name)) continue;\n    // A page a surviving finalized index references is not ours to delete.\n    if (finalized.has(name)) continue;\n    owned.add(name);\n  }\n  if (input.publishedMetadata) await settlePublishedMetadata(directory, input);\n  await Promise.all([...owned].map((name) => removeOwnedEntry(directory, name)));\n}\n\n/**\n * Publish the attempt index. Like page writes, every await is followed by a deadline check:\n * cancellation while the temp file is being written must never rename metadata into place, and an\n * abort right after the rename is still surfaced to the caller. The caller then withdraws its own\n * index and restores the prior finalized bytes it captured before this rename.\n */\nasync function writeFetchMetadata(\n  directory: string,\n  serialized: string,\n  signal?: AbortSignal,\n  observer?: ArtifactAttemptObserver,\n): Promise<void> {\n  const finalPath = join(directory, \"metadata.json\");\n  const tempName = `.metadata.json.${randomSuffix()}.tmp`;\n  const tempPath = join(directory, tempName);\n  signal?.throwIfAborted();\n  observer?.onTempFile?.(tempName);\n  try {\n    // The operation signal is part of the actual write; the post-await checks still catch an\n    // abort that lands after the write completes.\n    await writeFile(tempPath, serialized, {\n      encoding: \"utf8\",\n      mode: 0o600,\n      flag: \"wx\",\n      signal,\n    });\n    signal?.throwIfAborted();\n    await rename(tempPath, finalPath);\n    observer?.onPublished?.(\"metadata.json\");\n    signal?.throwIfAborted();\n    const stat = await lstat(finalPath);\n    if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(\"The web metadata failed verification\");\n    signal?.throwIfAborted();\n  } catch (error) {\n    await rm(tempPath, { force: true }).catch(() => undefined);\n    throw error instanceof Error ? error : new Error(\"The web metadata write failed\");\n  }\n}\n\n/* ------------------------------- formatting ------------------------------- */\n\nfunction searchOutput(result: Record<string, unknown>): { text: string; truncated: boolean } {\n  const results = result.results as Record<string, unknown>[];\n  const lines: string[] = [\n    `web_search ok — ${results.length} source(s); requestId ${String(result.requestId)}; retrievedAt ${String(\n      result.retrievedAt,\n    )}; depth ${String(result.effectiveDepth)}.`,\n    \"Snippets are provider search snippets for source selection, not page content.\",\n  ];\n  results.forEach((item, index) => {\n    const published = typeof item.publishedAt === \"string\" ? `; published ${item.publishedAt}` : \"\";\n    lines.push(\n      `\\n[${index + 1}] ${typeof item.title === \"string\" && item.title ? item.title : \"(untitled)\"}`,\n      `URL: ${String(item.url)}${published}`,\n      String(item.snippet ?? \"\"),\n    );\n  });\n  return truncateToByteBudget(lines.join(\"\\n\"), WEB_EXTENSION_LIMITS.toolResultMaxBytes);\n}\n\ninterface FetchItemView {\n  status: string;\n  url: string;\n  finalUrl: string | null;\n  content?: string;\n  sourceFetchedAt?: string | null;\n  upstreamTruncated?: boolean | null;\n  previewTruncated?: boolean;\n  artifactTruncated?: boolean;\n  code?: string;\n  retryable?: boolean;\n}\n\ninterface FetchOutput {\n  text: string;\n  artifacts: SavedArtifact[];\n  artifactWriteFailures: number;\n  metadataWritten: boolean;\n}\n\n/** Details artifact records returned to Pi: same combined truncation flag as the metadata. */\nfunction artifactDetails(\n  artifacts: SavedArtifact[],\n): Array<{ path: string; bytes: number; sha256: string; artifactTruncated: boolean }> {\n  return artifacts.map((artifact) => ({\n    path: artifact.relativePath,\n    bytes: artifact.bytes,\n    sha256: artifact.sha256,\n    artifactTruncated: artifact.artifactTruncated,\n  }));\n}\n\n/** Every requested URL failed: the batch is an operation failure, not a partial success. */\nfunction allFailedError(items: FetchItemView[]): Error {\n  const codes = items.map((item) => `${item.url}: ${String(item.code)}${item.retryable ? \" (retryable)\" : \"\"}`);\n  return new Error(`web_fetch failed for every requested URL — ${truncateToByteBudget(codes.join(\"; \"), 512).text}`);\n}\n\n/** One page's report section: metadata lines, flags, and the bounded preview. */\nfunction pageSection(index: number, item: FetchItemView, content: string, saved: SavedArtifact | undefined): string[] {\n  const preview = truncateToByteBudget(content, WEB_EXTENSION_LIMITS.previewPageMaxBytes);\n  const flags: string[] = [\"completeness unknown (extracted content, not raw HTML)\"];\n  if (preview.truncated || item.previewTruncated === true) flags.push(\"preview truncated\");\n  if (saved?.artifactTruncated || item.artifactTruncated === true) flags.push(\"stored content truncated\");\n  if (item.upstreamTruncated === true) flags.push(\"upstream truncated\");\n  if (item.upstreamTruncated === null || item.upstreamTruncated === undefined)\n    flags.push(\"upstream truncation unknown\");\n  return [\n    `\\n[${index + 1}] ${item.url} — ${item.status}`,\n    `finalUrl: ${item.finalUrl ?? \"unknown\"}; sourceFetchedAt: ${item.sourceFetchedAt ?? \"unknown\"}`,\n    saved\n      ? `saved: ${saved.relativePath} (${saved.bytes} bytes, sha256:${saved.sha256})`\n      : \"saved: none (artifact write failed; content exists only in this preview)\",\n    `flags: ${flags.join(\"; \")}`,\n    \"preview:\",\n    preview.text,\n  ];\n}\n\n/** The attempt index, including the combined upstream + local truncation flag per page. */\nfunction buildFetchMetadata(\n  result: Record<string, unknown>,\n  toolCallId: string,\n  pages: Array<{ index: number; item: FetchItemView; artifact: SavedArtifact }>,\n  results: FetchItemView[],\n): FetchMetadata {\n  return {\n    version: 1,\n    toolCallId,\n    requestId: String(result.requestId),\n    retrievedAt: String(result.retrievedAt),\n    effectiveDepth: String(result.effectiveDepth),\n    pages: pages.map((page) => ({\n      index: page.index,\n      url: page.item.url,\n      file: page.artifact.relativePath.split(\"/\").pop() ?? \"\",\n      bytes: page.artifact.bytes,\n      sha256: page.artifact.sha256,\n      artifactTruncated: page.artifact.artifactTruncated,\n      upstreamTruncated: page.item.upstreamTruncated ?? null,\n      finalUrl: page.item.finalUrl ?? null,\n      sourceFetchedAt: page.item.sourceFetchedAt ?? null,\n      completeness: \"unknown\",\n    })),\n    failures: results\n      .filter((item) => item.status === \"failed\")\n      .map((item) => ({ url: item.url, code: String(item.code), retryable: item.retryable === true })),\n  };\n}\n\n/** Save one non-failed page with the combined truncation flag; abort always propagates. */\nasync function saveOnePage(input: {\n  cwd: string;\n  toolCallId: string;\n  index: number;\n  item: FetchItemView;\n  content: string;\n  signal: AbortSignal;\n  observer: ArtifactAttemptObserver;\n}): Promise<{ saved?: SavedArtifact; writeFailed: boolean }> {\n  try {\n    const saved = await saveWebArtifact({\n      cwd: input.cwd,\n      toolCallId: input.toolCallId,\n      fileName: `page-${input.index}.md`,\n      content: input.content,\n      signal: input.signal,\n      observer: input.observer,\n    });\n    // The reported record carries the combined upstream + local truncation flag, matching the\n    // metadata index; neither source of truncation may silently disappear from the details.\n    return {\n      saved: { ...saved, artifactTruncated: saved.artifactTruncated || input.item.artifactTruncated === true },\n      writeFailed: false,\n    };\n  } catch (error) {\n    if (input.signal.aborted) throw error;\n    return { writeFailed: true };\n  }\n}\n\n/**\n * Explicit per-attempt ownership ledger: only entries registered here (temp names before their\n * first write, published names after their rename, and the index this attempt published) may be\n * removed by cleanup — no wildcard tmp sweeps.\n */\ninterface AttemptLedger {\n  readonly tempFiles: Set<string>;\n  readonly writtenThisAttempt: Set<string>;\n  readonly observer: ArtifactAttemptObserver;\n  metadataPublished: boolean;\n  /** sha256 of the index bytes this attempt renamed into place; undefined until then. */\n  publishedMetadataDigest: string | undefined;\n  /** Index bytes present immediately before this attempt's rename; restored on cancellation. */\n  priorMetadata: Buffer | undefined;\n}\n\nfunction createAttemptLedger(): AttemptLedger {\n  const tempFiles = new Set<string>();\n  const writtenThisAttempt = new Set<string>();\n  return {\n    tempFiles,\n    writtenThisAttempt,\n    metadataPublished: false,\n    publishedMetadataDigest: undefined,\n    priorMetadata: undefined,\n    observer: {\n      onTempFile: (name) => {\n        tempFiles.add(name);\n      },\n      onPublished: (name) => {\n        writtenThisAttempt.add(name);\n      },\n    },\n  };\n}\n\nfunction fetchHeader(result: Record<string, unknown>, results: FetchItemView[]): string {\n  return `web_fetch ${String(result.status)} — ${results.length} URL result(s); requestId ${String(\n    result.requestId,\n  )}; retrievedAt ${String(result.retrievedAt)}; depth ${String(result.effectiveDepth)}.`;\n}\n\n/** Open the attempt store up front so pre-existing files are known before any write. */\nasync function openAttemptStore(\n  cwd: string,\n  toolCallId: string,\n  signal: AbortSignal,\n): Promise<{ directory: string; preExisting: Set<string> }> {\n  const directory = await openArtifactDirectory(cwd, toolCallId, signal);\n  const preExisting = new Set(await readdir(directory).catch(() => [] as string[]));\n  return { directory, preExisting };\n}\n\nfunction failedPageSection(index: number, item: FetchItemView): string {\n  return `\\n[${index + 1}] ${item.url} — FAILED: ${String(item.code)}${item.retryable ? \" (retryable with a new call)\" : \"\"}`;\n}\n\n/**\n * Save every non-failed page for this attempt and build its report sections. A genuine write\n * failure is counted and reported; an abort always propagates so the caller never sees success.\n */\nasync function saveResultPages(\n  results: FetchItemView[],\n  toolCallId: string,\n  cwd: string,\n  signal: AbortSignal,\n  ledger: AttemptLedger,\n): Promise<{\n  artifacts: SavedArtifact[];\n  pages: Array<{ index: number; item: FetchItemView; artifact: SavedArtifact }>;\n  sections: string[];\n  artifactWriteFailures: number;\n}> {\n  const artifacts: SavedArtifact[] = [];\n  const pages: Array<{ index: number; item: FetchItemView; artifact: SavedArtifact }> = [];\n  const sections: string[] = [];\n  let artifactWriteFailures = 0;\n  for (const [index, item] of results.entries()) {\n    signal.throwIfAborted();\n    if (item.status === \"failed\") {\n      sections.push(failedPageSection(index, item));\n      continue;\n    }\n    const content = typeof item.content === \"string\" ? item.content : \"\";\n    const { saved, writeFailed } = await saveOnePage({\n      cwd,\n      toolCallId,\n      index,\n      item,\n      content,\n      signal,\n      observer: ledger.observer,\n    });\n    if (writeFailed) artifactWriteFailures += 1;\n    if (saved) {\n      artifacts.push(saved);\n      pages.push({ index, item, artifact: saved });\n    }\n    sections.push(...pageSection(index, item, content, saved));\n  }\n  return { artifacts, pages, sections, artifactWriteFailures };\n}\n\n/**\n * Publish the attempt index. A cancellation during the temp write is rethrown (never a degraded\n * success); only a genuine non-abort write failure returns `failed: true`.\n */\nasync function publishAttemptMetadata(\n  directory: string,\n  result: Record<string, unknown>,\n  toolCallId: string,\n  pages: Array<{ index: number; item: FetchItemView; artifact: SavedArtifact }>,\n  results: FetchItemView[],\n  signal: AbortSignal,\n  ledger: AttemptLedger,\n): Promise<{ written: boolean; failed: boolean }> {\n  const metadata = buildFetchMetadata(result, toolCallId, pages, results);\n  const serialized = `${JSON.stringify(metadata, undefined, 2)}\\n`;\n  const digest = sha256Hex(Buffer.from(serialized, \"utf8\"));\n  // Capture the index this rename replaces so a cancelled retry can put the prior index back. An\n  // unreadable (unsafe/oversize) existing index fails the attempt before any rename: we never\n  // overwrite an index that could not have been preserved.\n  const prior = await readBoundedRegularFile(join(directory, \"metadata.json\"), METADATA_READ_MAX_BYTES, signal);\n  if (prior.kind === \"oversize\" || prior.kind === \"unsafe\") {\n    throw new Error(\"The existing web metadata is not a bounded regular file\");\n  }\n  ledger.priorMetadata = prior.kind === \"regular\" ? prior.bytes : undefined;\n  try {\n    await writeFetchMetadata(directory, serialized, signal, {\n      onTempFile: (name) => {\n        ledger.tempFiles.add(name);\n      },\n      onPublished: () => {\n        ledger.metadataPublished = true;\n        ledger.publishedMetadataDigest = digest;\n      },\n    });\n    return { written: true, failed: false };\n  } catch (error) {\n    if (signal.aborted) throw error;\n    return { written: false, failed: true };\n  }\n}\n\nasync function cleanupAttempt(\n  cwd: string,\n  toolCallId: string,\n  preExisting: ReadonlySet<string>,\n  ledger: AttemptLedger,\n): Promise<void> {\n  await cleanupWebArtifacts({\n    cwd,\n    toolCallId,\n    preExisting,\n    writtenThisAttempt: ledger.writtenThisAttempt,\n    tempFiles: ledger.tempFiles,\n    publishedMetadata: ledger.metadataPublished,\n    priorMetadata: ledger.priorMetadata,\n    publishedMetadataDigest: ledger.publishedMetadataDigest,\n  });\n}\n\n/** One final truncation covers framing AND suffix: the total never exceeds the tool bound. */\nfunction assembleFetchText(sections: string[], artifactWriteFailures: number): string {\n  const suffix =\n    artifactWriteFailures > 0\n      ? `\\n${artifactWriteFailures} artifact write(s) failed; no fabricated paths are reported.`\n      : \"\";\n  return truncateToByteBudget(`${sections.join(\"\\n\")}${suffix}`, WEB_EXTENSION_LIMITS.toolResultMaxBytes).text;\n}\n\nasync function fetchOutput(\n  result: Record<string, unknown>,\n  toolCallId: string,\n  cwd: string,\n  signal: AbortSignal,\n): Promise<FetchOutput> {\n  const results = result.results as FetchItemView[];\n  const ledger = createAttemptLedger();\n  const sections: string[] = [fetchHeader(result, results)];\n  let directory: string | undefined;\n  let preExisting = new Set<string>();\n  try {\n    signal.throwIfAborted();\n    if (results.some((item) => item.status !== \"failed\")) {\n      const store = await openAttemptStore(cwd, toolCallId, signal);\n      directory = store.directory;\n      preExisting = store.preExisting;\n    }\n    const pageOutcome = await saveResultPages(results, toolCallId, cwd, signal, ledger);\n    sections.push(...pageOutcome.sections);\n    signal.throwIfAborted();\n    let artifactWriteFailures = pageOutcome.artifactWriteFailures;\n    let metadataWritten = false;\n    if (directory && pageOutcome.pages.length > 0) {\n      const published = await publishAttemptMetadata(\n        directory,\n        result,\n        toolCallId,\n        pageOutcome.pages,\n        results,\n        signal,\n        ledger,\n      );\n      metadataWritten = published.written;\n      if (published.written) sections.push(`\\nmetadata: ${\".opentag\"}/web/${toolCallId}/metadata.json`);\n      else if (published.failed) {\n        artifactWriteFailures += 1;\n        sections.push(\"\\nmetadata: write failed (page files above remain saved)\");\n      }\n    }\n    // Last check before reporting success: an abort that landed during the final publication still\n    // fails the tool result and lets cleanup withdraw this attempt's metadata/pages.\n    signal.throwIfAborted();\n    return {\n      text: assembleFetchText(sections, artifactWriteFailures),\n      artifacts: pageOutcome.artifacts,\n      artifactWriteFailures,\n      metadataWritten,\n    };\n  } catch (error) {\n    await cleanupAttempt(cwd, toolCallId, preExisting, ledger);\n    throw error;\n  }\n}\n\n/* ------------------------------ tool execution ----------------------------- */\n\nfunction errorEnvelope(body: Buffer): { code: string; retryable: boolean } | undefined {\n  try {\n    const parsed = JSON.parse(body.toString(\"utf8\")) as { error?: { code?: unknown; retryable?: unknown } };\n    if (typeof parsed?.error?.code !== \"string\") return undefined;\n    return { code: parsed.error.code, retryable: parsed.error.retryable === true };\n  } catch {\n    return undefined;\n  }\n}\n\nfunction boundedString(value: unknown, maxBytes: number): value is string {\n  return typeof value === \"string\" && Buffer.byteLength(value, \"utf8\") <= maxBytes;\n}\n\nconst INVALID_WIRE_PAYLOAD = \"The web gateway returned an invalid payload\";\n\nfunction invalidWirePayload(): Error {\n  return new Error(INVALID_WIRE_PAYLOAD);\n}\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n  return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction requireBoundedString(value: unknown, maxBytes: number): string {\n  if (!boundedString(value, maxBytes)) throw invalidWirePayload();\n  return value;\n}\n\nfunction requireNonEmptyBoundedString(value: unknown, maxBytes: number): string {\n  const text = requireBoundedString(value, maxBytes);\n  if (text.length === 0) throw invalidWirePayload();\n  return text;\n}\n\n/** Shared envelope: request id, retrieval time, depth, and the results array shape. */\nfunction assertWireEnvelope(record: Record<string, unknown>): void {\n  requireNonEmptyBoundedString(record.requestId, 128);\n  requireBoundedString(record.retrievedAt, 64);\n  if (record.effectiveDepth !== \"basic\" && record.effectiveDepth !== \"advanced\") throw invalidWirePayload();\n  if (!Array.isArray(record.results)) throw invalidWirePayload();\n}\n\nfunction assertSearchWireItem(value: unknown): void {\n  if (!isPlainRecord(value)) throw invalidWirePayload();\n  requireNonEmptyBoundedString(value.sourceId, 128);\n  requireBoundedString(value.title, 2048);\n  requireNonEmptyBoundedString(value.url, WEB_EXTENSION_LIMITS.urlMaxBytes);\n  requireBoundedString(value.snippet, 8192);\n  if (value.publishedAt !== undefined && !boundedString(value.publishedAt, 128)) throw invalidWirePayload();\n}\n\nfunction assertFailedFetchWireItem(item: Record<string, unknown>): void {\n  requireNonEmptyBoundedString(item.code, 64);\n  if (typeof item.retryable !== \"boolean\") throw invalidWirePayload();\n}\n\nfunction assertContentFetchWireItem(item: Record<string, unknown>): void {\n  if (item.status !== \"ok\" && item.status !== \"partial\") throw invalidWirePayload();\n  if (item.contentKind !== \"extracted\" || item.completeness !== \"unknown\") throw invalidWirePayload();\n  requireBoundedString(item.content, WEB_EXTENSION_LIMITS.pageBodyMaxBytes);\n  if (item.finalUrl !== null && !boundedString(item.finalUrl, WEB_EXTENSION_LIMITS.urlMaxBytes)) {\n    throw invalidWirePayload();\n  }\n  if (item.sourceFetchedAt !== null && !boundedString(item.sourceFetchedAt, 64)) throw invalidWirePayload();\n  if (item.upstreamTruncated !== null && typeof item.upstreamTruncated !== \"boolean\") throw invalidWirePayload();\n  if (typeof item.previewTruncated !== \"boolean\" || typeof item.artifactTruncated !== \"boolean\") {\n    throw invalidWirePayload();\n  }\n}\n\nfunction assertFetchWireItem(value: unknown): void {\n  if (!isPlainRecord(value)) throw invalidWirePayload();\n  requireNonEmptyBoundedString(value.url, WEB_EXTENSION_LIMITS.urlMaxBytes);\n  if (value.status === \"failed\") {\n    assertFailedFetchWireItem(value);\n    return;\n  }\n  assertContentFetchWireItem(value);\n}\n\nfunction assertSearchWireResult(record: Record<string, unknown>, results: unknown[]): void {\n  if (record.status !== \"ok\") throw invalidWirePayload();\n  if (results.length > 25) throw invalidWirePayload();\n  for (const item of results) assertSearchWireItem(item);\n}\n\nfunction assertFetchWireResult(record: Record<string, unknown>, results: unknown[]): void {\n  if (![\"ok\", \"partial\", \"failed\"].includes(String(record.status))) throw invalidWirePayload();\n  if (results.length > WEB_EXTENSION_LIMITS.fetchUrlsMax) throw invalidWirePayload();\n  for (const item of results) assertFetchWireItem(item);\n}\n\n/**\n * Defensive wire validation (the trusted gateway/client hold the authoritative shared schemas).\n * Enough shape and bounds are checked here to prevent mislabeled or synthetic successes.\n */\nfunction assertWireResult(value: unknown, kind: \"search\" | \"fetch\"): Record<string, unknown> {\n  if (!isPlainRecord(value)) throw invalidWirePayload();\n  assertWireEnvelope(value);\n  const results = value.results as unknown[];\n  if (kind === \"search\") assertSearchWireResult(value, results);\n  else assertFetchWireResult(value, results);\n  return value;\n}\n\nfunction normalizeUrl(raw: string): string | undefined {\n  try {\n    const url = new URL(raw);\n    url.hash = \"\";\n    if ((url.protocol === \"http:\" && url.port === \"80\") || (url.protocol === \"https:\" && url.port === \"443\")) {\n      url.port = \"\";\n    }\n    return url.toString();\n  } catch {\n    return undefined;\n  }\n}\n\n/**\n * Results must correspond exactly to the requested URLs after documented normalization/dedup\n * (fragment removed, host case/default ports normalized): every item matches one unclaimed\n * request, none are missing, and duplicates never borrow another URL's content.\n */\nfunction requestedUrlsMatch(urls: string[], results: FetchItemView[]): void {\n  const expected = new Map<string, number>();\n  for (const url of urls) {\n    const normalized = normalizeUrl(url);\n    if (normalized) expected.set(normalized, (expected.get(normalized) ?? 0) + 1);\n  }\n  const seen = new Set<string>();\n  for (const item of results) {\n    const normalized = normalizeUrl(item.url);\n    if (!normalized || !expected.has(normalized) || seen.has(normalized)) {\n      throw new Error(\"The web gateway returned a result for an unrequested or duplicated URL\");\n    }\n    seen.add(normalized);\n  }\n  if (seen.size !== expected.size) {\n    throw new Error(\"The web gateway omitted a requested URL from the results\");\n  }\n}\n\ntype GatewayOperation = \"web_search\" | \"web_fetch\";\n\n/** Validate the HTTP status and parse a successful JSON reply; failures stay bounded/redacted. */\nfunction requireWebSuccessReply(operation: GatewayOperation, reply: GatewayReply): Record<string, unknown> {\n  if (reply.status !== 200) {\n    const failure = errorEnvelope(reply.body);\n    throw new Error(\n      `${operation} failed: ${failure?.code ?? `http_${reply.status}`}${failure?.retryable ? \" (retryable)\" : \"\"}`,\n    );\n  }\n  return JSON.parse(reply.body.toString(\"utf8\")) as Record<string, unknown>;\n}\n\ninterface SearchToolParams {\n  query: string;\n  limit?: number;\n  domains?: string[];\n  timeRange?: string;\n  language?: string;\n  depth?: string;\n}\n\n/** Optional search fields are included only when the caller supplied them. */\nfunction searchRequestBody(toolCallId: string, params: SearchToolParams): Record<string, unknown> {\n  return {\n    protocolVersion: WEB_EXTENSION_LIMITS.protocolVersion,\n    toolCallId,\n    query: params.query,\n    ...(params.limit !== undefined ? { limit: params.limit } : {}),\n    ...(params.domains ? { domains: params.domains } : {}),\n    ...(params.timeRange ? { timeRange: params.timeRange } : {}),\n    ...(params.language ? { language: params.language } : {}),\n    depth: params.depth === \"advanced\" ? \"advanced\" : \"basic\",\n  };\n}\n\ninterface ExtensionTool {\n  name: string;\n  label: string;\n  description: string;\n  parameters: unknown;\n  execute(\n    toolCallId: string,\n    params: never,\n    signal: AbortSignal | undefined,\n    onUpdate: unknown,\n    ctx: { cwd: string },\n  ): Promise<{ content: { type: \"text\"; text: string }[]; details: unknown }>;\n}\n\nexport default function openTagWebTools(pi: { registerTool(tool: ExtensionTool): void }): void {\n  const socketPath = process.env[ENDPOINT_ENV];\n  // No endpoint descriptor: the trusted gateway is absent, so the tools must not register.\n  if (!socketPath) return;\n\n  pi.registerTool({\n    name: \"web_search\",\n    label: \"Web Search\",\n    description:\n      \"Search the public web through OpenTag's managed provider. Returns bounded source snippets for selection; \" +\n      \"use web_fetch on chosen URLs for page content. depth=advanced costs more and must be chosen explicitly. \" +\n      \"No automatic retries; a failed call may be retried as a new explicit call when marked retryable.\",\n    parameters: WEB_SEARCH_PARAMETERS,\n    async execute(runtimeToolCallId, rawParams, signal, _onUpdate, _ctx) {\n      const params = rawParams as SearchToolParams;\n      const toolCallId = deriveWebToolIdentity(socketPath, runtimeToolCallId);\n      // The original 15s budget stays authoritative for the HTTP hop AND all formatting: the\n      // deadline signal fires even if the gateway reply is already in flight.\n      const deadline = startOperationDeadline(WEB_EXTENSION_LIMITS.searchTimeoutMs, signal);\n      try {\n        const reply = await postToGateway(socketPath, \"/web/search\", searchRequestBody(toolCallId, params), {\n          deadlineAtMs: deadline.deadlineAtMs,\n          signal: deadline.signal,\n          maxResponseBytes: WEB_EXTENSION_LIMITS.searchResponseMaxBytes,\n        });\n        deadline.signal.throwIfAborted();\n        const result = assertWireResult(requireWebSuccessReply(\"web_search\", reply), \"search\");\n        const output = searchOutput(result);\n        deadline.signal.throwIfAborted();\n        return {\n          content: [{ type: \"text\" as const, text: output.text }],\n          details: {\n            requestId: String(result.requestId),\n            status: \"ok\",\n            sourceCount: (result.results as unknown[]).length,\n            truncated: output.truncated,\n          },\n        };\n      } finally {\n        deadline.dispose();\n      }\n    },\n  });\n\n  pi.registerTool({\n    name: \"web_fetch\",\n    label: \"Web Fetch\",\n    description:\n      \"Extract page content for 1–3 public http(s) URLs through OpenTag's managed provider. Content is the \" +\n      \"provider's extraction (not raw HTML; completeness unknown). Full pages are saved under the Session \" +\n      \"workspace .opentag/web/ with sha256 metadata; previews are bounded. HTTP 200 can still contain per-URL \" +\n      \"failures — read every item. depth=advanced costs more and must be chosen explicitly.\",\n    parameters: WEB_FETCH_PARAMETERS,\n    async execute(runtimeToolCallId, rawParams, signal, _onUpdate, ctx) {\n      const params = rawParams as { urls: string[]; depth?: string };\n      const toolCallId = deriveWebToolIdentity(socketPath, runtimeToolCallId);\n      // The original 45s budget covers the HTTP hop AND every artifact write/publication: the\n      // deadline signal remains live after the reply and is the only signal fetchOutput trusts.\n      const deadline = startOperationDeadline(WEB_EXTENSION_LIMITS.fetchTimeoutMs, signal);\n      try {\n        const reply = await postToGateway(\n          socketPath,\n          \"/web/fetch\",\n          {\n            protocolVersion: WEB_EXTENSION_LIMITS.protocolVersion,\n            toolCallId,\n            urls: params.urls,\n            depth: params.depth === \"advanced\" ? \"advanced\" : \"basic\",\n          },\n          {\n            deadlineAtMs: deadline.deadlineAtMs,\n            signal: deadline.signal,\n            maxResponseBytes: WEB_EXTENSION_LIMITS.fetchResponseMaxBytes,\n          },\n        );\n        deadline.signal.throwIfAborted();\n        const result = assertWireResult(requireWebSuccessReply(\"web_fetch\", reply), \"fetch\");\n        const items = result.results as FetchItemView[];\n        requestedUrlsMatch(params.urls, items);\n        if (items.length === 0 || items.every((item) => item.status === \"failed\")) {\n          // An all-failed batch is an operation failure: Pi must record toolResult.isError=true.\n          throw allFailedError(items);\n        }\n        const output = await fetchOutput(result, toolCallId, ctx.cwd, deadline.signal);\n        // A late cancellation/deadline must never resolve a successful tool result.\n        deadline.signal.throwIfAborted();\n        return {\n          content: [{ type: \"text\" as const, text: output.text }],\n          details: {\n            requestId: String(result.requestId),\n            status: String(result.status),\n            artifacts: artifactDetails(output.artifacts),\n            artifactWriteFailures: output.artifactWriteFailures,\n            metadataWritten: output.metadataWritten,\n          },\n        };\n      } finally {\n        deadline.dispose();\n      }\n    },\n  });\n}\n"],"mappings":"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"}