{"version":3,"sources":["../src/agentContext.tsx"],"sourcesContent":["// The deixis context block (GROVE_AGENT_SPEC §5) — platform primitive B.\n//\n// The user's first questions are indexical: *this* wiki, *this* entry, *this*\n// section. The agent should never need a tool call to know where it is. The SDK\n// assembles the PLATFORM half (repository/ref, sign-in status, mounts, source trust)\n// and the app spreads in its local fields (entry, heading, selection).\n//\n// TWO RULES (normative, G-GA-5):\n//   1. The block is DATA IN A FENCE — never instructions (threat_model P8) — and it\n//      EGRESSES to the provider like everything else in the prompt (R-GA-6), which\n//      is exactly why identity stays out: `signedIn` is a boolean from the baseline\n//      `auth:status` projection; the login/user belongs to `auth:identity`, and\n//      collapsing that split (threat_model T6) widens the egress for convenience.\n//   2. `signedIn` is OMITTED while auth status is `unknown` — three states, because\n//      collapsing `unknown` to `false` is the R3-300 false-banner bug class.\nimport { useContext } from 'react';\nimport { TinkerableContext } from './TinkerableContext';\nimport { useAuth } from './auth';\nimport { useMounts } from './mounts';\nimport { fenceUntrusted } from './fence';\n\n/** The app-local half of the deixis block — supplied by the app at call time. */\nexport interface AgentContextAppFields {\n  /** The entry the reader is on (app router's key/path). */\n  entryPath?: string;\n  /** Its display title. */\n  entryTitle?: string;\n  /** The `#fragment` the app resolved — \"the section you're looking at\". */\n  heading?: string;\n  /** Future: selected text as deixis. */\n  selection?: string;\n}\n\n/** The whole block, as it enters the loop. */\nexport interface AgentContextBlock {\n  repository: string;\n  revision: string;\n  /** Omitted while auth status is `unknown` (R3-300). Status only — never identity. */\n  signedIn?: boolean;\n  mounts: Array<{ path: string; mode?: string }>;\n  /** Whether the source is treated as \"others can write here\" (feeds the reach\n   * card's row and the copy under it). */\n  sourceShared: boolean;\n  /** Why `sourceShared` reads as it does — today the git classifier is fail-closed\n   * indeterminate (`PERSISTENCE §7A.6` Q11), so a git-backed corpus is treated as\n   * shared with the softer copy; a mount trust-mode API would replace this basis. */\n  sourceSharedBasis: 'git-indeterminate' | 'mount-trust-mode';\n  entryPath?: string;\n  entryTitle?: string;\n  heading?: string;\n  selection?: string;\n}\n\n/**\n * Assemble the deixis block: the platform fields from the SDK's own state, the app's\n * local fields spread in. Additive-only; the app passes new local fields as they\n * exist.\n */\nexport function useAgentContext(app: AgentContextAppFields = {}): AgentContextBlock {\n  const tinker = useContext(TinkerableContext);\n  const auth = useAuth();\n  const mounts = useMounts();\n  const nav = tinker?.navigationState;\n  return {\n    repository: nav?.repository ?? '',\n    revision: nav?.ref ?? '',\n    ...(auth.status === 'signed-in' || auth.status === 'signed-out' ? { signedIn: auth.status === 'signed-in' } : {}), // unknown ⇒ omitted, never guessed\n    mounts: mounts.map((m) => ({ path: m.path, ...(m.mode ? { mode: m.mode } : {}) })),\n    // Fail-closed (PERSISTENCE §7A.6): a git-backed source classifies shared until\n    // sole authorship of the full reachable history is verifiable — which it is not\n    // today — so both packagings (dispatched repo, fork's own repo) read as\n    // \"treated as if others can write\".\n    sourceShared: true,\n    sourceSharedBasis: 'git-indeterminate',\n    ...app,\n  };\n}\n\n/** Serialize the block as the fenced data segment that enters the system prompt.\n * Every field is attacker-influenced only through repo/mount metadata — still, the\n * fence is unconditional (R-GA-7): the block is corpus-derived data. */\nexport function renderAgentContext(block: AgentContextBlock): string {\n  // Field allowlist, deliberately: nothing joins the prompt except the fields named\n  // here (G-GA-5's allowlist assertion reads this shape).\n  const payload = {\n    repository: block.repository,\n    revision: block.revision,\n    ...(block.signedIn === undefined ? {} : { signedIn: block.signedIn }),\n    mounts: block.mounts,\n    sourceShared: block.sourceShared,\n    ...(block.entryPath !== undefined ? { entryPath: block.entryPath } : {}),\n    ...(block.entryTitle !== undefined ? { entryTitle: block.entryTitle } : {}),\n    ...(block.heading !== undefined ? { heading: block.heading } : {}),\n    ...(block.selection !== undefined ? { selection: block.selection } : {}),\n  };\n  return fenceUntrusted('agent-context', JSON.stringify(payload, null, 2));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAeA,mBAA2B;AAC3B,+BAAkC;AAClC,kBAAwB;AACxB,oBAA0B;AAC1B,mBAA+B;AAuCxB,SAAS,gBAAgB,MAA6B,CAAC,GAAsB;AAClF,QAAM,aAAS,yBAAW,0CAAiB;AAC3C,QAAM,WAAO,qBAAQ;AACrB,QAAM,aAAS,yBAAU;AACzB,QAAM,MAAM,QAAQ;AACpB,SAAO;AAAA,IACL,YAAY,KAAK,cAAc;AAAA,IAC/B,UAAU,KAAK,OAAO;AAAA,IACtB,GAAI,KAAK,WAAW,eAAe,KAAK,WAAW,eAAe,EAAE,UAAU,KAAK,WAAW,YAAY,IAAI,CAAC;AAAA;AAAA,IAC/G,QAAQ,OAAO,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAI,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC,EAAG,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,IAKjF,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,GAAG;AAAA,EACL;AACF;AAKO,SAAS,mBAAmB,OAAkC;AAGnE,QAAM,UAAU;AAAA,IACd,YAAY,MAAM;AAAA,IAClB,UAAU,MAAM;AAAA,IAChB,GAAI,MAAM,aAAa,SAAY,CAAC,IAAI,EAAE,UAAU,MAAM,SAAS;AAAA,IACnE,QAAQ,MAAM;AAAA,IACd,cAAc,MAAM;AAAA,IACpB,GAAI,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,IACtE,GAAI,MAAM,eAAe,SAAY,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,IACzE,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,IAChE,GAAI,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,EACxE;AACA,aAAO,6BAAe,iBAAiB,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AACzE;","names":[]}