@immediately-run/sdk 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MDXProvider.js +1 -0
- package/dist/MDXProvider.js.map +1 -1
- package/dist/TinkerableContext.js +1 -0
- package/dist/TinkerableContext.js.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/boot.js +1 -0
- package/dist/boot.js.map +1 -1
- package/dist/catalog.js +1 -0
- package/dist/catalog.js.map +1 -1
- package/dist/chunk-VHAA22YE.js +14 -0
- package/dist/chunk-VHAA22YE.js.map +1 -0
- package/dist/components/Admonition.js +1 -0
- package/dist/components/Admonition.js.map +1 -1
- package/dist/components/FileRouter.js +1 -0
- package/dist/components/FileRouter.js.map +1 -1
- package/dist/components/HeadingAnchor.js +1 -0
- package/dist/components/HeadingAnchor.js.map +1 -1
- package/dist/components/Include.js +1 -0
- package/dist/components/Include.js.map +1 -1
- package/dist/components/Link.js +1 -0
- package/dist/components/Link.js.map +1 -1
- package/dist/components/MDXComponents.js +1 -0
- package/dist/components/MDXComponents.js.map +1 -1
- package/dist/components/MainContent.js +1 -0
- package/dist/components/MainContent.js.map +1 -1
- package/dist/components/MountImage.js +1 -0
- package/dist/components/MountImage.js.map +1 -1
- package/dist/components/Routes.js +1 -0
- package/dist/components/Routes.js.map +1 -1
- package/dist/components/ScrollAfterNavigation.js +1 -0
- package/dist/components/ScrollAfterNavigation.js.map +1 -1
- package/dist/components/WikiLink.js +1 -0
- package/dist/components/WikiLink.js.map +1 -1
- package/dist/components/defaults.js +1 -0
- package/dist/components/defaults.js.map +1 -1
- package/dist/components/errors.js +1 -0
- package/dist/components/errors.js.map +1 -1
- package/dist/contextUtils.js +1 -0
- package/dist/contextUtils.js.map +1 -1
- package/dist/contribute.js +1 -0
- package/dist/contribute.js.map +1 -1
- package/dist/debug.js +1 -0
- package/dist/debug.js.map +1 -1
- package/dist/diagnostics.js +1 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/dnd.js +1 -0
- package/dist/dnd.js.map +1 -1
- package/dist/editor.js +1 -0
- package/dist/editor.js.map +1 -1
- package/dist/editorContext.js +1 -0
- package/dist/editorContext.js.map +1 -1
- package/dist/formFactor.js +1 -0
- package/dist/formFactor.js.map +1 -1
- package/dist/fs.js +1 -0
- package/dist/fs.js.map +1 -1
- package/dist/hooks.js +1 -0
- package/dist/hooks.js.map +1 -1
- package/dist/hostRuntime.js +1 -0
- package/dist/hostRuntime.js.map +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/injectedBundler.js +1 -0
- package/dist/injectedBundler.js.map +1 -1
- package/dist/ipc.js +1 -0
- package/dist/ipc.js.map +1 -1
- package/dist/irMarkers.js +1 -0
- package/dist/irMarkers.js.map +1 -1
- package/dist/launch.cjs +95 -0
- package/dist/launch.cjs.map +1 -0
- package/dist/launch.d.cts +76 -0
- package/dist/launch.d.ts +76 -0
- package/dist/launch.js +69 -0
- package/dist/launch.js.map +1 -0
- package/dist/llm.js +1 -0
- package/dist/llm.js.map +1 -1
- package/dist/loading.js +1 -0
- package/dist/loading.js.map +1 -1
- package/dist/markers.js +1 -0
- package/dist/markers.js.map +1 -1
- package/dist/moduleCache.js +1 -0
- package/dist/moduleCache.js.map +1 -1
- package/dist/mountMatch.js +1 -0
- package/dist/mountMatch.js.map +1 -1
- package/dist/mounts.cjs +28 -4
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +29 -4
- package/dist/mounts.d.ts +29 -4
- package/dist/mounts.js +28 -4
- package/dist/mounts.js.map +1 -1
- package/dist/netFetch.js +1 -0
- package/dist/netFetch.js.map +1 -1
- package/dist/onFsChange.js +1 -0
- package/dist/onFsChange.js.map +1 -1
- package/dist/pathUtils.js +1 -0
- package/dist/pathUtils.js.map +1 -1
- package/dist/protocolStream.js +1 -0
- package/dist/protocolStream.js.map +1 -1
- package/dist/pushChannel.js +1 -0
- package/dist/pushChannel.js.map +1 -1
- package/dist/ready.js +1 -0
- package/dist/ready.js.map +1 -1
- package/dist/region.js +1 -0
- package/dist/region.js.map +1 -1
- package/dist/routeMatch.js +1 -0
- package/dist/routeMatch.js.map +1 -1
- package/dist/routing.js +1 -0
- package/dist/routing.js.map +1 -1
- package/dist/runtime.js +1 -0
- package/dist/runtime.js.map +1 -1
- package/dist/safeContent/SafeContent.js +1 -0
- package/dist/safeContent/SafeContent.js.map +1 -1
- package/dist/safeContent/index.js +1 -0
- package/dist/safeContent/index.js.map +1 -1
- package/dist/safeContent/parseSafeMdast.js +1 -0
- package/dist/safeContent/parseSafeMdast.js.map +1 -1
- package/dist/safeContent/renderMdast.js +1 -0
- package/dist/safeContent/renderMdast.js.map +1 -1
- package/dist/safeContent/sanitizeUrl.js +1 -0
- package/dist/safeContent/sanitizeUrl.js.map +1 -1
- package/dist/safeContent/wikilink.js +1 -0
- package/dist/safeContent/wikilink.js.map +1 -1
- package/dist/sandboxUtils.js +1 -0
- package/dist/sandboxUtils.js.map +1 -1
- package/dist/scrollToId.js +1 -0
- package/dist/scrollToId.js.map +1 -1
- package/dist/secrets.js +1 -0
- package/dist/secrets.js.map +1 -1
- package/dist/tasks.js +1 -0
- package/dist/tasks.js.map +1 -1
- package/dist/testing.js +1 -0
- package/dist/testing.js.map +1 -1
- package/dist/theme.js +1 -0
- package/dist/theme.js.map +1 -1
- package/dist/urlUtils.js +1 -0
- package/dist/urlUtils.js.map +1 -1
- package/dist/vcs.js +1 -0
- package/dist/vcs.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safeContent/parseSafeMdast.ts"],"sourcesContent":["// The no-acorn parse pipeline for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// empirically verified 2026-07-07 against `@mdx-js/mdx` v3 / `micromark-extension-\n// mdx-jsx`). This is the load-bearing half of the fail-safe: `<Component/>` syntax\n// parses to an element node with **literal** attributes, and every expression\n// (`n={1+1}`, `f={fetch(\"/x\")}`, `{...spread}`) is captured as an **inert raw string\n// with no estree** — because we pass **NO acorn option** to `micromark-extension-mdx-\n// jsx`. There is therefore no evaluator anywhere in the pipeline (fail-safe), unlike\n// `@mdx-js/mdx`'s `compile({format:'mdx'})` which always wires acorn.\n//\n// The §5.1 residual guarantees are also here: the expression micromark extension is\n// **not** enabled (body `{…}` stays literal text), and **no `rehype-raw`** / raw-HTML\n// passthrough exists — raw HTML blocks arrive as inert `html` mdast nodes the renderer\n// prints as text, never injected.\n//\n// `micromark`/`mdast` are ESM-only. They are loaded with a memoised dynamic `import()`\n// (the transpiler's `compile.ts` pattern) so this module sits in the package's CJS\n// build too — a CJS consumer that never renders untrusted content never `require()`s\n// an ESM-only package, and one that does gets it via Node's ESM-from-CJS import. The\n// dynamic load changes only *when* the deps resolve, never the produced tree.\n//\n// The dynamic import targets `./mdastDeps` — a SINGLE self-contained artifact that a\n// dedicated build pass (`scripts/build-safecontent-deps.mjs`) inlines the whole ESM tree\n// into (see `mdastDeps.ts` for the WHY). This keeps the `import()` boundary (a consumer\n// that never renders safe content never loads the ~300KB parser) while making the imported\n// target one already-bundled file the immediately.run sandbox resolver can fetch without\n// walking the unified/micromark conditional-exports tree — the R3-213 on-host packaging\n// gap. In jest/ts-jest and the e2e bundle the same `./mdastDeps` resolves to the source\n// re-export module, which pulls the real deps from node_modules; the produced tree is\n// identical either way.\n\n// Type-only — the plugin VALUES are re-exported from (and inlined by) `./mdastDeps`; a\n// value import here would put a bare `@immediately-run/mdx-plugins` specifier in the dist\n// the sandbox resolver can't resolve on-host. `import type` is fully erased at build.\nimport type { HeadingAnchorOptions } from '@immediately-run/mdx-plugins';\n\n// A minimal structural mdast shape — we deliberately avoid a type dependency on the\n// ESM-only `mdast`/`unist` packages (which would break the CJS build's type surface).\nexport interface SafeMdastNode {\n type: string;\n value?: string;\n name?: string | null;\n url?: string;\n alt?: string | null;\n title?: string | null;\n depth?: number;\n ordered?: boolean;\n lang?: string | null;\n children?: SafeMdastNode[];\n attributes?: SafeMdxAttribute[];\n [key: string]: unknown;\n}\n\nexport interface SafeMdxAttribute {\n type: 'mdxJsxAttribute' | 'mdxJsxExpressionAttribute';\n name?: string;\n // A literal attribute carries a string; an expression attribute carries an\n // `mdxJsxAttributeValueExpression` object (a raw string, NO estree) or a string.\n value?: string | { type: string; value?: string } | null;\n}\n\n// `unified`'s `Plugin` type widens the return to `Transformer | void`; the safe path\n// invokes each transformer directly on the tree, so narrow it to a plain in-place mutator.\ntype MdastMutator = (tree: SafeMdastNode) => void;\n\ntype Deps = {\n fromMarkdown: (\n value: string,\n options: { extensions: unknown[]; mdastExtensions: unknown[] },\n ) => SafeMdastNode;\n mdxJsx: () => unknown;\n mdxJsxFromMarkdown: () => unknown;\n gfm: () => unknown;\n gfmFromMarkdown: () => unknown;\n // The three shared kernel remark plugins, also inlined into `./mdastDeps` (see there).\n remarkAdmonitions: () => MdastMutator;\n remarkWikiLinks: () => MdastMutator;\n remarkHeadingAnchors: (o?: HeadingAnchorOptions) => MdastMutator;\n};\n\nlet depsPromise: Promise<Deps> | null = null;\n\nfunction loadDeps(): Promise<Deps> {\n if (!depsPromise) {\n // ONE dynamic import of the pre-inlined artifact (see the header note + `mdastDeps.ts`)\n // — the five ESM parser packages AND the shared remark plugins, none of which the\n // sandbox resolver can walk/resolve as bare specifiers on-host.\n depsPromise = import('./mdastDeps').then((m) => {\n const d = m as unknown as Deps;\n return {\n fromMarkdown: d.fromMarkdown,\n mdxJsx: d.mdxJsx,\n mdxJsxFromMarkdown: d.mdxJsxFromMarkdown,\n gfm: d.gfm,\n gfmFromMarkdown: d.gfmFromMarkdown,\n remarkAdmonitions: d.remarkAdmonitions,\n remarkWikiLinks: d.remarkWikiLinks,\n remarkHeadingAnchors: d.remarkHeadingAnchors,\n } as Deps;\n });\n }\n return depsPromise;\n}\n\nexport interface ParseSafeMdastOptions {\n /** `false` ⇒ headings use plain text-slug ids (the R3-186 base; no `sec-…` section\n * ids, no `data-slug`) — the `sectionIds:false` frontmatter opt-out, matching the\n * compiled path. Default (undefined/true) ⇒ section-like headings get `sec-…` ids\n * (R3-211). */\n sectionIds?: boolean;\n}\n\n/**\n * Parse untrusted Markdown/MDX-syntax source to an mdast tree with **no evaluator in\n * the pipeline**: JSX-as-data (no acorn), GFM on, the expression extension OFF, no\n * raw-HTML re-parse. The returned tree is safe to hand to `renderMdast` — expression\n * attributes are inert strings and raw HTML is inert `html` nodes.\n *\n * After the no-acorn parse it runs the SHARED kernel remark plugins — in the SAME order\n * as the compiled path's `compile.ts` (admonitions §12 → wiki-links §13 → heading/section\n * anchors §15/R3-211; GFM is already applied via the micromark extension above) — so the\n * safe subset renders identically in both standards. The plugins only emit element nodes\n * with LITERAL attributes and set `data.hProperties.id` on headings; they add no evaluator\n * and no acorn, preserving the §5.1 fail-safe.\n */\nexport async function parseSafeMdast(\n source: string,\n options: ParseSafeMdastOptions = {},\n): Promise<SafeMdastNode> {\n const {\n fromMarkdown,\n mdxJsx,\n mdxJsxFromMarkdown,\n gfm,\n gfmFromMarkdown,\n remarkAdmonitions,\n remarkWikiLinks,\n remarkHeadingAnchors,\n } = await loadDeps();\n const tree = fromMarkdown(source, {\n // `mdxJsx()` WITHOUT an acorn option → JSX tags + literal attrs; expressions are\n // raw strings, never estree. The mdx *expression* extension is intentionally\n // absent, so `{…}` in body text stays literal. GFM for tables/task-lists.\n extensions: [mdxJsx(), gfm()],\n mdastExtensions: [mdxJsxFromMarkdown(), gfmFromMarkdown()],\n });\n // The SHARED kernel remark plugins, in the SAME order as the compiled path's `compile.ts`\n // (admonitions §12 → wiki-links §13 → heading/section anchors §15/R3-211).\n remarkAdmonitions()(tree);\n remarkWikiLinks()(tree);\n remarkHeadingAnchors({ sectionIds: options.sectionIds !== false })(tree);\n return tree;\n}\n"],"mappings":"AA+EA,IAAI,cAAoC;AAExC,SAAS,WAA0B;AACjC,MAAI,CAAC,aAAa;AAIhB,kBAAc,OAAO,aAAa,EAAE,KAAK,CAAC,MAAM;AAC9C,YAAM,IAAI;AACV,aAAO;AAAA,QACL,cAAc,EAAE;AAAA,QAChB,QAAQ,EAAE;AAAA,QACV,oBAAoB,EAAE;AAAA,QACtB,KAAK,EAAE;AAAA,QACP,iBAAiB,EAAE;AAAA,QACnB,mBAAmB,EAAE;AAAA,QACrB,iBAAiB,EAAE;AAAA,QACnB,sBAAsB,EAAE;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAuBA,eAAsB,eACpB,QACA,UAAiC,CAAC,GACV;AACxB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM,SAAS;AACnB,QAAM,OAAO,aAAa,QAAQ;AAAA;AAAA;AAAA;AAAA,IAIhC,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,IAC5B,iBAAiB,CAAC,mBAAmB,GAAG,gBAAgB,CAAC;AAAA,EAC3D,CAAC;AAGD,oBAAkB,EAAE,IAAI;AACxB,kBAAgB,EAAE,IAAI;AACtB,uBAAqB,EAAE,YAAY,QAAQ,eAAe,MAAM,CAAC,EAAE,IAAI;AACvE,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safeContent/parseSafeMdast.ts"],"sourcesContent":["// The no-acorn parse pipeline for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// empirically verified 2026-07-07 against `@mdx-js/mdx` v3 / `micromark-extension-\n// mdx-jsx`). This is the load-bearing half of the fail-safe: `<Component/>` syntax\n// parses to an element node with **literal** attributes, and every expression\n// (`n={1+1}`, `f={fetch(\"/x\")}`, `{...spread}`) is captured as an **inert raw string\n// with no estree** — because we pass **NO acorn option** to `micromark-extension-mdx-\n// jsx`. There is therefore no evaluator anywhere in the pipeline (fail-safe), unlike\n// `@mdx-js/mdx`'s `compile({format:'mdx'})` which always wires acorn.\n//\n// The §5.1 residual guarantees are also here: the expression micromark extension is\n// **not** enabled (body `{…}` stays literal text), and **no `rehype-raw`** / raw-HTML\n// passthrough exists — raw HTML blocks arrive as inert `html` mdast nodes the renderer\n// prints as text, never injected.\n//\n// `micromark`/`mdast` are ESM-only. They are loaded with a memoised dynamic `import()`\n// (the transpiler's `compile.ts` pattern) so this module sits in the package's CJS\n// build too — a CJS consumer that never renders untrusted content never `require()`s\n// an ESM-only package, and one that does gets it via Node's ESM-from-CJS import. The\n// dynamic load changes only *when* the deps resolve, never the produced tree.\n//\n// The dynamic import targets `./mdastDeps` — a SINGLE self-contained artifact that a\n// dedicated build pass (`scripts/build-safecontent-deps.mjs`) inlines the whole ESM tree\n// into (see `mdastDeps.ts` for the WHY). This keeps the `import()` boundary (a consumer\n// that never renders safe content never loads the ~300KB parser) while making the imported\n// target one already-bundled file the immediately.run sandbox resolver can fetch without\n// walking the unified/micromark conditional-exports tree — the R3-213 on-host packaging\n// gap. In jest/ts-jest and the e2e bundle the same `./mdastDeps` resolves to the source\n// re-export module, which pulls the real deps from node_modules; the produced tree is\n// identical either way.\n\n// Type-only — the plugin VALUES are re-exported from (and inlined by) `./mdastDeps`; a\n// value import here would put a bare `@immediately-run/mdx-plugins` specifier in the dist\n// the sandbox resolver can't resolve on-host. `import type` is fully erased at build.\nimport type { HeadingAnchorOptions } from '@immediately-run/mdx-plugins';\n\n// A minimal structural mdast shape — we deliberately avoid a type dependency on the\n// ESM-only `mdast`/`unist` packages (which would break the CJS build's type surface).\nexport interface SafeMdastNode {\n type: string;\n value?: string;\n name?: string | null;\n url?: string;\n alt?: string | null;\n title?: string | null;\n depth?: number;\n ordered?: boolean;\n lang?: string | null;\n children?: SafeMdastNode[];\n attributes?: SafeMdxAttribute[];\n [key: string]: unknown;\n}\n\nexport interface SafeMdxAttribute {\n type: 'mdxJsxAttribute' | 'mdxJsxExpressionAttribute';\n name?: string;\n // A literal attribute carries a string; an expression attribute carries an\n // `mdxJsxAttributeValueExpression` object (a raw string, NO estree) or a string.\n value?: string | { type: string; value?: string } | null;\n}\n\n// `unified`'s `Plugin` type widens the return to `Transformer | void`; the safe path\n// invokes each transformer directly on the tree, so narrow it to a plain in-place mutator.\ntype MdastMutator = (tree: SafeMdastNode) => void;\n\ntype Deps = {\n fromMarkdown: (\n value: string,\n options: { extensions: unknown[]; mdastExtensions: unknown[] },\n ) => SafeMdastNode;\n mdxJsx: () => unknown;\n mdxJsxFromMarkdown: () => unknown;\n gfm: () => unknown;\n gfmFromMarkdown: () => unknown;\n // The three shared kernel remark plugins, also inlined into `./mdastDeps` (see there).\n remarkAdmonitions: () => MdastMutator;\n remarkWikiLinks: () => MdastMutator;\n remarkHeadingAnchors: (o?: HeadingAnchorOptions) => MdastMutator;\n};\n\nlet depsPromise: Promise<Deps> | null = null;\n\nfunction loadDeps(): Promise<Deps> {\n if (!depsPromise) {\n // ONE dynamic import of the pre-inlined artifact (see the header note + `mdastDeps.ts`)\n // — the five ESM parser packages AND the shared remark plugins, none of which the\n // sandbox resolver can walk/resolve as bare specifiers on-host.\n depsPromise = import('./mdastDeps').then((m) => {\n const d = m as unknown as Deps;\n return {\n fromMarkdown: d.fromMarkdown,\n mdxJsx: d.mdxJsx,\n mdxJsxFromMarkdown: d.mdxJsxFromMarkdown,\n gfm: d.gfm,\n gfmFromMarkdown: d.gfmFromMarkdown,\n remarkAdmonitions: d.remarkAdmonitions,\n remarkWikiLinks: d.remarkWikiLinks,\n remarkHeadingAnchors: d.remarkHeadingAnchors,\n } as Deps;\n });\n }\n return depsPromise;\n}\n\nexport interface ParseSafeMdastOptions {\n /** `false` ⇒ headings use plain text-slug ids (the R3-186 base; no `sec-…` section\n * ids, no `data-slug`) — the `sectionIds:false` frontmatter opt-out, matching the\n * compiled path. Default (undefined/true) ⇒ section-like headings get `sec-…` ids\n * (R3-211). */\n sectionIds?: boolean;\n}\n\n/**\n * Parse untrusted Markdown/MDX-syntax source to an mdast tree with **no evaluator in\n * the pipeline**: JSX-as-data (no acorn), GFM on, the expression extension OFF, no\n * raw-HTML re-parse. The returned tree is safe to hand to `renderMdast` — expression\n * attributes are inert strings and raw HTML is inert `html` nodes.\n *\n * After the no-acorn parse it runs the SHARED kernel remark plugins — in the SAME order\n * as the compiled path's `compile.ts` (admonitions §12 → wiki-links §13 → heading/section\n * anchors §15/R3-211; GFM is already applied via the micromark extension above) — so the\n * safe subset renders identically in both standards. The plugins only emit element nodes\n * with LITERAL attributes and set `data.hProperties.id` on headings; they add no evaluator\n * and no acorn, preserving the §5.1 fail-safe.\n */\nexport async function parseSafeMdast(\n source: string,\n options: ParseSafeMdastOptions = {},\n): Promise<SafeMdastNode> {\n const {\n fromMarkdown,\n mdxJsx,\n mdxJsxFromMarkdown,\n gfm,\n gfmFromMarkdown,\n remarkAdmonitions,\n remarkWikiLinks,\n remarkHeadingAnchors,\n } = await loadDeps();\n const tree = fromMarkdown(source, {\n // `mdxJsx()` WITHOUT an acorn option → JSX tags + literal attrs; expressions are\n // raw strings, never estree. The mdx *expression* extension is intentionally\n // absent, so `{…}` in body text stays literal. GFM for tables/task-lists.\n extensions: [mdxJsx(), gfm()],\n mdastExtensions: [mdxJsxFromMarkdown(), gfmFromMarkdown()],\n });\n // The SHARED kernel remark plugins, in the SAME order as the compiled path's `compile.ts`\n // (admonitions §12 → wiki-links §13 → heading/section anchors §15/R3-211).\n remarkAdmonitions()(tree);\n remarkWikiLinks()(tree);\n remarkHeadingAnchors({ sectionIds: options.sectionIds !== false })(tree);\n return tree;\n}\n"],"mappings":";AA+EA,IAAI,cAAoC;AAExC,SAAS,WAA0B;AACjC,MAAI,CAAC,aAAa;AAIhB,kBAAc,OAAO,aAAa,EAAE,KAAK,CAAC,MAAM;AAC9C,YAAM,IAAI;AACV,aAAO;AAAA,QACL,cAAc,EAAE;AAAA,QAChB,QAAQ,EAAE;AAAA,QACV,oBAAoB,EAAE;AAAA,QACtB,KAAK,EAAE;AAAA,QACP,iBAAiB,EAAE;AAAA,QACnB,mBAAmB,EAAE;AAAA,QACrB,iBAAiB,EAAE;AAAA,QACnB,sBAAsB,EAAE;AAAA,MAC1B;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAuBA,eAAsB,eACpB,QACA,UAAiC,CAAC,GACV;AACxB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,MAAM,SAAS;AACnB,QAAM,OAAO,aAAa,QAAQ;AAAA;AAAA;AAAA;AAAA,IAIhC,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC;AAAA,IAC5B,iBAAiB,CAAC,mBAAmB,GAAG,gBAAgB,CAAC;AAAA,EAC3D,CAAC;AAGD,oBAAkB,EAAE,IAAI;AACxB,kBAAgB,EAAE,IAAI;AACtB,uBAAqB,EAAE,YAAY,QAAQ,eAAe,MAAM,CAAC,EAAE,IAAI;AACvE,SAAO;AACT;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, resolve: RenderMdastOptions['resolveWikiLink']): ReactNode {\n const label = token.label ?? token.target;\n const href = resolve?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement('a', { href: safe, 'data-wikilink': token.target, key: nextKey() }, label);\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts.resolveWikiLink)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n // R3-213: the no-acorn path has no hast stage, so the heading id the shared\n // `remarkHeadingAnchors` plugin sets via `data.hProperties.id` (+ the `data-slug`\n // fallback, R3-211) must be translated to React props HERE, or headings render\n // id-less and deep-linking (`#sec-8-9`) breaks in the safe path. These are\n // plugin-computed literal strings, not author input — safe to pass through.\n const hp = (node.data as { hProperties?: Record<string, unknown> } | undefined)?.hProperties;\n const headingProps: Record<string, string> = {};\n if (typeof hp?.id === 'string') headingProps.id = hp.id;\n if (typeof hp?.['data-slug'] === 'string') headingProps['data-slug'] = hp['data-slug'];\n return createElement(tag, { key, ...headingProps }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement('a', { key, href, title: node.title ?? undefined }, ...renderChildren(node, opts));\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement('img', { key, src, alt: node.alt ?? '', title: node.title ?? undefined });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safeContent/renderMdast.tsx"],"sourcesContent":["import { createElement, Fragment, type ReactNode } from 'react';\nimport { sanitizeUrl } from './sanitizeUrl';\nimport { splitWikiLinks, type WikiLinkToken } from './wikilink';\nimport type { SafeMdastNode, SafeMdxAttribute } from './parseSafeMdast';\n\n// The mdast→React renderer for the safe content path (TRUST_MODES_SPEC §5.1). PURE\n// and synchronous — it only reads `node.type` off an already-parsed tree, so it\n// carries no ESM dep and is exhaustively unit-testable. Every security property lives\n// here:\n//\n// - **JSX tags resolve ONLY to a component registry, by name.** A `<Component/>`\n// node becomes `registry[name]` with **literal string props only**; an unknown tag\n// renders its children inert (a Fragment, no element) — so author-written `<div\n// onclick=…>`, `<img onerror=…>`, `<script>` never become an intrinsic element with\n// attacker attributes. (Block-level raw HTML arrives as an `html` node, below.)\n// - **Expression attributes are dropped.** `f={fetch(\"/x\")}` / `{...spread}` are\n// `mdxJsxExpressionAttribute` or object-valued `mdxJsxAttribute`s — never passed to\n// a component, never evaluated. (There is also no evaluator to reach — parse used\n// no acorn.)\n// - **Raw HTML is inert text.** `html` nodes render as their literal string via a\n// text node — never `dangerouslySetInnerHTML` (no `rehype-raw`).\n// - **URL-scheme sanitizer** on every `link`/`image` URL (`sanitizeUrl`).\n// - **Wikilinks resolve in-mount only** via an injected resolver; the renderer never\n// fetches.\n\nexport interface SafeContentComponents {\n /** Safe components an app exposes to `<Component/>` syntax (host/app-provided).\n * Looked up by the JSX tag name; anything not here renders inert. */\n [tag: string]: React.ComponentType<Record<string, string>>;\n}\n\nexport interface RenderMdastOptions {\n /** The component registry for `<Component/>` syntax. Absent ⇒ all JSX tags inert. */\n components?: SafeContentComponents;\n /**\n * Resolve a wikilink target to an href, **within the granted mount only** — a pure,\n * synchronous, mount-scoped lookup that MUST NOT fetch or reach out of the mount.\n * Return `undefined` for an unresolvable/out-of-mount target (rendered as inert\n * text). Absent ⇒ every wikilink renders as inert text.\n */\n resolveWikiLink?: (target: string) => string | undefined;\n}\n\n/** Extract the literal string props of a JSX element — `mdxJsxAttribute`s whose value\n * is a plain string. Expression attributes (object value) and spreads are DROPPED. */\nfunction literalProps(attributes: SafeMdxAttribute[] | undefined): Record<string, string> {\n const props: Record<string, string> = {};\n for (const attr of attributes ?? []) {\n if (attr.type !== 'mdxJsxAttribute') continue; // drop `{...spread}`\n if (typeof attr.name !== 'string') continue;\n // A literal attribute's value is a string (or null → boolean-ish `true`). An\n // expression value is an object (`mdxJsxAttributeValueExpression`) — DROP it: it\n // is an inert raw string, never a real value, and must never reach a component.\n if (typeof attr.value === 'string') props[attr.name] = attr.value;\n else if (attr.value === null || attr.value === undefined) props[attr.name] = '';\n // object-valued (expression) → skipped\n }\n return props;\n}\n\nlet keyCounter = 0;\nconst nextKey = () => `sc-${keyCounter++}`;\n\n/** Render a wikilink token via the in-mount resolver, or inert text when it can't\n * resolve (never a network call — resolution is the injected mount-scoped callback). */\nfunction renderWiki(token: WikiLinkToken, resolve: RenderMdastOptions['resolveWikiLink']): ReactNode {\n const label = token.label ?? token.target;\n const href = resolve?.(token.target);\n const safe = href !== undefined ? sanitizeUrl(href) : undefined;\n if (safe === undefined) return label; // inert text — unresolved or out-of-mount\n return createElement('a', { href: safe, 'data-wikilink': token.target, key: nextKey() }, label);\n}\n\n/** Render a `text` node, splitting any `[[…]]` wikilinks out of it. */\nfunction renderText(value: string, opts: RenderMdastOptions): ReactNode {\n const parts = splitWikiLinks(value);\n if (!parts) return value;\n return parts.map((p, i) =>\n 'text' in p\n ? createElement(Fragment, { key: `t${i}` }, p.text)\n : createElement(Fragment, { key: `w${i}` }, renderWiki(p.wiki, opts.resolveWikiLink)),\n );\n}\n\nfunction renderChildren(node: SafeMdastNode, opts: RenderMdastOptions): ReactNode[] {\n return (node.children ?? []).map((c, i) => renderNode(c, opts, i));\n}\n\n// Standard mdast block/inline nodes → a FIXED, safe React element. We control every\n// attribute; author input only reaches text content and (sanitized) URLs.\nconst HEADING_TAGS = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const;\n\nfunction renderNode(node: SafeMdastNode, opts: RenderMdastOptions, index = 0): ReactNode {\n const key = `n${index}`;\n switch (node.type) {\n case 'root':\n return createElement(Fragment, { key }, ...renderChildren(node, opts));\n case 'text':\n return renderText(node.value ?? '', opts);\n case 'paragraph':\n return createElement('p', { key }, ...renderChildren(node, opts));\n case 'heading': {\n const tag = HEADING_TAGS[Math.min(Math.max((node.depth ?? 1) - 1, 0), 5)];\n // R3-213: the no-acorn path has no hast stage, so the heading id the shared\n // `remarkHeadingAnchors` plugin sets via `data.hProperties.id` (+ the `data-slug`\n // fallback, R3-211) must be translated to React props HERE, or headings render\n // id-less and deep-linking (`#sec-8-9`) breaks in the safe path. These are\n // plugin-computed literal strings, not author input — safe to pass through.\n const hp = (node.data as { hProperties?: Record<string, unknown> } | undefined)?.hProperties;\n const headingProps: Record<string, string> = {};\n if (typeof hp?.id === 'string') headingProps.id = hp.id;\n if (typeof hp?.['data-slug'] === 'string') headingProps['data-slug'] = hp['data-slug'];\n return createElement(tag, { key, ...headingProps }, ...renderChildren(node, opts));\n }\n case 'strong':\n return createElement('strong', { key }, ...renderChildren(node, opts));\n case 'emphasis':\n return createElement('em', { key }, ...renderChildren(node, opts));\n case 'delete':\n return createElement('del', { key }, ...renderChildren(node, opts));\n case 'inlineCode':\n return createElement('code', { key }, node.value ?? '');\n case 'code':\n return createElement('pre', { key }, createElement('code', null, node.value ?? ''));\n case 'blockquote':\n return createElement('blockquote', { key }, ...renderChildren(node, opts));\n case 'list':\n return createElement(node.ordered ? 'ol' : 'ul', { key }, ...renderChildren(node, opts));\n case 'listItem':\n return createElement('li', { key }, ...renderChildren(node, opts));\n case 'thematicBreak':\n return createElement('hr', { key });\n case 'break':\n return createElement('br', { key });\n case 'link': {\n const href = sanitizeUrl(node.url);\n // A rejected scheme → render the link TEXT only (inert), never an <a href>.\n if (href === undefined) return createElement(Fragment, { key }, ...renderChildren(node, opts));\n return createElement('a', { key, href, title: node.title ?? undefined }, ...renderChildren(node, opts));\n }\n case 'image': {\n const src = sanitizeUrl(node.url);\n if (src === undefined) return node.alt ? createElement(Fragment, { key }, node.alt) : null;\n return createElement('img', { key, src, alt: node.alt ?? '', title: node.title ?? undefined });\n }\n // GFM tables.\n case 'table':\n return createElement('table', { key }, createElement('tbody', null, ...renderChildren(node, opts)));\n case 'tableRow':\n return createElement('tr', { key }, ...renderChildren(node, opts));\n case 'tableCell':\n return createElement('td', { key }, ...renderChildren(node, opts));\n // Raw HTML (`<script>`, `<div onclick>` at block level) → INERT TEXT. No\n // `rehype-raw`, never `dangerouslySetInnerHTML`. The literal markup is shown, not run.\n case 'html':\n return createElement(Fragment, { key }, node.value ?? '');\n // JSX `<Component/>` syntax → registry lookup by NAME, literal props only.\n case 'mdxJsxFlowElement':\n case 'mdxJsxTextElement': {\n const name = typeof node.name === 'string' ? node.name : '';\n const Component = name ? opts.components?.[name] : undefined;\n const children = renderChildren(node, opts);\n // Unknown tag (not in the registry) OR a fragment `<>` → render children inert,\n // NO element, NO author attributes. This is what neutralizes `<script>`/`<div\n // onclick>`/`<img onerror>` written as JSX.\n if (!Component) return createElement(Fragment, { key }, ...children);\n return createElement(Component, { key, ...literalProps(node.attributes) }, ...children);\n }\n // Inert expression nodes (should not occur — expression extension is off — but be\n // defensive if a tree from elsewhere carries them): render nothing.\n case 'mdxFlowExpression':\n case 'mdxTextExpression':\n case 'mdxjsEsm':\n return null;\n default:\n // Unknown node → render its children (never its raw value as markup).\n return node.children ? createElement(Fragment, { key }, ...renderChildren(node, opts)) : null;\n }\n}\n\n/**\n * Render a safe-parsed mdast tree to React. Pure and synchronous; carries every §5.1\n * security property (registry-only JSX, dropped expressions, inert raw HTML, URL\n * sanitizing, in-mount wikilinks). Feed it a tree from {@link parseSafeMdast}.\n */\nexport function renderMdast(tree: SafeMdastNode, options: RenderMdastOptions = {}): ReactNode {\n keyCounter = 0;\n return renderNode(tree, options);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safeContent/sanitizeUrl.ts"],"sourcesContent":["// URL-scheme sanitizer for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// AGENT_AUTHORING §10). A §5.1 residual requirement: link/image URLs in untrusted\n// content are allowlisted to `http`/`https`/`mailto` and relative URLs; every other\n// scheme — `javascript:`, `data:`, `vbscript:`, `file:`, … — is rejected. We do NOT\n// rely on React to neutralize `javascript:` (it does not, on an `<a href>`), so this\n// is the single chokepoint every rendered `href`/`src` passes through.\n\n// A leading `scheme:` per RFC 3986 (`ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" )`).\n// Anything before the first `:` that matches this is an absolute-URI scheme; if the\n// URL has no such prefix it is relative (path/query/fragment) and allowed.\nconst SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;\nconst ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto']);\n\n// ASCII control chars (0x00–0x1F, 0x7F) that browsers ignore INSIDE a scheme — the\n// `java\\tscript:` / `java\\nscript:` obfuscation — stripped anywhere before testing.\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\u0000-\\u001F\\u007F]/g;\n\n/**\n * Return the URL if its scheme is allowed (or it is relative/anchor), else\n * `undefined`. Control chars are stripped first so an obfuscated scheme cannot slip\n * past. A protocol-relative `//host` URL is treated as relative-with-authority and\n * allowed (it inherits the page's https scheme).\n */\nexport function sanitizeUrl(url: unknown): string | undefined {\n if (typeof url !== 'string') return undefined;\n const cleaned = url.replace(CONTROL_CHARS, '').trim();\n if (cleaned === '') return undefined;\n const m = SCHEME.exec(cleaned);\n if (!m) return cleaned; // no scheme → relative / anchor / query — allowed\n return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? cleaned : undefined;\n}\n"],"mappings":"AAUA,MAAM,SAAS;AACf,MAAM,kBAAkB,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAK3D,MAAM,gBAAgB;AAQf,SAAS,YAAY,KAAkC;AAC5D,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,UAAU,IAAI,QAAQ,eAAe,EAAE,EAAE,KAAK;AACpD,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,IAAI,OAAO,KAAK,OAAO;AAC7B,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,gBAAgB,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,UAAU;AAC7D;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safeContent/sanitizeUrl.ts"],"sourcesContent":["// URL-scheme sanitizer for the safe content renderer (TRUST_MODES_SPEC §5.1,\n// AGENT_AUTHORING §10). A §5.1 residual requirement: link/image URLs in untrusted\n// content are allowlisted to `http`/`https`/`mailto` and relative URLs; every other\n// scheme — `javascript:`, `data:`, `vbscript:`, `file:`, … — is rejected. We do NOT\n// rely on React to neutralize `javascript:` (it does not, on an `<a href>`), so this\n// is the single chokepoint every rendered `href`/`src` passes through.\n\n// A leading `scheme:` per RFC 3986 (`ALPHA *( ALPHA / DIGIT / \"+\" / \"-\" / \".\" )`).\n// Anything before the first `:` that matches this is an absolute-URI scheme; if the\n// URL has no such prefix it is relative (path/query/fragment) and allowed.\nconst SCHEME = /^([a-zA-Z][a-zA-Z0-9+.-]*):/;\nconst ALLOWED_SCHEMES = new Set(['http', 'https', 'mailto']);\n\n// ASCII control chars (0x00–0x1F, 0x7F) that browsers ignore INSIDE a scheme — the\n// `java\\tscript:` / `java\\nscript:` obfuscation — stripped anywhere before testing.\n// eslint-disable-next-line no-control-regex\nconst CONTROL_CHARS = /[\\u0000-\\u001F\\u007F]/g;\n\n/**\n * Return the URL if its scheme is allowed (or it is relative/anchor), else\n * `undefined`. Control chars are stripped first so an obfuscated scheme cannot slip\n * past. A protocol-relative `//host` URL is treated as relative-with-authority and\n * allowed (it inherits the page's https scheme).\n */\nexport function sanitizeUrl(url: unknown): string | undefined {\n if (typeof url !== 'string') return undefined;\n const cleaned = url.replace(CONTROL_CHARS, '').trim();\n if (cleaned === '') return undefined;\n const m = SCHEME.exec(cleaned);\n if (!m) return cleaned; // no scheme → relative / anchor / query — allowed\n return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? cleaned : undefined;\n}\n"],"mappings":";AAUA,MAAM,SAAS;AACf,MAAM,kBAAkB,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,CAAC;AAK3D,MAAM,gBAAgB;AAQf,SAAS,YAAY,KAAkC;AAC5D,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,UAAU,IAAI,QAAQ,eAAe,EAAE,EAAE,KAAK;AACpD,MAAI,YAAY,GAAI,QAAO;AAC3B,QAAM,IAAI,OAAO,KAAK,OAAO;AAC7B,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,gBAAgB,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,IAAI,UAAU;AAC7D;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/safeContent/wikilink.ts"],"sourcesContent":["// Wikilink parsing for the safe content renderer (TRUST_MODES_SPEC §5.1). A §5.1\n// residual: wikilinks resolve **only within the granted mount, never a fetch**. The\n// kernel's executable pipeline compiles `[[target]]`/`[[label|target]]` to a\n// `<WikiLink>` component (transpiler `remarkWikiLinks`, MARKDOWN_SYNTAX_SPEC §13); the\n// SAFE renderer parses raw source, so it splits `[[…]]` out of text itself with the\n// same byte-local rule and hands the raw target/label to an injected, **in-mount**\n// resolver. The renderer never fetches; resolution is the consumer's mount-scoped\n// callback (or, absent one, the link renders as inert text).\n\n// Inner content up to the closing `]]`, forbidding `[`/`]` so nested brackets can't\n// be swallowed. Global — a text run may hold several.\nconst WIKILINK = /\\[\\[([^[\\]]+)\\]\\]/g;\n\nexport interface WikiLinkToken {\n /** The raw target path, verbatim (relative or absolute; resolved in-mount only). */\n target: string;\n /** The explicit label, or undefined (the component derives one). */\n label?: string;\n}\n\nexport type WikiPart = { text: string } | { wiki: WikiLinkToken };\n\n/** Parse the `[[label|target]]` / `[[target]]` inner text (label first, then target\n * — the §13.1 order), or `null` for an empty target (leave the literal `[[…]]`). */\nexport function parseWikiInner(inner: string): WikiLinkToken | null {\n const pipe = inner.indexOf('|');\n let target: string;\n let label: string | undefined;\n if (pipe === -1) {\n target = inner.trim();\n } else {\n label = inner.slice(0, pipe).trim();\n target = inner.slice(pipe + 1).trim();\n }\n if (target === '') return null;\n return label ? { target, label } : { target };\n}\n\n/**\n * Split a text string on its `[[…]]` runs into ordered text / wiki parts, or `null`\n * when there is no usable wiki-link (so the caller keeps the text node untouched).\n */\nexport function splitWikiLinks(value: string): WikiPart[] | null {\n if (value.indexOf('[[') === -1) return null;\n const out: WikiPart[] = [];\n let lastIndex = 0;\n let produced = false;\n WIKILINK.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK.exec(value)) !== null) {\n const token = parseWikiInner(match[1]);\n if (!token) continue; // empty target → keep the literal `[[…]]`\n if (match.index > lastIndex) out.push({ text: value.slice(lastIndex, match.index) });\n out.push({ wiki: token });\n lastIndex = match.index + match[0].length;\n produced = true;\n }\n if (!produced) return null;\n if (lastIndex < value.length) out.push({ text: value.slice(lastIndex) });\n return out;\n}\n"],"mappings":"AAWA,MAAM,WAAW;AAaV,SAAS,eAAe,OAAqC;AAClE,QAAM,OAAO,MAAM,QAAQ,GAAG;AAC9B,MAAI;AACJ,MAAI;AACJ,MAAI,SAAS,IAAI;AACf,aAAS,MAAM,KAAK;AAAA,EACtB,OAAO;AACL,YAAQ,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK;AAClC,aAAS,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK;AAAA,EACtC;AACA,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO,QAAQ,EAAE,QAAQ,MAAM,IAAI,EAAE,OAAO;AAC9C;AAMO,SAAS,eAAe,OAAkC;AAC/D,MAAI,MAAM,QAAQ,IAAI,MAAM,GAAI,QAAO;AACvC,QAAM,MAAkB,CAAC;AACzB,MAAI,YAAY;AAChB,MAAI,WAAW;AACf,WAAS,YAAY;AACrB,MAAI;AACJ,UAAQ,QAAQ,SAAS,KAAK,KAAK,OAAO,MAAM;AAC9C,UAAM,QAAQ,eAAe,MAAM,CAAC,CAAC;AACrC,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,QAAQ,UAAW,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,WAAW,MAAM,KAAK,EAAE,CAAC;AACnF,QAAI,KAAK,EAAE,MAAM,MAAM,CAAC;AACxB,gBAAY,MAAM,QAAQ,MAAM,CAAC,EAAE;AACnC,eAAW;AAAA,EACb;AACA,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,YAAY,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,EAAE,CAAC;AACvE,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/safeContent/wikilink.ts"],"sourcesContent":["// Wikilink parsing for the safe content renderer (TRUST_MODES_SPEC §5.1). A §5.1\n// residual: wikilinks resolve **only within the granted mount, never a fetch**. The\n// kernel's executable pipeline compiles `[[target]]`/`[[label|target]]` to a\n// `<WikiLink>` component (transpiler `remarkWikiLinks`, MARKDOWN_SYNTAX_SPEC §13); the\n// SAFE renderer parses raw source, so it splits `[[…]]` out of text itself with the\n// same byte-local rule and hands the raw target/label to an injected, **in-mount**\n// resolver. The renderer never fetches; resolution is the consumer's mount-scoped\n// callback (or, absent one, the link renders as inert text).\n\n// Inner content up to the closing `]]`, forbidding `[`/`]` so nested brackets can't\n// be swallowed. Global — a text run may hold several.\nconst WIKILINK = /\\[\\[([^[\\]]+)\\]\\]/g;\n\nexport interface WikiLinkToken {\n /** The raw target path, verbatim (relative or absolute; resolved in-mount only). */\n target: string;\n /** The explicit label, or undefined (the component derives one). */\n label?: string;\n}\n\nexport type WikiPart = { text: string } | { wiki: WikiLinkToken };\n\n/** Parse the `[[label|target]]` / `[[target]]` inner text (label first, then target\n * — the §13.1 order), or `null` for an empty target (leave the literal `[[…]]`). */\nexport function parseWikiInner(inner: string): WikiLinkToken | null {\n const pipe = inner.indexOf('|');\n let target: string;\n let label: string | undefined;\n if (pipe === -1) {\n target = inner.trim();\n } else {\n label = inner.slice(0, pipe).trim();\n target = inner.slice(pipe + 1).trim();\n }\n if (target === '') return null;\n return label ? { target, label } : { target };\n}\n\n/**\n * Split a text string on its `[[…]]` runs into ordered text / wiki parts, or `null`\n * when there is no usable wiki-link (so the caller keeps the text node untouched).\n */\nexport function splitWikiLinks(value: string): WikiPart[] | null {\n if (value.indexOf('[[') === -1) return null;\n const out: WikiPart[] = [];\n let lastIndex = 0;\n let produced = false;\n WIKILINK.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK.exec(value)) !== null) {\n const token = parseWikiInner(match[1]);\n if (!token) continue; // empty target → keep the literal `[[…]]`\n if (match.index > lastIndex) out.push({ text: value.slice(lastIndex, match.index) });\n out.push({ wiki: token });\n lastIndex = match.index + match[0].length;\n produced = true;\n }\n if (!produced) return null;\n if (lastIndex < value.length) out.push({ text: value.slice(lastIndex) });\n return out;\n}\n"],"mappings":";AAWA,MAAM,WAAW;AAaV,SAAS,eAAe,OAAqC;AAClE,QAAM,OAAO,MAAM,QAAQ,GAAG;AAC9B,MAAI;AACJ,MAAI;AACJ,MAAI,SAAS,IAAI;AACf,aAAS,MAAM,KAAK;AAAA,EACtB,OAAO;AACL,YAAQ,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK;AAClC,aAAS,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK;AAAA,EACtC;AACA,MAAI,WAAW,GAAI,QAAO;AAC1B,SAAO,QAAQ,EAAE,QAAQ,MAAM,IAAI,EAAE,OAAO;AAC9C;AAMO,SAAS,eAAe,OAAkC;AAC/D,MAAI,MAAM,QAAQ,IAAI,MAAM,GAAI,QAAO;AACvC,QAAM,MAAkB,CAAC;AACzB,MAAI,YAAY;AAChB,MAAI,WAAW;AACf,WAAS,YAAY;AACrB,MAAI;AACJ,UAAQ,QAAQ,SAAS,KAAK,KAAK,OAAO,MAAM;AAC9C,UAAM,QAAQ,eAAe,MAAM,CAAC,CAAC;AACrC,QAAI,CAAC,MAAO;AACZ,QAAI,MAAM,QAAQ,UAAW,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,WAAW,MAAM,KAAK,EAAE,CAAC;AACnF,QAAI,KAAK,EAAE,MAAM,MAAM,CAAC;AACxB,gBAAY,MAAM,QAAQ,MAAM,CAAC,EAAE;AACnC,eAAW;AAAA,EACb;AACA,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI,YAAY,MAAM,OAAQ,KAAI,KAAK,EAAE,MAAM,MAAM,MAAM,SAAS,EAAE,CAAC;AACvE,SAAO;AACT;","names":[]}
|
package/dist/sandboxUtils.js
CHANGED
package/dist/sandboxUtils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sandboxUtils.ts"],"sourcesContent":["// Host transport access (SDK_PACKAGING_SPEC §4, expose-transport). All host comms\n// (sendMessage / protocolRequest / onMessage) go through ONE resolver so the SDK is\n// transport-agnostic: it works whether it was INJECTED into the bundler's evaluation\n// context (the current path — `module.evaluation.module.bundler.messageBus`) OR\n// fetched from npm as an ordinary dependency, in which case the sandbox runtime hands\n// it the transport via the §4 discovery global (`globalThis.__immediatelyRun__`).\n//\n// Dual-mode (§8): injection wins while it's active, so existing behaviour is\n// byte-for-byte preserved; the global is the fallback the npm-fetched SDK uses. When\n// injection is removed (phase 3), only the global path remains — `bundler.*` stops\n// being API, which is the whole point.\nimport { getHostRuntime } from './hostRuntime';\n\ninterface HostTransport {\n sendMessage(type: string, data?: Record<string, any>): void;\n protocolRequest(protocolName: string, method: string, params: Array<any>): Promise<any>;\n onMessage(handler: (msg: any) => void): { dispose(): void };\n}\n\nfunction transport(): HostTransport {\n // Injected bundler messageBus first — the current path, unchanged.\n try {\n // @ts-ignore - `module.evaluation` is injected by the sandbox runtime\n const injected = module?.evaluation?.module?.bundler?.messageBus;\n if (injected && typeof injected.sendMessage === 'function') return injected;\n } catch {\n /* no injection in this realm — fall through to the §4 global */\n }\n // §4 runtime-discovery transport (the npm-fetched SDK path).\n const t = getHostRuntime()?.transport as HostTransport | undefined;\n if (t && typeof t.sendMessage === 'function') return t;\n throw new Error('immediately.run: no host transport (neither injected nor __immediatelyRun__)');\n}\n\nexport const sendMessage = (type: string, data: Record<string, any> = {}) => {\n transport().sendMessage(type, data);\n};\n\nexport const protocolRequest = (\n protocolName: string,\n method: string,\n params: Array<any>,\n): Promise<any> => transport().protocolRequest(protocolName, method, params);\n\nexport const addListener = (\n msgType: string,\n handler: (msg: any) => void,\n event?: any,\n): (() => void) => {\n const onMessage = event ?? transport().onMessage;\n const disposable = onMessage((msg: any) => {\n if (msg.type === msgType) {\n handler(msg);\n }\n });\n return () => disposable.dispose();\n};\n"],"mappings":"AAWA,SAAS,sBAAsB;AAQ/B,SAAS,YAA2B;AAElC,MAAI;AAEF,UAAM,WAAW,QAAQ,YAAY,QAAQ,SAAS;AACtD,QAAI,YAAY,OAAO,SAAS,gBAAgB,WAAY,QAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AAEA,QAAM,IAAI,eAAe,GAAG;AAC5B,MAAI,KAAK,OAAO,EAAE,gBAAgB,WAAY,QAAO;AACrD,QAAM,IAAI,MAAM,8EAA8E;AAChG;AAEO,MAAM,cAAc,CAAC,MAAc,OAA4B,CAAC,MAAM;AAC3E,YAAU,EAAE,YAAY,MAAM,IAAI;AACpC;AAEO,MAAM,kBAAkB,CAC7B,cACA,QACA,WACiB,UAAU,EAAE,gBAAgB,cAAc,QAAQ,MAAM;AAEpE,MAAM,cAAc,CACzB,SACA,SACA,UACiB;AACjB,QAAM,YAAY,SAAS,UAAU,EAAE;AACvC,QAAM,aAAa,UAAU,CAAC,QAAa;AACzC,QAAI,IAAI,SAAS,SAAS;AACxB,cAAQ,GAAG;AAAA,IACb;AAAA,EACF,CAAC;AACD,SAAO,MAAM,WAAW,QAAQ;AAClC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/sandboxUtils.ts"],"sourcesContent":["// Host transport access (SDK_PACKAGING_SPEC §4, expose-transport). All host comms\n// (sendMessage / protocolRequest / onMessage) go through ONE resolver so the SDK is\n// transport-agnostic: it works whether it was INJECTED into the bundler's evaluation\n// context (the current path — `module.evaluation.module.bundler.messageBus`) OR\n// fetched from npm as an ordinary dependency, in which case the sandbox runtime hands\n// it the transport via the §4 discovery global (`globalThis.__immediatelyRun__`).\n//\n// Dual-mode (§8): injection wins while it's active, so existing behaviour is\n// byte-for-byte preserved; the global is the fallback the npm-fetched SDK uses. When\n// injection is removed (phase 3), only the global path remains — `bundler.*` stops\n// being API, which is the whole point.\nimport { getHostRuntime } from './hostRuntime';\n\ninterface HostTransport {\n sendMessage(type: string, data?: Record<string, any>): void;\n protocolRequest(protocolName: string, method: string, params: Array<any>): Promise<any>;\n onMessage(handler: (msg: any) => void): { dispose(): void };\n}\n\nfunction transport(): HostTransport {\n // Injected bundler messageBus first — the current path, unchanged.\n try {\n // @ts-ignore - `module.evaluation` is injected by the sandbox runtime\n const injected = module?.evaluation?.module?.bundler?.messageBus;\n if (injected && typeof injected.sendMessage === 'function') return injected;\n } catch {\n /* no injection in this realm — fall through to the §4 global */\n }\n // §4 runtime-discovery transport (the npm-fetched SDK path).\n const t = getHostRuntime()?.transport as HostTransport | undefined;\n if (t && typeof t.sendMessage === 'function') return t;\n throw new Error('immediately.run: no host transport (neither injected nor __immediatelyRun__)');\n}\n\nexport const sendMessage = (type: string, data: Record<string, any> = {}) => {\n transport().sendMessage(type, data);\n};\n\nexport const protocolRequest = (\n protocolName: string,\n method: string,\n params: Array<any>,\n): Promise<any> => transport().protocolRequest(protocolName, method, params);\n\nexport const addListener = (\n msgType: string,\n handler: (msg: any) => void,\n event?: any,\n): (() => void) => {\n const onMessage = event ?? transport().onMessage;\n const disposable = onMessage((msg: any) => {\n if (msg.type === msgType) {\n handler(msg);\n }\n });\n return () => disposable.dispose();\n};\n"],"mappings":";AAWA,SAAS,sBAAsB;AAQ/B,SAAS,YAA2B;AAElC,MAAI;AAEF,UAAM,WAAW,QAAQ,YAAY,QAAQ,SAAS;AACtD,QAAI,YAAY,OAAO,SAAS,gBAAgB,WAAY,QAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AAEA,QAAM,IAAI,eAAe,GAAG;AAC5B,MAAI,KAAK,OAAO,EAAE,gBAAgB,WAAY,QAAO;AACrD,QAAM,IAAI,MAAM,8EAA8E;AAChG;AAEO,MAAM,cAAc,CAAC,MAAc,OAA4B,CAAC,MAAM;AAC3E,YAAU,EAAE,YAAY,MAAM,IAAI;AACpC;AAEO,MAAM,kBAAkB,CAC7B,cACA,QACA,WACiB,UAAU,EAAE,gBAAgB,cAAc,QAAQ,MAAM;AAEpE,MAAM,cAAc,CACzB,SACA,SACA,UACiB;AACjB,QAAM,YAAY,SAAS,UAAU,EAAE;AACvC,QAAM,aAAa,UAAU,CAAC,QAAa;AACzC,QAAI,IAAI,SAAS,SAAS;AACxB,cAAQ,GAAG;AAAA,IACb;AAAA,EACF,CAAC;AACD,SAAO,MAAM,WAAW,QAAQ;AAClC;","names":[]}
|
package/dist/scrollToId.js
CHANGED
package/dist/scrollToId.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scrollToId.ts"],"sourcesContent":["/**\n * Scroll the element addressed by a fragment id into view (deep-linking Capability\n * C, MARKDOWN_SYNTAX_SPEC §13.5). Tries the element's own `id` first — a heading's\n * `sec-…`/text-slug id (§15) — then the `[data-slug]` fallback, so a citation that\n * used the prose text slug still lands even when the heading's own id is the\n * prose-independent `sec-…` id. Returns whether a target was found; the caller\n * decides whether to fall back to top-of-page. **Never throws** — a missing\n * fragment is a soft failure, not an error.\n */\nexport const scrollToId = (id: string): boolean => {\n if (!id || typeof document === 'undefined') return false;\n let el: Element | null = null;\n try {\n el = document.getElementById(id);\n if (!el) {\n // A citation that targeted the prose text slug still resolves via the\n // `data-slug` hook the kernel emits alongside the `sec-…` id.\n const escaped =\n typeof CSS !== 'undefined' && typeof CSS.escape === 'function'\n ? CSS.escape(id)\n : id.replace(/[\"\\\\]/g, '\\\\$&');\n el = document.querySelector(`[data-slug=\"${escaped}\"]`);\n }\n } catch {\n return false;\n }\n if (el && typeof (el as HTMLElement).scrollIntoView === 'function') {\n (el as HTMLElement).scrollIntoView();\n return true;\n }\n return false;\n};\n"],"mappings":"AASO,MAAM,aAAa,CAAC,OAAwB;AACjD,MAAI,CAAC,MAAM,OAAO,aAAa,YAAa,QAAO;AACnD,MAAI,KAAqB;AACzB,MAAI;AACF,SAAK,SAAS,eAAe,EAAE;AAC/B,QAAI,CAAC,IAAI;AAGP,YAAM,UACJ,OAAO,QAAQ,eAAe,OAAO,IAAI,WAAW,aAChD,IAAI,OAAO,EAAE,IACb,GAAG,QAAQ,UAAU,MAAM;AACjC,WAAK,SAAS,cAAc,eAAe,OAAO,IAAI;AAAA,IACxD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,MAAM,OAAQ,GAAmB,mBAAmB,YAAY;AAClE,IAAC,GAAmB,eAAe;AACnC,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/scrollToId.ts"],"sourcesContent":["/**\n * Scroll the element addressed by a fragment id into view (deep-linking Capability\n * C, MARKDOWN_SYNTAX_SPEC §13.5). Tries the element's own `id` first — a heading's\n * `sec-…`/text-slug id (§15) — then the `[data-slug]` fallback, so a citation that\n * used the prose text slug still lands even when the heading's own id is the\n * prose-independent `sec-…` id. Returns whether a target was found; the caller\n * decides whether to fall back to top-of-page. **Never throws** — a missing\n * fragment is a soft failure, not an error.\n */\nexport const scrollToId = (id: string): boolean => {\n if (!id || typeof document === 'undefined') return false;\n let el: Element | null = null;\n try {\n el = document.getElementById(id);\n if (!el) {\n // A citation that targeted the prose text slug still resolves via the\n // `data-slug` hook the kernel emits alongside the `sec-…` id.\n const escaped =\n typeof CSS !== 'undefined' && typeof CSS.escape === 'function'\n ? CSS.escape(id)\n : id.replace(/[\"\\\\]/g, '\\\\$&');\n el = document.querySelector(`[data-slug=\"${escaped}\"]`);\n }\n } catch {\n return false;\n }\n if (el && typeof (el as HTMLElement).scrollIntoView === 'function') {\n (el as HTMLElement).scrollIntoView();\n return true;\n }\n return false;\n};\n"],"mappings":";AASO,MAAM,aAAa,CAAC,OAAwB;AACjD,MAAI,CAAC,MAAM,OAAO,aAAa,YAAa,QAAO;AACnD,MAAI,KAAqB;AACzB,MAAI;AACF,SAAK,SAAS,eAAe,EAAE;AAC/B,QAAI,CAAC,IAAI;AAGP,YAAM,UACJ,OAAO,QAAQ,eAAe,OAAO,IAAI,WAAW,aAChD,IAAI,OAAO,EAAE,IACb,GAAG,QAAQ,UAAU,MAAM;AACjC,WAAK,SAAS,cAAc,eAAe,OAAO,IAAI;AAAA,IACxD;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,MAAM,OAAQ,GAAmB,mBAAmB,YAAY;AAClE,IAAC,GAAmB,eAAe;AACnC,WAAO;AAAA,EACT;AACA,SAAO;AACT;","names":[]}
|
package/dist/secrets.js
CHANGED
package/dist/secrets.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/secrets.ts"],"sourcesContent":["// The host-owned secret store — app-facing surface (SECRETS_SPEC §4/§5).\n//\n// An app can ASK the user to store a secret (`requestAddSecret`), be GRANTED the\n// right to USE a specific secret (`requestSecret`, the powerbox flow), LIST\n// secret METADATA (`getSecrets`/`useSecrets`, never values), and REVOKE one\n// (`revokeSecret`). The secret VALUE never crosses this boundary: it is read\n// host-side, once, at the `net:fetch` injection point (SECRETS_SPEC §6). These\n// functions move only hints, metadata, and grant handles.\n//\n// Resolves LLM_AND_AGENTS_SPEC D2 — the host-mediated BYOK key store. Inert until\n// the host implements `protocol-secrets` + the `secrets-metadata` channel\n// (SECRETS_SPEC §3/§6 host work, roadmap P1.E); the contract is shipped here so\n// apps (e.g. the in-browser coding agent, P3-73) can be written against it.\nimport { protocolRequest } from './sandboxUtils';\nimport { createPushChannel } from './pushChannel';\n\n/** The closed secret-type vocabulary (SECRETS_SPEC §2). `api-key` is always\n * origin-bound; `oauth-refresh` is reserved (no substitution in v1). */\nexport type SecretType = 'api-key' | 'bearer-token' | 'oauth-refresh';\n\n/**\n * The metadata-only projection of a stored secret (SECRETS_SPEC §2/§4) — exactly\n * what `secrets:list` and the powerbox return. **There is no `value` field by\n * design**: the plaintext is never part of any record an app receives.\n */\nexport interface SecretView {\n id: string;\n type: SecretType;\n family?: string;\n description: string;\n /** Required for `type:'api-key'` — the one https origin it may be sent to. */\n boundOrigin?: string;\n /** ISO-8601, or null if never used (drives the §8.15 90-day expiry). */\n lastUsedAt: string | null;\n}\n\n/** Hints for the host's \"add secret\" modal (SECRETS_SPEC §4 `secrets:add`). The\n * app supplies only hints; the user types the value into host chrome. */\nexport interface SecretHints {\n type?: SecretType;\n family?: string;\n /** Pre-fill the bound origin (e.g. `https://api.anthropic.com`). */\n suggestedOrigin?: string;\n description?: string;\n}\n\n/** What `requestSecret()` matches against in the powerbox picker (SECRETS_SPEC §5). */\nexport interface SecretQuery {\n type?: SecretType;\n family?: string;\n}\n\n/**\n * The result of a granted `requestSecret()` — a durable `(appKey, secretId)` use\n * grant plus the secret's metadata. **Never the value.** Hold onto nothing but\n * this; the host substitutes the value into matching `net:fetch` requests.\n */\nexport interface SecretGrant {\n /** Opaque handle for the minted `(appKey, secretId)` grant. */\n grantId: string;\n /** Metadata of the bound secret (no value). */\n secret: SecretView;\n}\n\n/** An error from a secret operation, carrying a machine-readable `code`. */\nexport interface SecretError extends Error {\n code: 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'invalid-params' | 'unknown';\n}\n\ntype SecretResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a `protocol-secrets` request, unwrapping the host's {ok,data} envelope\n// and throwing a typed SecretError on failure (mirrors mounts.ts `request`).\nconst request = async <T = unknown>(\n method: string,\n query: object = {},\n): Promise<T> => {\n const res = (await protocolRequest('secrets', method, [query])) as SecretResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'secret request failed') as SecretError;\n err.code = (res?.code as SecretError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n/**\n * Ask the user to store a new secret (SECRETS_SPEC §4 `secrets:add`). Opens a\n * **host-drawn** modal (the value is typed into host chrome, never via the app);\n * resolves with the new secret's {@link SecretView} metadata, or rejects with a\n * {@link SecretError} (`cancelled` if the user dismisses the modal). Requires the\n * `secrets:add` capability.\n */\nexport const requestAddSecret = (hints: SecretHints = {}): Promise<SecretView> =>\n request<SecretView>('add', hints);\n\n/**\n * Ask the user to bind one of their stored secrets to this app (SECRETS_SPEC §5,\n * the powerbox flow — modeled on `requestSpace()`). The host draws a picker of\n * **only the user's matching secrets**; the user picks, declines, or creates one.\n * On success the host records a durable `(appKey, secretId)` grant and resolves\n * with a {@link SecretGrant} (handle + metadata, **never the value**).\n *\n * **No existence oracle (T20/T27):** a decline, an ungranted secret, and a\n * nonexistent secret are indistinguishable — all reject with a {@link SecretError}\n * `cancelled`; the app never sees the list it chose from.\n */\nexport const requestSecret = (query: SecretQuery = {}): Promise<SecretGrant> =>\n request<SecretGrant>('request', query);\n\n/**\n * Delete a stored secret and tombstone every dependent per-app use grant\n * (SECRETS_SPEC §4 `secrets:revoke`, §8.15 cascade). Requires `secrets:revoke`.\n */\nexport const revokeSecret = async (id: string): Promise<void> => {\n await request('revoke', { id });\n};\n\n// The metadata-only `secrets-metadata` channel (Recipe A): the host pushes the\n// current secret metadata on change and replays it on register-frame; gated by\n// `secrets:list`. NEVER carries a value (SECRETS_SPEC §4).\nconst channel = createPushChannel<SecretView[]>({\n pushType: 'secrets-metadata',\n requestType: 'request-secrets-metadata',\n initial: [],\n parse: (msg) => (Array.isArray(msg.secrets) ? (msg.secrets as SecretView[]) : undefined),\n});\n\n/** The metadata of the user's stored secrets (never values), `secrets:list`. Poll\n * for a one-off read; use {@link onSecretsChange}/{@link useSecrets} to react. */\nexport const getSecrets = (): SecretView[] => channel.get();\n\n/** Subscribe to secret-metadata changes (added/revoked). Invoked immediately with\n * the current list, then on every change. Returns an unsubscribe fn. */\nexport const onSecretsChange = (listener: (secrets: SecretView[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning the user's secret metadata (never values), re-rendering\n * on change. For the Settings app (SECRETS_SPEC §7). */\nexport const useSecrets = (): SecretView[] => channel.use();\n"],"mappings":"AAaA,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AA6DlC,MAAM,UAAU,OACd,QACA,QAAgB,CAAC,MACF;AACf,QAAM,MAAO,MAAM,gBAAgB,WAAW,QAAQ,CAAC,KAAK,CAAC;AAC7D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,uBAAuB;AAC7D,QAAI,OAAQ,KAAK,QAAgC;AACjD,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AASO,MAAM,mBAAmB,CAAC,QAAqB,CAAC,MACrD,QAAoB,OAAO,KAAK;AAa3B,MAAM,gBAAgB,CAAC,QAAqB,CAAC,MAClD,QAAqB,WAAW,KAAK;AAMhC,MAAM,eAAe,OAAO,OAA8B;AAC/D,QAAM,QAAQ,UAAU,EAAE,GAAG,CAAC;AAChC;AAKA,MAAM,UAAU,kBAAgC;AAAA,EAC9C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA2B;AAChF,CAAC;AAIM,MAAM,aAAa,MAAoB,QAAQ,IAAI;AAInD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAIpB,MAAM,aAAa,MAAoB,QAAQ,IAAI;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/secrets.ts"],"sourcesContent":["// The host-owned secret store — app-facing surface (SECRETS_SPEC §4/§5).\n//\n// An app can ASK the user to store a secret (`requestAddSecret`), be GRANTED the\n// right to USE a specific secret (`requestSecret`, the powerbox flow), LIST\n// secret METADATA (`getSecrets`/`useSecrets`, never values), and REVOKE one\n// (`revokeSecret`). The secret VALUE never crosses this boundary: it is read\n// host-side, once, at the `net:fetch` injection point (SECRETS_SPEC §6). These\n// functions move only hints, metadata, and grant handles.\n//\n// Resolves LLM_AND_AGENTS_SPEC D2 — the host-mediated BYOK key store. Inert until\n// the host implements `protocol-secrets` + the `secrets-metadata` channel\n// (SECRETS_SPEC §3/§6 host work, roadmap P1.E); the contract is shipped here so\n// apps (e.g. the in-browser coding agent, P3-73) can be written against it.\nimport { protocolRequest } from './sandboxUtils';\nimport { createPushChannel } from './pushChannel';\n\n/** The closed secret-type vocabulary (SECRETS_SPEC §2). `api-key` is always\n * origin-bound; `oauth-refresh` is reserved (no substitution in v1). */\nexport type SecretType = 'api-key' | 'bearer-token' | 'oauth-refresh';\n\n/**\n * The metadata-only projection of a stored secret (SECRETS_SPEC §2/§4) — exactly\n * what `secrets:list` and the powerbox return. **There is no `value` field by\n * design**: the plaintext is never part of any record an app receives.\n */\nexport interface SecretView {\n id: string;\n type: SecretType;\n family?: string;\n description: string;\n /** Required for `type:'api-key'` — the one https origin it may be sent to. */\n boundOrigin?: string;\n /** ISO-8601, or null if never used (drives the §8.15 90-day expiry). */\n lastUsedAt: string | null;\n}\n\n/** Hints for the host's \"add secret\" modal (SECRETS_SPEC §4 `secrets:add`). The\n * app supplies only hints; the user types the value into host chrome. */\nexport interface SecretHints {\n type?: SecretType;\n family?: string;\n /** Pre-fill the bound origin (e.g. `https://api.anthropic.com`). */\n suggestedOrigin?: string;\n description?: string;\n}\n\n/** What `requestSecret()` matches against in the powerbox picker (SECRETS_SPEC §5). */\nexport interface SecretQuery {\n type?: SecretType;\n family?: string;\n}\n\n/**\n * The result of a granted `requestSecret()` — a durable `(appKey, secretId)` use\n * grant plus the secret's metadata. **Never the value.** Hold onto nothing but\n * this; the host substitutes the value into matching `net:fetch` requests.\n */\nexport interface SecretGrant {\n /** Opaque handle for the minted `(appKey, secretId)` grant. */\n grantId: string;\n /** Metadata of the bound secret (no value). */\n secret: SecretView;\n}\n\n/** An error from a secret operation, carrying a machine-readable `code`. */\nexport interface SecretError extends Error {\n code: 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'invalid-params' | 'unknown';\n}\n\ntype SecretResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\n// Issue a `protocol-secrets` request, unwrapping the host's {ok,data} envelope\n// and throwing a typed SecretError on failure (mirrors mounts.ts `request`).\nconst request = async <T = unknown>(\n method: string,\n query: object = {},\n): Promise<T> => {\n const res = (await protocolRequest('secrets', method, [query])) as SecretResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'secret request failed') as SecretError;\n err.code = (res?.code as SecretError['code']) ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n/**\n * Ask the user to store a new secret (SECRETS_SPEC §4 `secrets:add`). Opens a\n * **host-drawn** modal (the value is typed into host chrome, never via the app);\n * resolves with the new secret's {@link SecretView} metadata, or rejects with a\n * {@link SecretError} (`cancelled` if the user dismisses the modal). Requires the\n * `secrets:add` capability.\n */\nexport const requestAddSecret = (hints: SecretHints = {}): Promise<SecretView> =>\n request<SecretView>('add', hints);\n\n/**\n * Ask the user to bind one of their stored secrets to this app (SECRETS_SPEC §5,\n * the powerbox flow — modeled on `requestSpace()`). The host draws a picker of\n * **only the user's matching secrets**; the user picks, declines, or creates one.\n * On success the host records a durable `(appKey, secretId)` grant and resolves\n * with a {@link SecretGrant} (handle + metadata, **never the value**).\n *\n * **No existence oracle (T20/T27):** a decline, an ungranted secret, and a\n * nonexistent secret are indistinguishable — all reject with a {@link SecretError}\n * `cancelled`; the app never sees the list it chose from.\n */\nexport const requestSecret = (query: SecretQuery = {}): Promise<SecretGrant> =>\n request<SecretGrant>('request', query);\n\n/**\n * Delete a stored secret and tombstone every dependent per-app use grant\n * (SECRETS_SPEC §4 `secrets:revoke`, §8.15 cascade). Requires `secrets:revoke`.\n */\nexport const revokeSecret = async (id: string): Promise<void> => {\n await request('revoke', { id });\n};\n\n// The metadata-only `secrets-metadata` channel (Recipe A): the host pushes the\n// current secret metadata on change and replays it on register-frame; gated by\n// `secrets:list`. NEVER carries a value (SECRETS_SPEC §4).\nconst channel = createPushChannel<SecretView[]>({\n pushType: 'secrets-metadata',\n requestType: 'request-secrets-metadata',\n initial: [],\n parse: (msg) => (Array.isArray(msg.secrets) ? (msg.secrets as SecretView[]) : undefined),\n});\n\n/** The metadata of the user's stored secrets (never values), `secrets:list`. Poll\n * for a one-off read; use {@link onSecretsChange}/{@link useSecrets} to react. */\nexport const getSecrets = (): SecretView[] => channel.get();\n\n/** Subscribe to secret-metadata changes (added/revoked). Invoked immediately with\n * the current list, then on every change. Returns an unsubscribe fn. */\nexport const onSecretsChange = (listener: (secrets: SecretView[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning the user's secret metadata (never values), re-rendering\n * on change. For the Settings app (SECRETS_SPEC §7). */\nexport const useSecrets = (): SecretView[] => channel.use();\n"],"mappings":";AAaA,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AA6DlC,MAAM,UAAU,OACd,QACA,QAAgB,CAAC,MACF;AACf,QAAM,MAAO,MAAM,gBAAgB,WAAW,QAAQ,CAAC,KAAK,CAAC;AAC7D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,uBAAuB;AAC7D,QAAI,OAAQ,KAAK,QAAgC;AACjD,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AASO,MAAM,mBAAmB,CAAC,QAAqB,CAAC,MACrD,QAAoB,OAAO,KAAK;AAa3B,MAAM,gBAAgB,CAAC,QAAqB,CAAC,MAClD,QAAqB,WAAW,KAAK;AAMhC,MAAM,eAAe,OAAO,OAA8B;AAC/D,QAAM,QAAQ,UAAU,EAAE,GAAG,CAAC;AAChC;AAKA,MAAM,UAAU,kBAAgC;AAAA,EAC9C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA2B;AAChF,CAAC;AAIM,MAAM,aAAa,MAAoB,QAAQ,IAAI;AAInD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAIpB,MAAM,aAAa,MAAoB,QAAQ,IAAI;","names":[]}
|
package/dist/tasks.js
CHANGED
package/dist/tasks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tasks.ts"],"sourcesContent":["// Task invocation — apps invoking apps (UI_AS_APPS_SPEC §5.7). The\n// `startActivityForResult` pattern: one app invokes another by TASK CONTRACT\n// (never by app name — the user's override picks the bound app), passes typed\n// params, and awaits a typed result. The callee runs in a host-owned overlay\n// under ITS OWN grants — data crosses, your authority does not (§5.7).\n//\n// Two roles:\n// - CALLER: `invokeTask(task, params)` (Recipe B — a deferred reply the host\n// holds open until the callee finishes). Delegate a file with `capFile(...)`:\n// the host resolves it against YOUR grants and mints an attenuated chroot.\n// - CALLEE: read `useTaskInput()`, then `completeTask(result)` / `cancelTask()`.\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\n\n// ── caller side ─────────────────────────────────────────────────────────────\n\n/** A delegated FILE capability marker for a task param (§5.7). */\nexport interface FileCap {\n $cap: 'file';\n mountId: string;\n relPath: string;\n mode: 'ro' | 'rw';\n}\n\n/**\n * Build a delegated file reference for a task param. The host resolves it against\n * YOUR OWN grants and mints an attenuated, task-scoped chroot for the callee — you\n * can only delegate a path you already hold (attenuation only, never escalation).\n *\n * file: capFile({ mountId: 'space:abc', relPath: 'photos/cat.jpg' }, { mode: 'rw' })\n */\nexport const capFile = (\n ref: { mountId: string; relPath: string },\n opts: { mode: 'ro' | 'rw' },\n): FileCap => ({ $cap: 'file', mountId: ref.mountId, relPath: ref.relPath, mode: opts.mode });\n\n/** A delegated DIRECTORY capability marker for a task param (D2). Like {@link FileCap}\n * but `relPath` names a DIRECTORY: the host chroots the callee AT that directory\n * (the whole subtree). Used for the `pick-file` `roots` — one chroot per root. */\nexport interface DirCap {\n $cap: 'dir';\n mountId: string;\n relPath: string;\n mode: 'ro' | 'rw';\n}\n\n/**\n * Build a delegated DIRECTORY reference for a task param (the directory analogue of\n * {@link capFile}). The host resolves it against YOUR OWN grants and mints an\n * attenuated, task-scoped chroot of that directory for the callee — you can only\n * delegate a directory you already hold (attenuation only, never escalation):\n *\n * roots: [capDir({ mountId: 'space:abc', relPath: 'boards' }, { mode: 'rw' })]\n */\nexport const capDir = (\n ref: { mountId: string; relPath: string },\n opts: { mode: 'ro' | 'rw' },\n): DirCap => ({ $cap: 'dir', mountId: ref.mountId, relPath: ref.relPath, mode: opts.mode });\n\n/**\n * Invoke another app via a task contract and await its typed result (Recipe B).\n * Rejects with a machine `.code` on refusal: `cancelled` (user dismissed the\n * overlay), `timeout` (§5.7.1 liveness), `forbidden` (undeclared task or a file\n * delegation you don't hold), `no-such-task`, `task-cycle`/`task-depth-exceeded`/\n * `task-version-mismatch`, or `invalid-params` (result failed the contract schema).\n */\nexport const invokeTask = async <R = unknown>(\n task: string,\n params: Record<string, unknown> = {},\n): Promise<R> => {\n const res = (await protocolRequest('task', 'invoke', [{ task, params }])) as\n | { ok: true; data: R }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `task '${task}' failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n// ── callee side ─────────────────────────────────────────────────────────────\n\n/** The params this app was invoked with as a task callee. */\nexport interface TaskInput {\n task: string;\n params: Record<string, unknown>;\n}\n\nlet latestInput: TaskInput | null = null;\nconst inputListeners = new Set<(i: TaskInput) => void>();\n\n// The host delivers a `task-input` message to the callee's iframe right after it\n// mounts the overlay (the §5.7 \"params via the region's mount event\").\naddListener('task-input', (m: { task: string; params?: Record<string, unknown> }) => {\n latestInput = { task: m.task, params: m.params ?? {} };\n inputListeners.forEach((l) => l(latestInput!));\n});\n\n/** The task params this app was invoked with, or null if it isn't a task callee. */\nexport const getTaskInput = (): TaskInput | null => latestInput;\n\n/**\n * Finish the task, returning a result to the caller. The host validates it against\n * the contract's result schema before resolving the caller (`invalid-params` on\n * violation), then tears down this overlay.\n */\nexport const completeTask = (result: unknown): void => sendMessage('task-complete', { result });\n\n/** Abort the task; the caller's `invokeTask` rejects with `cancelled`. */\nexport const cancelTask = (): void => sendMessage('task-cancel', {});\n\n/** React hook: the task input for this callee, re-rendering when it arrives. */\nexport const useTaskInput = (): TaskInput | null => {\n const [input, setInput] = useState<TaskInput | null>(getTaskInput);\n useEffect(() => {\n const l = (i: TaskInput) => setInput(i);\n inputListeners.add(l);\n if (latestInput) setInput(latestInput);\n return () => {\n inputListeners.delete(l);\n };\n }, []);\n return input;\n};\n"],"mappings":"AAWA,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB,aAAa,mBAAmB;AAmBnD,MAAM,UAAU,CACrB,KACA,UACa,EAAE,MAAM,QAAQ,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,MAAM,KAAK,KAAK;AAoBpF,MAAM,SAAS,CACpB,KACA,UACY,EAAE,MAAM,OAAO,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,MAAM,KAAK,KAAK;AASlF,MAAM,aAAa,OACxB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,MAAO,MAAM,gBAAgB,QAAQ,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,CAAC;AAIvE,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,SAAS,IAAI,UAAU;AAC7D,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAUA,IAAI,cAAgC;AACpC,MAAM,iBAAiB,oBAAI,IAA4B;AAIvD,YAAY,cAAc,CAAC,MAA0D;AACnF,gBAAc,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,UAAU,CAAC,EAAE;AACrD,iBAAe,QAAQ,CAAC,MAAM,EAAE,WAAY,CAAC;AAC/C,CAAC;AAGM,MAAM,eAAe,MAAwB;AAO7C,MAAM,eAAe,CAAC,WAA0B,YAAY,iBAAiB,EAAE,OAAO,CAAC;AAGvF,MAAM,aAAa,MAAY,YAAY,eAAe,CAAC,CAAC;AAG5D,MAAM,eAAe,MAAwB;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA2B,YAAY;AACjE,YAAU,MAAM;AACd,UAAM,IAAI,CAAC,MAAiB,SAAS,CAAC;AACtC,mBAAe,IAAI,CAAC;AACpB,QAAI,YAAa,UAAS,WAAW;AACrC,WAAO,MAAM;AACX,qBAAe,OAAO,CAAC;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,CAAC;AACL,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/tasks.ts"],"sourcesContent":["// Task invocation — apps invoking apps (UI_AS_APPS_SPEC §5.7). The\n// `startActivityForResult` pattern: one app invokes another by TASK CONTRACT\n// (never by app name — the user's override picks the bound app), passes typed\n// params, and awaits a typed result. The callee runs in a host-owned overlay\n// under ITS OWN grants — data crosses, your authority does not (§5.7).\n//\n// Two roles:\n// - CALLER: `invokeTask(task, params)` (Recipe B — a deferred reply the host\n// holds open until the callee finishes). Delegate a file with `capFile(...)`:\n// the host resolves it against YOUR grants and mints an attenuated chroot.\n// - CALLEE: read `useTaskInput()`, then `completeTask(result)` / `cancelTask()`.\nimport { useEffect, useState } from 'react';\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\n\n// ── caller side ─────────────────────────────────────────────────────────────\n\n/** A delegated FILE capability marker for a task param (§5.7). */\nexport interface FileCap {\n $cap: 'file';\n mountId: string;\n relPath: string;\n mode: 'ro' | 'rw';\n}\n\n/**\n * Build a delegated file reference for a task param. The host resolves it against\n * YOUR OWN grants and mints an attenuated, task-scoped chroot for the callee — you\n * can only delegate a path you already hold (attenuation only, never escalation).\n *\n * file: capFile({ mountId: 'space:abc', relPath: 'photos/cat.jpg' }, { mode: 'rw' })\n */\nexport const capFile = (\n ref: { mountId: string; relPath: string },\n opts: { mode: 'ro' | 'rw' },\n): FileCap => ({ $cap: 'file', mountId: ref.mountId, relPath: ref.relPath, mode: opts.mode });\n\n/** A delegated DIRECTORY capability marker for a task param (D2). Like {@link FileCap}\n * but `relPath` names a DIRECTORY: the host chroots the callee AT that directory\n * (the whole subtree). Used for the `pick-file` `roots` — one chroot per root. */\nexport interface DirCap {\n $cap: 'dir';\n mountId: string;\n relPath: string;\n mode: 'ro' | 'rw';\n}\n\n/**\n * Build a delegated DIRECTORY reference for a task param (the directory analogue of\n * {@link capFile}). The host resolves it against YOUR OWN grants and mints an\n * attenuated, task-scoped chroot of that directory for the callee — you can only\n * delegate a directory you already hold (attenuation only, never escalation):\n *\n * roots: [capDir({ mountId: 'space:abc', relPath: 'boards' }, { mode: 'rw' })]\n */\nexport const capDir = (\n ref: { mountId: string; relPath: string },\n opts: { mode: 'ro' | 'rw' },\n): DirCap => ({ $cap: 'dir', mountId: ref.mountId, relPath: ref.relPath, mode: opts.mode });\n\n/**\n * Invoke another app via a task contract and await its typed result (Recipe B).\n * Rejects with a machine `.code` on refusal: `cancelled` (user dismissed the\n * overlay), `timeout` (§5.7.1 liveness), `forbidden` (undeclared task or a file\n * delegation you don't hold), `no-such-task`, `task-cycle`/`task-depth-exceeded`/\n * `task-version-mismatch`, or `invalid-params` (result failed the contract schema).\n */\nexport const invokeTask = async <R = unknown>(\n task: string,\n params: Record<string, unknown> = {},\n): Promise<R> => {\n const res = (await protocolRequest('task', 'invoke', [{ task, params }])) as\n | { ok: true; data: R }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `task '${task}' failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n// ── callee side ─────────────────────────────────────────────────────────────\n\n/** The params this app was invoked with as a task callee. */\nexport interface TaskInput {\n task: string;\n params: Record<string, unknown>;\n}\n\nlet latestInput: TaskInput | null = null;\nconst inputListeners = new Set<(i: TaskInput) => void>();\n\n// The host delivers a `task-input` message to the callee's iframe right after it\n// mounts the overlay (the §5.7 \"params via the region's mount event\").\naddListener('task-input', (m: { task: string; params?: Record<string, unknown> }) => {\n latestInput = { task: m.task, params: m.params ?? {} };\n inputListeners.forEach((l) => l(latestInput!));\n});\n\n/** The task params this app was invoked with, or null if it isn't a task callee. */\nexport const getTaskInput = (): TaskInput | null => latestInput;\n\n/**\n * Finish the task, returning a result to the caller. The host validates it against\n * the contract's result schema before resolving the caller (`invalid-params` on\n * violation), then tears down this overlay.\n */\nexport const completeTask = (result: unknown): void => sendMessage('task-complete', { result });\n\n/** Abort the task; the caller's `invokeTask` rejects with `cancelled`. */\nexport const cancelTask = (): void => sendMessage('task-cancel', {});\n\n/** React hook: the task input for this callee, re-rendering when it arrives. */\nexport const useTaskInput = (): TaskInput | null => {\n const [input, setInput] = useState<TaskInput | null>(getTaskInput);\n useEffect(() => {\n const l = (i: TaskInput) => setInput(i);\n inputListeners.add(l);\n if (latestInput) setInput(latestInput);\n return () => {\n inputListeners.delete(l);\n };\n }, []);\n return input;\n};\n"],"mappings":";AAWA,SAAS,WAAW,gBAAgB;AACpC,SAAS,iBAAiB,aAAa,mBAAmB;AAmBnD,MAAM,UAAU,CACrB,KACA,UACa,EAAE,MAAM,QAAQ,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,MAAM,KAAK,KAAK;AAoBpF,MAAM,SAAS,CACpB,KACA,UACY,EAAE,MAAM,OAAO,SAAS,IAAI,SAAS,SAAS,IAAI,SAAS,MAAM,KAAK,KAAK;AASlF,MAAM,aAAa,OACxB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,MAAO,MAAM,gBAAgB,QAAQ,UAAU,CAAC,EAAE,MAAM,OAAO,CAAC,CAAC;AAIvE,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,SAAS,IAAI,UAAU;AAC7D,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAUA,IAAI,cAAgC;AACpC,MAAM,iBAAiB,oBAAI,IAA4B;AAIvD,YAAY,cAAc,CAAC,MAA0D;AACnF,gBAAc,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,UAAU,CAAC,EAAE;AACrD,iBAAe,QAAQ,CAAC,MAAM,EAAE,WAAY,CAAC;AAC/C,CAAC;AAGM,MAAM,eAAe,MAAwB;AAO7C,MAAM,eAAe,CAAC,WAA0B,YAAY,iBAAiB,EAAE,OAAO,CAAC;AAGvF,MAAM,aAAa,MAAY,YAAY,eAAe,CAAC,CAAC;AAG5D,MAAM,eAAe,MAAwB;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA2B,YAAY;AACjE,YAAU,MAAM;AACd,UAAM,IAAI,CAAC,MAAiB,SAAS,CAAC;AACtC,mBAAe,IAAI,CAAC;AACpB,QAAI,YAAa,UAAS,WAAW;AACrC,WAAO,MAAM;AACX,qBAAe,OAAO,CAAC;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,CAAC;AACL,SAAO;AACT;","names":[]}
|
package/dist/testing.js
CHANGED
package/dist/testing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Public testing utility — `@immediately-run/sdk/testing` (TESTING_AUTOMATION_SPEC\n// §3/§7). A mock host transport so apps built on this SDK can unit-test their host\n// interaction in CI, with no live bundler/host: drive the SDK's REAL transport path\n// by installing this at the §4 discovery global, then `emit()` host→app pushes and\n// assert your app reacts.\n//\n// import { createMockHost } from '@immediately-run/sdk/testing';\n// const host = createMockHost();\n// host.install(); // SDK now resolves this transport\n// host.stubProtocol('spaces', 'list', () => ({ ok: true, data: [] }));\n// // …render your app / call SDK functions…\n// host.emit({ type: 'mount-add', mount: { path: '/spaces/a', type: 'firestore', id: 'a' } });\n// expect(host.sent).toContainEqual({ type: 'request-mounts', data: {} });\n// host.uninstall();\n//\n// It implements the `sandboxUtils` host transport (sendMessage / protocolRequest /\n// onMessage), so tests exercise the actual `transport()` resolution, the\n// `addListener` type-filter, and `protocolRequest` round-trips — higher fidelity\n// than mocking `./sandboxUtils` wholesale.\n\n/** A message pushed host → app, as it arrives on `onMessage`. */\nexport interface HostMessage {\n type: string;\n [k: string]: unknown;\n}\n\n/** An outbound `sendMessage` captured by the mock. */\nexport interface SentMessage {\n type: string;\n data: Record<string, unknown>;\n}\n\n/** An outbound `protocolRequest` captured by the mock. */\nexport interface ProtocolCall {\n protocol: string;\n method: string;\n params: unknown[];\n}\n\n/** Responder stub for a `protocolRequest(protocol, method, …)`. */\nexport type ProtocolResponder = (params: unknown[]) => unknown;\n\n/** Extra §4 discovery-global fields to publish alongside `transport` on install. */\nexport interface MockHostGlobalExtras {\n runtimeVersion?: string;\n protocolVersion?: string;\n appMountPath?: string;\n}\n\ninterface ImmediatelyRunGlobal extends MockHostGlobalExtras {\n transport?: unknown;\n}\n\n/** The controllable mock host returned by {@link createMockHost}. */\nexport interface MockHost {\n /** The §4 host transport object (also published on the global by `install`). */\n transport: {\n sendMessage(type: string, data?: Record<string, unknown>): void;\n protocolRequest(protocol: string, method: string, params: unknown[]): Promise<unknown>;\n onMessage(handler: (msg: HostMessage) => void): { dispose(): void };\n };\n /** Publish the transport at `globalThis.__immediatelyRun__` (npm-fetched path). */\n install(extras?: MockHostGlobalExtras): void;\n /** Remove the discovery global (call in `afterEach`). */\n uninstall(): void;\n /** Simulate a host → app push; delivered to every live `onMessage` handler. */\n emit(msg: HostMessage): void;\n /** Register a response for a `protocolRequest`; unstubbed calls reject. */\n stubProtocol(protocol: string, method: string, responder: ProtocolResponder): void;\n /** Every `sendMessage` the SDK made, in order. */\n readonly sent: SentMessage[];\n /** Every `protocolRequest` the SDK made, in order. */\n readonly protocolCalls: ProtocolCall[];\n /** Count of live `onMessage` listeners (leak checks). */\n handlerCount(): number;\n}\n\nconst GLOBAL_KEY = '__immediatelyRun__';\n\n/** Build a controllable mock host. Nothing is global until you call `install()`. */\nexport function createMockHost(): MockHost {\n const handlers = new Set<(msg: HostMessage) => void>();\n const sent: SentMessage[] = [];\n const protocolCalls: ProtocolCall[] = [];\n const responders = new Map<string, ProtocolResponder>();\n const key = (protocol: string, method: string) => `${protocol}::${method}`;\n\n const transport: MockHost['transport'] = {\n sendMessage(type, data = {}) {\n sent.push({ type, data });\n },\n protocolRequest(protocol, method, params) {\n protocolCalls.push({ protocol, method, params });\n const responder = responders.get(key(protocol, method));\n if (!responder) {\n return Promise.reject(\n new Error(`mockHost: no stub for protocolRequest('${protocol}', '${method}')`),\n );\n }\n // Resolve async so callers observe real microtask ordering.\n return Promise.resolve().then(() => responder(params));\n },\n onMessage(handler) {\n handlers.add(handler);\n return { dispose: () => handlers.delete(handler) };\n },\n };\n\n const g = globalThis as { [GLOBAL_KEY]?: ImmediatelyRunGlobal };\n\n return {\n transport,\n install(extras = {}) {\n g[GLOBAL_KEY] = { ...extras, transport };\n },\n uninstall() {\n delete g[GLOBAL_KEY];\n },\n emit(msg) {\n // Copy so a handler that unsubscribes mid-emit doesn't mutate the live set.\n for (const handler of [...handlers]) handler(msg);\n },\n stubProtocol(protocol, method, responder) {\n responders.set(key(protocol, method), responder);\n },\n sent,\n protocolCalls,\n handlerCount: () => handlers.size,\n };\n}\n"],"mappings":"AA6EA,MAAM,aAAa;AAGZ,SAAS,iBAA2B;AACzC,QAAM,WAAW,oBAAI,IAAgC;AACrD,QAAM,OAAsB,CAAC;AAC7B,QAAM,gBAAgC,CAAC;AACvC,QAAM,aAAa,oBAAI,IAA+B;AACtD,QAAM,MAAM,CAAC,UAAkB,WAAmB,GAAG,QAAQ,KAAK,MAAM;AAExE,QAAM,YAAmC;AAAA,IACvC,YAAY,MAAM,OAAO,CAAC,GAAG;AAC3B,WAAK,KAAK,EAAE,MAAM,KAAK,CAAC;AAAA,IAC1B;AAAA,IACA,gBAAgB,UAAU,QAAQ,QAAQ;AACxC,oBAAc,KAAK,EAAE,UAAU,QAAQ,OAAO,CAAC;AAC/C,YAAM,YAAY,WAAW,IAAI,IAAI,UAAU,MAAM,CAAC;AACtD,UAAI,CAAC,WAAW;AACd,eAAO,QAAQ;AAAA,UACb,IAAI,MAAM,0CAA0C,QAAQ,OAAO,MAAM,IAAI;AAAA,QAC/E;AAAA,MACF;AAEA,aAAO,QAAQ,QAAQ,EAAE,KAAK,MAAM,UAAU,MAAM,CAAC;AAAA,IACvD;AAAA,IACA,UAAU,SAAS;AACjB,eAAS,IAAI,OAAO;AACpB,aAAO,EAAE,SAAS,MAAM,SAAS,OAAO,OAAO,EAAE;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,IAAI;AAEV,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,SAAS,CAAC,GAAG;AACnB,QAAE,UAAU,IAAI,EAAE,GAAG,QAAQ,UAAU;AAAA,IACzC;AAAA,IACA,YAAY;AACV,aAAO,EAAE,UAAU;AAAA,IACrB;AAAA,IACA,KAAK,KAAK;AAER,iBAAW,WAAW,CAAC,GAAG,QAAQ,EAAG,SAAQ,GAAG;AAAA,IAClD;AAAA,IACA,aAAa,UAAU,QAAQ,WAAW;AACxC,iBAAW,IAAI,IAAI,UAAU,MAAM,GAAG,SAAS;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,MAAM,SAAS;AAAA,EAC/B;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/testing.ts"],"sourcesContent":["// Public testing utility — `@immediately-run/sdk/testing` (TESTING_AUTOMATION_SPEC\n// §3/§7). A mock host transport so apps built on this SDK can unit-test their host\n// interaction in CI, with no live bundler/host: drive the SDK's REAL transport path\n// by installing this at the §4 discovery global, then `emit()` host→app pushes and\n// assert your app reacts.\n//\n// import { createMockHost } from '@immediately-run/sdk/testing';\n// const host = createMockHost();\n// host.install(); // SDK now resolves this transport\n// host.stubProtocol('spaces', 'list', () => ({ ok: true, data: [] }));\n// // …render your app / call SDK functions…\n// host.emit({ type: 'mount-add', mount: { path: '/spaces/a', type: 'firestore', id: 'a' } });\n// expect(host.sent).toContainEqual({ type: 'request-mounts', data: {} });\n// host.uninstall();\n//\n// It implements the `sandboxUtils` host transport (sendMessage / protocolRequest /\n// onMessage), so tests exercise the actual `transport()` resolution, the\n// `addListener` type-filter, and `protocolRequest` round-trips — higher fidelity\n// than mocking `./sandboxUtils` wholesale.\n\n/** A message pushed host → app, as it arrives on `onMessage`. */\nexport interface HostMessage {\n type: string;\n [k: string]: unknown;\n}\n\n/** An outbound `sendMessage` captured by the mock. */\nexport interface SentMessage {\n type: string;\n data: Record<string, unknown>;\n}\n\n/** An outbound `protocolRequest` captured by the mock. */\nexport interface ProtocolCall {\n protocol: string;\n method: string;\n params: unknown[];\n}\n\n/** Responder stub for a `protocolRequest(protocol, method, …)`. */\nexport type ProtocolResponder = (params: unknown[]) => unknown;\n\n/** Extra §4 discovery-global fields to publish alongside `transport` on install. */\nexport interface MockHostGlobalExtras {\n runtimeVersion?: string;\n protocolVersion?: string;\n appMountPath?: string;\n}\n\ninterface ImmediatelyRunGlobal extends MockHostGlobalExtras {\n transport?: unknown;\n}\n\n/** The controllable mock host returned by {@link createMockHost}. */\nexport interface MockHost {\n /** The §4 host transport object (also published on the global by `install`). */\n transport: {\n sendMessage(type: string, data?: Record<string, unknown>): void;\n protocolRequest(protocol: string, method: string, params: unknown[]): Promise<unknown>;\n onMessage(handler: (msg: HostMessage) => void): { dispose(): void };\n };\n /** Publish the transport at `globalThis.__immediatelyRun__` (npm-fetched path). */\n install(extras?: MockHostGlobalExtras): void;\n /** Remove the discovery global (call in `afterEach`). */\n uninstall(): void;\n /** Simulate a host → app push; delivered to every live `onMessage` handler. */\n emit(msg: HostMessage): void;\n /** Register a response for a `protocolRequest`; unstubbed calls reject. */\n stubProtocol(protocol: string, method: string, responder: ProtocolResponder): void;\n /** Every `sendMessage` the SDK made, in order. */\n readonly sent: SentMessage[];\n /** Every `protocolRequest` the SDK made, in order. */\n readonly protocolCalls: ProtocolCall[];\n /** Count of live `onMessage` listeners (leak checks). */\n handlerCount(): number;\n}\n\nconst GLOBAL_KEY = '__immediatelyRun__';\n\n/** Build a controllable mock host. Nothing is global until you call `install()`. */\nexport function createMockHost(): MockHost {\n const handlers = new Set<(msg: HostMessage) => void>();\n const sent: SentMessage[] = [];\n const protocolCalls: ProtocolCall[] = [];\n const responders = new Map<string, ProtocolResponder>();\n const key = (protocol: string, method: string) => `${protocol}::${method}`;\n\n const transport: MockHost['transport'] = {\n sendMessage(type, data = {}) {\n sent.push({ type, data });\n },\n protocolRequest(protocol, method, params) {\n protocolCalls.push({ protocol, method, params });\n const responder = responders.get(key(protocol, method));\n if (!responder) {\n return Promise.reject(\n new Error(`mockHost: no stub for protocolRequest('${protocol}', '${method}')`),\n );\n }\n // Resolve async so callers observe real microtask ordering.\n return Promise.resolve().then(() => responder(params));\n },\n onMessage(handler) {\n handlers.add(handler);\n return { dispose: () => handlers.delete(handler) };\n },\n };\n\n const g = globalThis as { [GLOBAL_KEY]?: ImmediatelyRunGlobal };\n\n return {\n transport,\n install(extras = {}) {\n g[GLOBAL_KEY] = { ...extras, transport };\n },\n uninstall() {\n delete g[GLOBAL_KEY];\n },\n emit(msg) {\n // Copy so a handler that unsubscribes mid-emit doesn't mutate the live set.\n for (const handler of [...handlers]) handler(msg);\n },\n stubProtocol(protocol, method, responder) {\n responders.set(key(protocol, method), responder);\n },\n sent,\n protocolCalls,\n handlerCount: () => handlers.size,\n };\n}\n"],"mappings":";AA6EA,MAAM,aAAa;AAGZ,SAAS,iBAA2B;AACzC,QAAM,WAAW,oBAAI,IAAgC;AACrD,QAAM,OAAsB,CAAC;AAC7B,QAAM,gBAAgC,CAAC;AACvC,QAAM,aAAa,oBAAI,IAA+B;AACtD,QAAM,MAAM,CAAC,UAAkB,WAAmB,GAAG,QAAQ,KAAK,MAAM;AAExE,QAAM,YAAmC;AAAA,IACvC,YAAY,MAAM,OAAO,CAAC,GAAG;AAC3B,WAAK,KAAK,EAAE,MAAM,KAAK,CAAC;AAAA,IAC1B;AAAA,IACA,gBAAgB,UAAU,QAAQ,QAAQ;AACxC,oBAAc,KAAK,EAAE,UAAU,QAAQ,OAAO,CAAC;AAC/C,YAAM,YAAY,WAAW,IAAI,IAAI,UAAU,MAAM,CAAC;AACtD,UAAI,CAAC,WAAW;AACd,eAAO,QAAQ;AAAA,UACb,IAAI,MAAM,0CAA0C,QAAQ,OAAO,MAAM,IAAI;AAAA,QAC/E;AAAA,MACF;AAEA,aAAO,QAAQ,QAAQ,EAAE,KAAK,MAAM,UAAU,MAAM,CAAC;AAAA,IACvD;AAAA,IACA,UAAU,SAAS;AACjB,eAAS,IAAI,OAAO;AACpB,aAAO,EAAE,SAAS,MAAM,SAAS,OAAO,OAAO,EAAE;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,IAAI;AAEV,SAAO;AAAA,IACL;AAAA,IACA,QAAQ,SAAS,CAAC,GAAG;AACnB,QAAE,UAAU,IAAI,EAAE,GAAG,QAAQ,UAAU;AAAA,IACzC;AAAA,IACA,YAAY;AACV,aAAO,EAAE,UAAU;AAAA,IACrB;AAAA,IACA,KAAK,KAAK;AAER,iBAAW,WAAW,CAAC,GAAG,QAAQ,EAAG,SAAQ,GAAG;AAAA,IAClD;AAAA,IACA,aAAa,UAAU,QAAQ,WAAW;AACxC,iBAAW,IAAI,IAAI,UAAU,MAAM,GAAG,SAAS;AAAA,IACjD;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,MAAM,SAAS;AAAA,EAC/B;AACF;","names":[]}
|
package/dist/theme.js
CHANGED
package/dist/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/theme.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\nimport { protocolRequest } from './sandboxUtils';\n\n/**\n * The host UI theme, mirrored from the immediately.run host window into the\n * sandbox. Your app can read this to render in step with the host chrome\n * (light / dark).\n *\n * This is the baseline `theme:read` capability — every app may read it. Changing\n * the host theme is a separate, elevated action (`theme:set`), available only to\n * the theme-toggle system app.\n */\nexport type HostTheme = 'light' | 'dark';\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `theme` and\n// answers `request-theme` (wire format: site-main channelBridge.ts). The host's\n// default before it reports is `dark` (sandbox themeState.DEFAULT_THEME).\nconst channel = createPushChannel<HostTheme>({\n pushType: 'theme',\n requestType: 'request-theme',\n initial: 'dark',\n parse: (msg) => (msg.theme === 'light' || msg.theme === 'dark' ? msg.theme : undefined),\n});\n\n/**\n * Returns the current host theme. Poll this for a one-off read; use\n * {@link onHostThemeChange} or {@link useHostTheme} to react to changes.\n */\nexport const getHostTheme = (): HostTheme => channel.get();\n\n/**\n * Subscribe to host theme changes. The listener is invoked immediately with the\n * current theme, then again on every change. Returns an unsubscribe fn.\n */\nexport const onHostThemeChange = (listener: (theme: HostTheme) => void): (() => void) =>\n channel.onChange(listener);\n\n/**\n * React hook returning the current host theme, re-rendering when it changes.\n * The recommended way to implement an app's own `useTheme`: follow the host,\n * allow a local override.\n */\nexport const useHostTheme = (): HostTheme => channel.use();\n\n/**\n * Set the host UI theme — the ELEVATED `theme:set` action (§8.5). The host\n * applies it and re-pushes the new value to every `theme:read` iframe, so your\n * own {@link useHostTheme} confirms the change (the loop closes with no special\n * case). Only a grant holding `theme:set` (e.g. the theme-toggle system app) may\n * call this; any other app is rejected host-side with a `forbidden`\n * {@link Error} (carrying `.code`), regardless of what the app claims. Update\n * optimistically and let the re-push confirm.\n */\nexport const setHostTheme = async (theme: HostTheme): Promise<void> => {\n const res = (await protocolRequest('theme', 'set', [{ theme }])) as\n | { ok: true; data?: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'setHostTheme failed') as Error & {\n code?: string;\n };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n};\n"],"mappings":"AAAA,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAgBhC,MAAM,UAAU,kBAA6B;AAAA,EAC3C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAS,IAAI,UAAU,WAAW,IAAI,UAAU,SAAS,IAAI,QAAQ;AAC/E,CAAC;AAMM,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAMlD,MAAM,oBAAoB,CAAC,aAChC,QAAQ,SAAS,QAAQ;AAOpB,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAWlD,MAAM,eAAe,OAAO,UAAoC;AACrE,QAAM,MAAO,MAAM,gBAAgB,SAAS,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AAI9D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,qBAAqB;AAG3D,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/theme.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\nimport { protocolRequest } from './sandboxUtils';\n\n/**\n * The host UI theme, mirrored from the immediately.run host window into the\n * sandbox. Your app can read this to render in step with the host chrome\n * (light / dark).\n *\n * This is the baseline `theme:read` capability — every app may read it. Changing\n * the host theme is a separate, elevated action (`theme:set`), available only to\n * the theme-toggle system app.\n */\nexport type HostTheme = 'light' | 'dark';\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `theme` and\n// answers `request-theme` (wire format: site-main channelBridge.ts). The host's\n// default before it reports is `dark` (sandbox themeState.DEFAULT_THEME).\nconst channel = createPushChannel<HostTheme>({\n pushType: 'theme',\n requestType: 'request-theme',\n initial: 'dark',\n parse: (msg) => (msg.theme === 'light' || msg.theme === 'dark' ? msg.theme : undefined),\n});\n\n/**\n * Returns the current host theme. Poll this for a one-off read; use\n * {@link onHostThemeChange} or {@link useHostTheme} to react to changes.\n */\nexport const getHostTheme = (): HostTheme => channel.get();\n\n/**\n * Subscribe to host theme changes. The listener is invoked immediately with the\n * current theme, then again on every change. Returns an unsubscribe fn.\n */\nexport const onHostThemeChange = (listener: (theme: HostTheme) => void): (() => void) =>\n channel.onChange(listener);\n\n/**\n * React hook returning the current host theme, re-rendering when it changes.\n * The recommended way to implement an app's own `useTheme`: follow the host,\n * allow a local override.\n */\nexport const useHostTheme = (): HostTheme => channel.use();\n\n/**\n * Set the host UI theme — the ELEVATED `theme:set` action (§8.5). The host\n * applies it and re-pushes the new value to every `theme:read` iframe, so your\n * own {@link useHostTheme} confirms the change (the loop closes with no special\n * case). Only a grant holding `theme:set` (e.g. the theme-toggle system app) may\n * call this; any other app is rejected host-side with a `forbidden`\n * {@link Error} (carrying `.code`), regardless of what the app claims. Update\n * optimistically and let the re-push confirm.\n */\nexport const setHostTheme = async (theme: HostTheme): Promise<void> => {\n const res = (await protocolRequest('theme', 'set', [{ theme }])) as\n | { ok: true; data?: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'setHostTheme failed') as Error & {\n code?: string;\n };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n};\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AAgBhC,MAAM,UAAU,kBAA6B;AAAA,EAC3C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAS,IAAI,UAAU,WAAW,IAAI,UAAU,SAAS,IAAI,QAAQ;AAC/E,CAAC;AAMM,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAMlD,MAAM,oBAAoB,CAAC,aAChC,QAAQ,SAAS,QAAQ;AAOpB,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAWlD,MAAM,eAAe,OAAO,UAAoC;AACrE,QAAM,MAAO,MAAM,gBAAgB,SAAS,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;AAI9D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,qBAAqB;AAG3D,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACF;","names":[]}
|
package/dist/urlUtils.js
CHANGED
package/dist/urlUtils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/urlUtils.ts"],"sourcesContent":["import { joinPaths } from \"./pathUtils\";\nimport { NavigationState, PathState } from \"./TinkerableContext\";\n\nexport const FILES_PREFIX = '/files';\n\n/**\n * Mount point of the Git repository inside the sandbox filesystem. The sandbox\n * fs is rooted at `/` (so apps can reach dynamic mounts like `/firestore`), with\n * the repo mounted here. URL subpaths are repo-relative, so the file router\n * resolves them under `APP_ROOT`.\n */\nexport const APP_ROOT = '/app';\n\n/** Resolve a repo-relative path (e.g. a URL subpath) to its absolute sandbox path. */\nexport const underAppRoot = (repoRelativePath: string): string =>\n joinPaths(APP_ROOT, repoRelativePath);\n\nexport const getOuterHostname = (outerHref:string) => {\n const url = new URL(outerHref);\n return `${url.protocol}//${url.hostname}`;\n}\n\nexport const getSearchParams = (search?: string): Record<string, string> => Object.fromEntries(\n [...(new URLSearchParams(search ?? window.location.search).entries())]);\n\n/**\n * Split a link target into its path part and its `#fragment` (the `#` dropped),\n * e.g. `\"FOO.mdx#sec-8-9\"` → `[\"FOO.mdx\", \"sec-8-9\"]`, `\"#sec-3\"` → `[\"\", \"sec-3\"]`,\n * `\"FOO.mdx\"` → `[\"FOO.mdx\", \"\"]`. Only the **first** `#` splits — a fragment never\n * contains another `#`. The seam between wiki-links (§13) and heading ids (§15):\n * existence is resolved on the path part, the fragment is threaded to navigation.\n * MARKDOWN_SYNTAX_SPEC §13.5.\n */\nexport const splitHash = (target: string): [string, string] => {\n const i = target.indexOf('#');\n return i === -1 ? [target, ''] : [target.slice(0, i), target.slice(i + 1)];\n};\n\n\nexport const parseTarget = (target: string, navigation: NavigationState): NavigationState => {\n const newNavigation = { ...navigation };\n let [prehash, hash] = target.split(\"#\")\n if (prehash) {\n let [path, search] = prehash.split(\"?\")\n if (path) {\n newNavigation.sandboxPath = path\n }\n newNavigation.search = search ? search : '';\n }\n newNavigation.hash = hash ? hash : '';\n return newNavigation\n}\n\n\nexport const maybeParseUrl = (str: string): URL | null => {\n try {\n return new URL(str);\n } catch (_) {\n return null;\n }\n}\n\nexport const isAbsolutePath = (sandboxPath: string) => sandboxPath.startsWith('/');\n\nexport const repositoryPrefixURL = (outerHref:string, navigationState: NavigationState) => constructUrl(outerHref, {\n ...navigationState,\n sandboxPath: ''\n });\n\nexport const constructOuterUrl = (previousOuterHref:string, sandboxTarget:string, navigationState: NavigationState, addFilesPrefix=true):string => {\n if (isAbsolutePath(sandboxTarget)) {\n // Split a trailing `#fragment` off before the target is folded into the\n // sandboxPath, and carry it in `hash` instead — a fragment addresses a section,\n // not a file, so it must not leak into the path (MARKDOWN_SYNTAX_SPEC §13.5). An\n // absolute target with no fragment clears any stale hash from the prior state.\n const [pathPart, hash] = splitHash(sandboxTarget);\n return constructUrl(\n previousOuterHref,\n {\n ...navigationState,\n sandboxPath: addFilesPrefix ? joinPaths(FILES_PREFIX, pathPart) : pathPart,\n hash,\n })\n }\n return (\n new URL(\n sandboxTarget,\n constructUrl(\n previousOuterHref,\n navigationState\n )\n )\n ).toString();\n}\n\nexport const isInternalHref = (outerHref:string, target: string, navigationState: NavigationState) => {\n const parsedUrl = maybeParseUrl(target);\n if (parsedUrl) {\n return target.startsWith(repositoryPrefixURL(outerHref, navigationState));\n }\n // if target is not a valid URL, then assume it's relative.\n return true;\n}\n\nexport type PathSegment = {\n name: string,\n pattern: string,\n transform?: (pathSegment: string) => string,\n // When true, the leading slash that delimits this segment is optional, so the\n // whole `/segment` group can be absent. Used for the trailing sandboxPath:\n // an outer href of `/mode/provider/namespace/repository/ref` (no trailing\n // slash, no sub-path) must still parse, otherwise the regex matches nothing\n // and every segment comes back empty.\n optionalLeadingSlash?: boolean\n}\n\nconst PATH_SEGMENTS: PathSegment[] = [\n { name: 'mode', pattern: '\\\\w+' },\n { name: 'provider', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'namespace', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'repository', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'ref', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'sandboxPath', pattern: '.*', transform: s => `/${s}`, optionalLeadingSlash: true }\n];\n\nconst OUTER_HREF_REGEXP = new RegExp(\n '^' +\n PATH_SEGMENTS.map(({ name, pattern, optionalLeadingSlash }) =>\n optionalLeadingSlash\n ? `(?:\\/(?<${name}>${pattern}))?`\n : `\\/(?<${name}>${pattern})`\n ).join('') +\n \"$\"\n);\n\n\nexport const parsePath = (pathname: string): PathState => {\n const matchResults = pathname.match(OUTER_HREF_REGEXP)?.groups ?? {};\n return PATH_SEGMENTS.reduce((acc: Partial<PathState>, { name, transform }: PathSegment) => {\n let value: string | undefined = undefined;\n if (name in matchResults) {\n value = matchResults[name];\n }\n if (!value) {\n // fall back to default value if var not present in\n value = '';\n }\n if (typeof value === 'string') {\n acc[name] = transform ? transform(value) : value;\n }\n return acc;\n }, {}) as PathState;\n}\n\nexport const parseHref = (href: string): NavigationState => {\n const parsedUrl = new URL(href);\n const pathnameState = parsePath(parsedUrl.pathname);\n return {\n ...pathnameState,\n search: parsedUrl.search.substring(1),\n hash: parsedUrl.hash.substring(1),\n } as NavigationState\n}\n\nconst stripSlashPrefix = (s: string): string => s.startsWith('/') ? s.substring(1) : s;\n\nexport const constructUrl = (outerHref:string, navigationState: NavigationState): string => {\n const path = PATH_SEGMENTS.map(({ name }) => {\n let value = navigationState[name];\n return stripSlashPrefix(value ?? '');\n }).join('/');\n const host = getOuterHostname(outerHref);\n let url = `${host}/${path}`\n if (navigationState.search) {\n url += '?' + navigationState.search\n }\n if (navigationState.hash) {\n url += '#' + navigationState.hash\n }\n return url;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/urlUtils.ts"],"sourcesContent":["import { joinPaths } from \"./pathUtils\";\nimport { NavigationState, PathState } from \"./TinkerableContext\";\n\nexport const FILES_PREFIX = '/files';\n\n/**\n * Mount point of the Git repository inside the sandbox filesystem. The sandbox\n * fs is rooted at `/` (so apps can reach dynamic mounts like `/firestore`), with\n * the repo mounted here. URL subpaths are repo-relative, so the file router\n * resolves them under `APP_ROOT`.\n */\nexport const APP_ROOT = '/app';\n\n/** Resolve a repo-relative path (e.g. a URL subpath) to its absolute sandbox path. */\nexport const underAppRoot = (repoRelativePath: string): string =>\n joinPaths(APP_ROOT, repoRelativePath);\n\nexport const getOuterHostname = (outerHref:string) => {\n const url = new URL(outerHref);\n return `${url.protocol}//${url.hostname}`;\n}\n\nexport const getSearchParams = (search?: string): Record<string, string> => Object.fromEntries(\n [...(new URLSearchParams(search ?? window.location.search).entries())]);\n\n/**\n * Split a link target into its path part and its `#fragment` (the `#` dropped),\n * e.g. `\"FOO.mdx#sec-8-9\"` → `[\"FOO.mdx\", \"sec-8-9\"]`, `\"#sec-3\"` → `[\"\", \"sec-3\"]`,\n * `\"FOO.mdx\"` → `[\"FOO.mdx\", \"\"]`. Only the **first** `#` splits — a fragment never\n * contains another `#`. The seam between wiki-links (§13) and heading ids (§15):\n * existence is resolved on the path part, the fragment is threaded to navigation.\n * MARKDOWN_SYNTAX_SPEC §13.5.\n */\nexport const splitHash = (target: string): [string, string] => {\n const i = target.indexOf('#');\n return i === -1 ? [target, ''] : [target.slice(0, i), target.slice(i + 1)];\n};\n\n\nexport const parseTarget = (target: string, navigation: NavigationState): NavigationState => {\n const newNavigation = { ...navigation };\n let [prehash, hash] = target.split(\"#\")\n if (prehash) {\n let [path, search] = prehash.split(\"?\")\n if (path) {\n newNavigation.sandboxPath = path\n }\n newNavigation.search = search ? search : '';\n }\n newNavigation.hash = hash ? hash : '';\n return newNavigation\n}\n\n\nexport const maybeParseUrl = (str: string): URL | null => {\n try {\n return new URL(str);\n } catch (_) {\n return null;\n }\n}\n\nexport const isAbsolutePath = (sandboxPath: string) => sandboxPath.startsWith('/');\n\nexport const repositoryPrefixURL = (outerHref:string, navigationState: NavigationState) => constructUrl(outerHref, {\n ...navigationState,\n sandboxPath: ''\n });\n\nexport const constructOuterUrl = (previousOuterHref:string, sandboxTarget:string, navigationState: NavigationState, addFilesPrefix=true):string => {\n if (isAbsolutePath(sandboxTarget)) {\n // Split a trailing `#fragment` off before the target is folded into the\n // sandboxPath, and carry it in `hash` instead — a fragment addresses a section,\n // not a file, so it must not leak into the path (MARKDOWN_SYNTAX_SPEC §13.5). An\n // absolute target with no fragment clears any stale hash from the prior state.\n const [pathPart, hash] = splitHash(sandboxTarget);\n return constructUrl(\n previousOuterHref,\n {\n ...navigationState,\n sandboxPath: addFilesPrefix ? joinPaths(FILES_PREFIX, pathPart) : pathPart,\n hash,\n })\n }\n return (\n new URL(\n sandboxTarget,\n constructUrl(\n previousOuterHref,\n navigationState\n )\n )\n ).toString();\n}\n\nexport const isInternalHref = (outerHref:string, target: string, navigationState: NavigationState) => {\n const parsedUrl = maybeParseUrl(target);\n if (parsedUrl) {\n return target.startsWith(repositoryPrefixURL(outerHref, navigationState));\n }\n // if target is not a valid URL, then assume it's relative.\n return true;\n}\n\nexport type PathSegment = {\n name: string,\n pattern: string,\n transform?: (pathSegment: string) => string,\n // When true, the leading slash that delimits this segment is optional, so the\n // whole `/segment` group can be absent. Used for the trailing sandboxPath:\n // an outer href of `/mode/provider/namespace/repository/ref` (no trailing\n // slash, no sub-path) must still parse, otherwise the regex matches nothing\n // and every segment comes back empty.\n optionalLeadingSlash?: boolean\n}\n\nconst PATH_SEGMENTS: PathSegment[] = [\n { name: 'mode', pattern: '\\\\w+' },\n { name: 'provider', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'namespace', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'repository', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'ref', pattern: '[a-zA-Z0-9-_]+' },\n { name: 'sandboxPath', pattern: '.*', transform: s => `/${s}`, optionalLeadingSlash: true }\n];\n\nconst OUTER_HREF_REGEXP = new RegExp(\n '^' +\n PATH_SEGMENTS.map(({ name, pattern, optionalLeadingSlash }) =>\n optionalLeadingSlash\n ? `(?:\\/(?<${name}>${pattern}))?`\n : `\\/(?<${name}>${pattern})`\n ).join('') +\n \"$\"\n);\n\n\nexport const parsePath = (pathname: string): PathState => {\n const matchResults = pathname.match(OUTER_HREF_REGEXP)?.groups ?? {};\n return PATH_SEGMENTS.reduce((acc: Partial<PathState>, { name, transform }: PathSegment) => {\n let value: string | undefined = undefined;\n if (name in matchResults) {\n value = matchResults[name];\n }\n if (!value) {\n // fall back to default value if var not present in\n value = '';\n }\n if (typeof value === 'string') {\n acc[name] = transform ? transform(value) : value;\n }\n return acc;\n }, {}) as PathState;\n}\n\nexport const parseHref = (href: string): NavigationState => {\n const parsedUrl = new URL(href);\n const pathnameState = parsePath(parsedUrl.pathname);\n return {\n ...pathnameState,\n search: parsedUrl.search.substring(1),\n hash: parsedUrl.hash.substring(1),\n } as NavigationState\n}\n\nconst stripSlashPrefix = (s: string): string => s.startsWith('/') ? s.substring(1) : s;\n\nexport const constructUrl = (outerHref:string, navigationState: NavigationState): string => {\n const path = PATH_SEGMENTS.map(({ name }) => {\n let value = navigationState[name];\n return stripSlashPrefix(value ?? '');\n }).join('/');\n const host = getOuterHostname(outerHref);\n let url = `${host}/${path}`\n if (navigationState.search) {\n url += '?' + navigationState.search\n }\n if (navigationState.hash) {\n url += '#' + navigationState.hash\n }\n return url;\n}\n"],"mappings":"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","names":[]}
|
package/dist/vcs.js
CHANGED
package/dist/vcs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vcs.ts"],"sourcesContent":["// The `vcs:read` / `vcs:reset` source-control surface — app-facing wrappers\n// (UI_AS_APPS_SPEC §5.3 Recipe A read channel + §8.4 Recipe B actions;\n// migrate-sidebars-to-apps Phase 05; roadmap R3-52).\n//\n// A first-party contribute panel (the `panel.contribute` app, Phase 06) reads the\n// live source-control state the native `SourceControlPanel` shows — the working-tree\n// diff summary, the branch lineage, and the open-PR list — and can trigger a\n// refresh/reset. The host derives all of it from authenticated GitHub calls +\n// the COW layers; NONE of the underlying `DiffResult` / `FileSystem` / OAuth token\n// ever crosses the boundary (§8.10) — the wire carries only the plain-JSON\n// {@link VcsState} the host projects.\n//\n// Read side: a Recipe-A push channel, the same get/onChange/use trio as\n// `editorContext` / `diagnostics`. Inert until the host wires the channel\n// (site-main `channelBridge`); an app without `vcs:read` simply sees the empty\n// initial. Action side: `protocol-vcs` requests gated host-side — `refreshDiff` /\n// `refreshPRs` by `vcs:read`, `resetWorkingTree` by first-party-only `vcs:reset`.\nimport { createPushChannel } from './pushChannel';\nimport { protocolRequest } from './sandboxUtils';\n\n/** One changed path in the working tree (vs. the loaded ref). `status` mirrors the\n * host `DiffResult` change kinds; `path` is repo-relative. Contents are NOT\n * carried — the native panel showed path + status, and a per-file content diff is\n * a deferred follow-up (plan step 6). */\nexport interface VcsChange {\n path: string;\n status: 'created' | 'modified' | 'deleted';\n}\n\n/** The branch the working tree sits on and the upstream it diverged from\n * (host `BranchInfo`, §15.1). `null` until the user is on a immediately.run-created\n * branch. `upstreamPushable` is `null` while push access is still being probed. */\nexport interface VcsBranch {\n name: string;\n parentRepo: string;\n parentRef: string;\n parentCommitSha: string;\n upstreamPushable: boolean | null;\n}\n\n/** One pull request open from the current branch (host `BranchPR`). */\nexport interface VcsPR {\n number: number;\n url: string;\n title: string;\n state: 'open' | 'closed' | 'merged';\n draft: boolean;\n}\n\n/** The whole source-control snapshot the host projects to a `vcs:read` frame.\n * Plain JSON — never a `DiffResult` / `FileSystem` / `Journal`. */\nexport interface VcsState {\n changes: VcsChange[];\n branch: VcsBranch | null;\n prs: VcsPR[];\n /** True while the host is recomputing the diff — the panel shows a spinner\n * without a separate request (plan gotcha). */\n diffLoading: boolean;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `vcs:read`): an empty, branch-less snapshot. */\nconst EMPTY: VcsState = { changes: [], branch: null, prs: [], diffLoading: false };\n\nconst isChangeArray = (v: unknown): v is VcsChange[] =>\n Array.isArray(v) &&\n v.every(\n (c) =>\n !!c &&\n typeof (c as VcsChange).path === 'string' &&\n typeof (c as VcsChange).status === 'string',\n );\n\nconst channel = createPushChannel<VcsState>({\n pushType: 'vcs-state',\n requestType: 'request-vcs-state',\n initial: EMPTY,\n parse: (msg) => {\n // Require a well-formed `changes` array; tolerate an absent branch/prs. A\n // malformed push is ignored (returns undefined) so the last good state stands.\n if (!isChangeArray(msg.changes)) return undefined;\n const branch =\n msg.branch && typeof msg.branch === 'object' ? (msg.branch as VcsBranch) : null;\n const prs = Array.isArray(msg.prs) ? (msg.prs as VcsPR[]) : [];\n return {\n changes: msg.changes,\n branch,\n prs,\n diffLoading: msg.diffLoading === true,\n };\n },\n});\n\n/** One-off read of the current source-control state. Returns the empty snapshot\n * until the host answers (or if the app lacks `vcs:read`). Use\n * {@link onVcsStateChange} / {@link useVcsState} to react to live updates. */\nexport const getVcsState = (): VcsState => channel.get();\n\n/** Subscribe to source-control changes. Invoked immediately with the current\n * value, then on every host push (diff refresh, PR poll, branch change). Returns\n * an unsubscribe. */\nexport const onVcsStateChange = (listener: (state: VcsState) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current source-control state, re-rendering on every change. */\nexport const useVcsState = (): VcsState => channel.use();\n\n// ---------------------------------------------------------------------------\n// Actions (Recipe B, §8.4). The app NAMES an intent and the HOST performs it —\n// the COW/journal stays in the kernel (§2/§4). `refreshDiff`/`refreshPRs` only\n// cause a host-side recompute + a fresh push (gated `vcs:read`, no new authority);\n// `resetWorkingTree` DISCARDS the user's unsaved work, gated by the first-party-only\n// `vcs:reset` — a fork can never hold it. The arm-then-confirm UX stays in the app;\n// the authority is gated host-side (§8.9), and the host requires `confirm:true` as\n// belt-and-braces (T22).\n// ---------------------------------------------------------------------------\n\n/** An error from a `vcs` action, carrying a machine-readable `.code`. */\nexport interface VcsActionError extends Error {\n code:\n | 'forbidden' // the frame lacks the required capability (`vcs:read` / `vcs:reset`)\n | 'invalid-params' // reset called without `confirm: true`\n | 'no-target' // there is no host contribute session (not in edit mode)\n | 'unknown';\n}\n\ntype VcsResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\nconst vcsRequest = async (method: string, arg: Record<string, unknown> = {}): Promise<void> => {\n const res = (await protocolRequest('vcs', method, [arg])) as VcsResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `vcs ${method} failed`) as VcsActionError;\n err.code = (res?.code as VcsActionError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Ask the host to recompute the working-tree diff and push a fresh {@link VcsState}.\n * Gated `vcs:read`. Rejects with a {@link VcsActionError} (`.code`). */\nexport const refreshDiff = (): Promise<void> => vcsRequest('refreshDiff');\n\n/** Ask the host to re-poll the open PRs and push a fresh {@link VcsState}. Gated\n * `vcs:read`. Rejects with a {@link VcsActionError} (`.code`). */\nexport const refreshPRs = (): Promise<void> => vcsRequest('refreshPRs');\n\n/** Ask the host to DISCARD the working tree (COW writable wipe + journal clear) —\n * irreversible. First-party-only (`vcs:reset`); a fork/preview is refused at the\n * gate. Requires `confirm: true` (host belt-and-braces). Rejects with a\n * {@link VcsActionError} (`.code`). */\nexport const resetWorkingTree = (): Promise<void> => vcsRequest('reset', { confirm: true });\n"],"mappings":"AAiBA,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AA4ChC,MAAM,QAAkB,EAAE,SAAS,CAAC,GAAG,QAAQ,MAAM,KAAK,CAAC,GAAG,aAAa,MAAM;AAEjF,MAAM,gBAAgB,CAAC,MACrB,MAAM,QAAQ,CAAC,KACf,EAAE;AAAA,EACA,CAAC,MACC,CAAC,CAAC,KACF,OAAQ,EAAgB,SAAS,YACjC,OAAQ,EAAgB,WAAW;AACvC;AAEF,MAAM,UAAU,kBAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAQ;AAGd,QAAI,CAAC,cAAc,IAAI,OAAO,EAAG,QAAO;AACxC,UAAM,SACJ,IAAI,UAAU,OAAO,IAAI,WAAW,WAAY,IAAI,SAAuB;AAC7E,UAAM,MAAM,MAAM,QAAQ,IAAI,GAAG,IAAK,IAAI,MAAkB,CAAC;AAC7D,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA,aAAa,IAAI,gBAAgB;AAAA,IACnC;AAAA,EACF;AACF,CAAC;AAKM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAKhD,MAAM,mBAAmB,CAAC,aAC/B,QAAQ,SAAS,QAAQ;AAGpB,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAyBvD,MAAM,aAAa,OAAO,QAAgB,MAA+B,CAAC,MAAqB;AAC7F,QAAM,MAAO,MAAM,gBAAgB,OAAO,QAAQ,CAAC,GAAG,CAAC;AACvD,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,OAAO,MAAM,SAAS;AAC5D,QAAI,OAAQ,KAAK,QAAmC;AACpD,UAAM;AAAA,EACR;AACF;AAIO,MAAM,cAAc,MAAqB,WAAW,aAAa;AAIjE,MAAM,aAAa,MAAqB,WAAW,YAAY;AAM/D,MAAM,mBAAmB,MAAqB,WAAW,SAAS,EAAE,SAAS,KAAK,CAAC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/vcs.ts"],"sourcesContent":["// The `vcs:read` / `vcs:reset` source-control surface — app-facing wrappers\n// (UI_AS_APPS_SPEC §5.3 Recipe A read channel + §8.4 Recipe B actions;\n// migrate-sidebars-to-apps Phase 05; roadmap R3-52).\n//\n// A first-party contribute panel (the `panel.contribute` app, Phase 06) reads the\n// live source-control state the native `SourceControlPanel` shows — the working-tree\n// diff summary, the branch lineage, and the open-PR list — and can trigger a\n// refresh/reset. The host derives all of it from authenticated GitHub calls +\n// the COW layers; NONE of the underlying `DiffResult` / `FileSystem` / OAuth token\n// ever crosses the boundary (§8.10) — the wire carries only the plain-JSON\n// {@link VcsState} the host projects.\n//\n// Read side: a Recipe-A push channel, the same get/onChange/use trio as\n// `editorContext` / `diagnostics`. Inert until the host wires the channel\n// (site-main `channelBridge`); an app without `vcs:read` simply sees the empty\n// initial. Action side: `protocol-vcs` requests gated host-side — `refreshDiff` /\n// `refreshPRs` by `vcs:read`, `resetWorkingTree` by first-party-only `vcs:reset`.\nimport { createPushChannel } from './pushChannel';\nimport { protocolRequest } from './sandboxUtils';\n\n/** One changed path in the working tree (vs. the loaded ref). `status` mirrors the\n * host `DiffResult` change kinds; `path` is repo-relative. Contents are NOT\n * carried — the native panel showed path + status, and a per-file content diff is\n * a deferred follow-up (plan step 6). */\nexport interface VcsChange {\n path: string;\n status: 'created' | 'modified' | 'deleted';\n}\n\n/** The branch the working tree sits on and the upstream it diverged from\n * (host `BranchInfo`, §15.1). `null` until the user is on a immediately.run-created\n * branch. `upstreamPushable` is `null` while push access is still being probed. */\nexport interface VcsBranch {\n name: string;\n parentRepo: string;\n parentRef: string;\n parentCommitSha: string;\n upstreamPushable: boolean | null;\n}\n\n/** One pull request open from the current branch (host `BranchPR`). */\nexport interface VcsPR {\n number: number;\n url: string;\n title: string;\n state: 'open' | 'closed' | 'merged';\n draft: boolean;\n}\n\n/** The whole source-control snapshot the host projects to a `vcs:read` frame.\n * Plain JSON — never a `DiffResult` / `FileSystem` / `Journal`. */\nexport interface VcsState {\n changes: VcsChange[];\n branch: VcsBranch | null;\n prs: VcsPR[];\n /** True while the host is recomputing the diff — the panel shows a spinner\n * without a separate request (plan gotcha). */\n diffLoading: boolean;\n}\n\n/** Value before the host answers — also the value when the app may not read the\n * channel (no `vcs:read`): an empty, branch-less snapshot. */\nconst EMPTY: VcsState = { changes: [], branch: null, prs: [], diffLoading: false };\n\nconst isChangeArray = (v: unknown): v is VcsChange[] =>\n Array.isArray(v) &&\n v.every(\n (c) =>\n !!c &&\n typeof (c as VcsChange).path === 'string' &&\n typeof (c as VcsChange).status === 'string',\n );\n\nconst channel = createPushChannel<VcsState>({\n pushType: 'vcs-state',\n requestType: 'request-vcs-state',\n initial: EMPTY,\n parse: (msg) => {\n // Require a well-formed `changes` array; tolerate an absent branch/prs. A\n // malformed push is ignored (returns undefined) so the last good state stands.\n if (!isChangeArray(msg.changes)) return undefined;\n const branch =\n msg.branch && typeof msg.branch === 'object' ? (msg.branch as VcsBranch) : null;\n const prs = Array.isArray(msg.prs) ? (msg.prs as VcsPR[]) : [];\n return {\n changes: msg.changes,\n branch,\n prs,\n diffLoading: msg.diffLoading === true,\n };\n },\n});\n\n/** One-off read of the current source-control state. Returns the empty snapshot\n * until the host answers (or if the app lacks `vcs:read`). Use\n * {@link onVcsStateChange} / {@link useVcsState} to react to live updates. */\nexport const getVcsState = (): VcsState => channel.get();\n\n/** Subscribe to source-control changes. Invoked immediately with the current\n * value, then on every host push (diff refresh, PR poll, branch change). Returns\n * an unsubscribe. */\nexport const onVcsStateChange = (listener: (state: VcsState) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current source-control state, re-rendering on every change. */\nexport const useVcsState = (): VcsState => channel.use();\n\n// ---------------------------------------------------------------------------\n// Actions (Recipe B, §8.4). The app NAMES an intent and the HOST performs it —\n// the COW/journal stays in the kernel (§2/§4). `refreshDiff`/`refreshPRs` only\n// cause a host-side recompute + a fresh push (gated `vcs:read`, no new authority);\n// `resetWorkingTree` DISCARDS the user's unsaved work, gated by the first-party-only\n// `vcs:reset` — a fork can never hold it. The arm-then-confirm UX stays in the app;\n// the authority is gated host-side (§8.9), and the host requires `confirm:true` as\n// belt-and-braces (T22).\n// ---------------------------------------------------------------------------\n\n/** An error from a `vcs` action, carrying a machine-readable `.code`. */\nexport interface VcsActionError extends Error {\n code:\n | 'forbidden' // the frame lacks the required capability (`vcs:read` / `vcs:reset`)\n | 'invalid-params' // reset called without `confirm: true`\n | 'no-target' // there is no host contribute session (not in edit mode)\n | 'unknown';\n}\n\ntype VcsResult =\n | { ok: true; data: unknown }\n | { ok: false; code: string; message: string };\n\nconst vcsRequest = async (method: string, arg: Record<string, unknown> = {}): Promise<void> => {\n const res = (await protocolRequest('vcs', method, [arg])) as VcsResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `vcs ${method} failed`) as VcsActionError;\n err.code = (res?.code as VcsActionError['code']) ?? 'unknown';\n throw err;\n }\n};\n\n/** Ask the host to recompute the working-tree diff and push a fresh {@link VcsState}.\n * Gated `vcs:read`. Rejects with a {@link VcsActionError} (`.code`). */\nexport const refreshDiff = (): Promise<void> => vcsRequest('refreshDiff');\n\n/** Ask the host to re-poll the open PRs and push a fresh {@link VcsState}. Gated\n * `vcs:read`. Rejects with a {@link VcsActionError} (`.code`). */\nexport const refreshPRs = (): Promise<void> => vcsRequest('refreshPRs');\n\n/** Ask the host to DISCARD the working tree (COW writable wipe + journal clear) —\n * irreversible. First-party-only (`vcs:reset`); a fork/preview is refused at the\n * gate. Requires `confirm: true` (host belt-and-braces). Rejects with a\n * {@link VcsActionError} (`.code`). */\nexport const resetWorkingTree = (): Promise<void> => vcsRequest('reset', { confirm: true });\n"],"mappings":";AAiBA,SAAS,yBAAyB;AAClC,SAAS,uBAAuB;AA4ChC,MAAM,QAAkB,EAAE,SAAS,CAAC,GAAG,QAAQ,MAAM,KAAK,CAAC,GAAG,aAAa,MAAM;AAEjF,MAAM,gBAAgB,CAAC,MACrB,MAAM,QAAQ,CAAC,KACf,EAAE;AAAA,EACA,CAAC,MACC,CAAC,CAAC,KACF,OAAQ,EAAgB,SAAS,YACjC,OAAQ,EAAgB,WAAW;AACvC;AAEF,MAAM,UAAU,kBAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QAAQ;AAGd,QAAI,CAAC,cAAc,IAAI,OAAO,EAAG,QAAO;AACxC,UAAM,SACJ,IAAI,UAAU,OAAO,IAAI,WAAW,WAAY,IAAI,SAAuB;AAC7E,UAAM,MAAM,MAAM,QAAQ,IAAI,GAAG,IAAK,IAAI,MAAkB,CAAC;AAC7D,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA,aAAa,IAAI,gBAAgB;AAAA,IACnC;AAAA,EACF;AACF,CAAC;AAKM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAKhD,MAAM,mBAAmB,CAAC,aAC/B,QAAQ,SAAS,QAAQ;AAGpB,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAyBvD,MAAM,aAAa,OAAO,QAAgB,MAA+B,CAAC,MAAqB;AAC7F,QAAM,MAAO,MAAM,gBAAgB,OAAO,QAAQ,CAAC,GAAG,CAAC;AACvD,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,OAAO,MAAM,SAAS;AAC5D,QAAI,OAAQ,KAAK,QAAmC;AACpD,UAAM;AAAA,EACR;AACF;AAIO,MAAM,cAAc,MAAqB,WAAW,aAAa;AAIjE,MAAM,aAAa,MAAqB,WAAW,YAAY;AAM/D,MAAM,mBAAmB,MAAqB,WAAW,SAAS,EAAE,SAAS,KAAK,CAAC;","names":[]}
|
package/dist/version.cjs
CHANGED
|
@@ -21,7 +21,7 @@ __export(version_exports, {
|
|
|
21
21
|
SDK_VERSION: () => SDK_VERSION
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(version_exports);
|
|
24
|
-
const SDK_VERSION = "0.
|
|
24
|
+
const SDK_VERSION = "0.36.0";
|
|
25
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
26
26
|
0 && (module.exports = {
|
|
27
27
|
SDK_VERSION
|
package/dist/version.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.36.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
|
package/dist/version.d.cts
CHANGED
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
package/dist/version.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.36.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
|
package/package.json
CHANGED