@immediately-run/sdk 0.33.0 → 0.35.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 +1 -0
- package/dist/index.d.ts +1 -0
- 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.js +1 -0
- 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/mdastDeps.cjs +186 -1
- package/dist/safeContent/mdastDeps.js +186 -1
- package/dist/safeContent/parseSafeMdast.cjs +26 -17
- package/dist/safeContent/parseSafeMdast.cjs.map +1 -1
- package/dist/safeContent/parseSafeMdast.js +27 -21
- 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 +2 -2
package/dist/irMarkers.js
CHANGED
package/dist/irMarkers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/irMarkers.ts"],"sourcesContent":["// The `ir.*` load-profiling marker vocabulary + the host-side allowlist + the\n// `ir.interactive` precedence rule (LOAD_PROFILING_SPEC §3/§3.1, R3-46).\n//\n// This module is PURE and dependency-free on purpose: the spec (§3.1) requires the\n// host's vocabulary allowlist to be MIRRORED from this same definition (\"additions\n// to the vocabulary are spec amendments, mirrored in the host allowlist in the same\n// change\"). Keeping the table + validator here, with no transport/React imports,\n// lets the sandbox side (this SDK) and the host (site-main, its own mirrored copy)\n// share an identical contract that is unit-tested in both places.\n\n/**\n * Each `ir.*` marker name → the attribute keys its payload may carry (the §3\n * table). A forwarded marker is accepted only if BOTH its name is defined here AND\n * every attribute key it carries is in that marker's allowed set (LP-5). An empty\n * array means \"no attributes\" (a bare mark).\n */\nexport const IR_MARKERS = {\n \"ir.open\": [\"url\", \"provider\", \"ns\", \"repo\", \"ref\", \"refKind\"],\n \"ir.fetch\": [\"source\", \"bytes\", \"requestCount\", \"cacheHit\", \"httpStatus\"],\n \"ir.mount\": [\"phantomCount\", \"writablePrimed\"],\n \"ir.sandbox.boot\": [],\n \"ir.transpile\": [\"moduleCount\", \"cacheHit\", \"bytesIn\", \"bytesOut\"],\n \"ir.deps\": [\"depCount\", \"bytes\", \"requestCount\", \"cacheHit\", \"cdn\"],\n \"ir.eval\": [\"moduleCount\"],\n \"ir.fmp\": [],\n \"ir.interactive\": [\"cold\"],\n \"ir.verify\": [\"result\", \"blocking\"],\n \"ir.refresh\": [\"bytes\"],\n} as const;\n\n/** A canonical `ir.*` load-profiling marker name (a key of {@link IR_MARKERS}). */\nexport type IrMarkerName = keyof typeof IR_MARKERS;\n\n// Per-module / per-dep sub-marks (§3): a defined aggregate plus a `[…]` selector,\n// e.g. `ir.transpile.mod[/src/App.tsx]` or `ir.deps.pkg[react@18.2.0]`. A sub-mark\n// inherits its aggregate's attribute schema.\nconst SUBMARK_RE = /^(ir\\.transpile\\.mod|ir\\.deps\\.pkg)\\[[^\\]]+\\]$/;\nconst submarkAggregate = (name: string): IrMarkerName | null =>\n name.startsWith(\"ir.transpile.mod\") ? \"ir.transpile\" : name.startsWith(\"ir.deps.pkg\") ? \"ir.deps\" : null;\n\n/** Is `name` a defined top-level `ir.*` marker (not a sub-mark)? */\nexport const isIrMarkerName = (name: string): name is IrMarkerName =>\n Object.prototype.hasOwnProperty.call(IR_MARKERS, name);\n\n/** Is `name` an accepted marker name — a defined top-level marker OR a recognized\n * per-module/per-dep sub-mark? */\nexport const isAllowedMarkerName = (name: string): boolean =>\n isIrMarkerName(name) || SUBMARK_RE.test(name);\n\n/** A marker forwarded across the origin boundary (§3.2): a name, the sandbox-side\n * `performance.now()` timestamp, and the optional attribute payload. */\nexport interface ForwardedMarker {\n name: string;\n /** Sandbox-relative timestamp (`performance.now()`) at emission (§3.2). */\n at: number;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * The LP-5 vocabulary allowlist (pure). Accept a forwarded marker ONLY if its name\n * is in the vocabulary AND every attribute key is in that marker's schema. An\n * unknown name — or a defined name carrying an out-of-schema attribute key — is\n * DROPPED (returns `null`), never recorded. This is the gate against an untrusted\n * sandbox minting arbitrary names/values into the host timeline (an injection\n * surface for dashboards / the deferred RUM endpoint). Sub-marks inherit their\n * aggregate's schema.\n */\nexport function validateMarker(m: ForwardedMarker | null | undefined): ForwardedMarker | null {\n if (!m || typeof m.name !== \"string\") return null;\n if (typeof m.at !== \"number\" || !Number.isFinite(m.at)) return null;\n if (!isAllowedMarkerName(m.name)) return null;\n const base: IrMarkerName = isIrMarkerName(m.name) ? m.name : submarkAggregate(m.name)!;\n const allowed: readonly string[] = IR_MARKERS[base];\n const attrs = m.attrs;\n if (attrs !== undefined) {\n if (typeof attrs !== \"object\" || attrs === null) return null;\n for (const key of Object.keys(attrs)) {\n if (!allowed.includes(key)) return null; // out-of-schema attribute → drop the whole marker\n }\n }\n return { name: m.name, at: m.at, ...(attrs !== undefined ? { attrs } : {}) };\n}\n\n/**\n * `ir.interactive = max(rootRenderCommit, reportReady)` (LP2-3). An app-called\n * `reportReady()` may only DELAY interactive — never advance it before the root\n * render commits. A `reportReady()` that fires early is recorded but takes effect\n * at the commit; if the app never reports, the commit alone stands. Budgets bind to\n * the later of the two, so an app can't game its budget by declaring itself ready\n * before it has rendered anything. PURE.\n */\nexport function resolveInteractive(rootRenderCommitAt: number, reportReadyAt?: number): number {\n if (reportReadyAt === undefined) return rootRenderCommitAt;\n return Math.max(rootRenderCommitAt, reportReadyAt);\n}\n"],"mappings":"AAgBO,MAAM,aAAa;AAAA,EACxB,WAAW,CAAC,OAAO,YAAY,MAAM,QAAQ,OAAO,SAAS;AAAA,EAC7D,YAAY,CAAC,UAAU,SAAS,gBAAgB,YAAY,YAAY;AAAA,EACxE,YAAY,CAAC,gBAAgB,gBAAgB;AAAA,EAC7C,mBAAmB,CAAC;AAAA,EACpB,gBAAgB,CAAC,eAAe,YAAY,WAAW,UAAU;AAAA,EACjE,WAAW,CAAC,YAAY,SAAS,gBAAgB,YAAY,KAAK;AAAA,EAClE,WAAW,CAAC,aAAa;AAAA,EACzB,UAAU,CAAC;AAAA,EACX,kBAAkB,CAAC,MAAM;AAAA,EACzB,aAAa,CAAC,UAAU,UAAU;AAAA,EAClC,cAAc,CAAC,OAAO;AACxB;AAQA,MAAM,aAAa;AACnB,MAAM,mBAAmB,CAAC,SACxB,KAAK,WAAW,kBAAkB,IAAI,iBAAiB,KAAK,WAAW,aAAa,IAAI,YAAY;AAG/F,MAAM,iBAAiB,CAAC,SAC7B,OAAO,UAAU,eAAe,KAAK,YAAY,IAAI;AAIhD,MAAM,sBAAsB,CAAC,SAClC,eAAe,IAAI,KAAK,WAAW,KAAK,IAAI;AAoBvC,SAAS,eAAe,GAA+D;AAC5F,MAAI,CAAC,KAAK,OAAO,EAAE,SAAS,SAAU,QAAO;AAC7C,MAAI,OAAO,EAAE,OAAO,YAAY,CAAC,OAAO,SAAS,EAAE,EAAE,EAAG,QAAO;AAC/D,MAAI,CAAC,oBAAoB,EAAE,IAAI,EAAG,QAAO;AACzC,QAAM,OAAqB,eAAe,EAAE,IAAI,IAAI,EAAE,OAAO,iBAAiB,EAAE,IAAI;AACpF,QAAM,UAA6B,WAAW,IAAI;AAClD,QAAM,QAAQ,EAAE;AAChB,MAAI,UAAU,QAAW;AACvB,QAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,eAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,UAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAAA,IACrC;AAAA,EACF;AACA,SAAO,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG;AAC7E;AAUO,SAAS,mBAAmB,oBAA4B,eAAgC;AAC7F,MAAI,kBAAkB,OAAW,QAAO;AACxC,SAAO,KAAK,IAAI,oBAAoB,aAAa;AACnD;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/irMarkers.ts"],"sourcesContent":["// The `ir.*` load-profiling marker vocabulary + the host-side allowlist + the\n// `ir.interactive` precedence rule (LOAD_PROFILING_SPEC §3/§3.1, R3-46).\n//\n// This module is PURE and dependency-free on purpose: the spec (§3.1) requires the\n// host's vocabulary allowlist to be MIRRORED from this same definition (\"additions\n// to the vocabulary are spec amendments, mirrored in the host allowlist in the same\n// change\"). Keeping the table + validator here, with no transport/React imports,\n// lets the sandbox side (this SDK) and the host (site-main, its own mirrored copy)\n// share an identical contract that is unit-tested in both places.\n\n/**\n * Each `ir.*` marker name → the attribute keys its payload may carry (the §3\n * table). A forwarded marker is accepted only if BOTH its name is defined here AND\n * every attribute key it carries is in that marker's allowed set (LP-5). An empty\n * array means \"no attributes\" (a bare mark).\n */\nexport const IR_MARKERS = {\n \"ir.open\": [\"url\", \"provider\", \"ns\", \"repo\", \"ref\", \"refKind\"],\n \"ir.fetch\": [\"source\", \"bytes\", \"requestCount\", \"cacheHit\", \"httpStatus\"],\n \"ir.mount\": [\"phantomCount\", \"writablePrimed\"],\n \"ir.sandbox.boot\": [],\n \"ir.transpile\": [\"moduleCount\", \"cacheHit\", \"bytesIn\", \"bytesOut\"],\n \"ir.deps\": [\"depCount\", \"bytes\", \"requestCount\", \"cacheHit\", \"cdn\"],\n \"ir.eval\": [\"moduleCount\"],\n \"ir.fmp\": [],\n \"ir.interactive\": [\"cold\"],\n \"ir.verify\": [\"result\", \"blocking\"],\n \"ir.refresh\": [\"bytes\"],\n} as const;\n\n/** A canonical `ir.*` load-profiling marker name (a key of {@link IR_MARKERS}). */\nexport type IrMarkerName = keyof typeof IR_MARKERS;\n\n// Per-module / per-dep sub-marks (§3): a defined aggregate plus a `[…]` selector,\n// e.g. `ir.transpile.mod[/src/App.tsx]` or `ir.deps.pkg[react@18.2.0]`. A sub-mark\n// inherits its aggregate's attribute schema.\nconst SUBMARK_RE = /^(ir\\.transpile\\.mod|ir\\.deps\\.pkg)\\[[^\\]]+\\]$/;\nconst submarkAggregate = (name: string): IrMarkerName | null =>\n name.startsWith(\"ir.transpile.mod\") ? \"ir.transpile\" : name.startsWith(\"ir.deps.pkg\") ? \"ir.deps\" : null;\n\n/** Is `name` a defined top-level `ir.*` marker (not a sub-mark)? */\nexport const isIrMarkerName = (name: string): name is IrMarkerName =>\n Object.prototype.hasOwnProperty.call(IR_MARKERS, name);\n\n/** Is `name` an accepted marker name — a defined top-level marker OR a recognized\n * per-module/per-dep sub-mark? */\nexport const isAllowedMarkerName = (name: string): boolean =>\n isIrMarkerName(name) || SUBMARK_RE.test(name);\n\n/** A marker forwarded across the origin boundary (§3.2): a name, the sandbox-side\n * `performance.now()` timestamp, and the optional attribute payload. */\nexport interface ForwardedMarker {\n name: string;\n /** Sandbox-relative timestamp (`performance.now()`) at emission (§3.2). */\n at: number;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * The LP-5 vocabulary allowlist (pure). Accept a forwarded marker ONLY if its name\n * is in the vocabulary AND every attribute key is in that marker's schema. An\n * unknown name — or a defined name carrying an out-of-schema attribute key — is\n * DROPPED (returns `null`), never recorded. This is the gate against an untrusted\n * sandbox minting arbitrary names/values into the host timeline (an injection\n * surface for dashboards / the deferred RUM endpoint). Sub-marks inherit their\n * aggregate's schema.\n */\nexport function validateMarker(m: ForwardedMarker | null | undefined): ForwardedMarker | null {\n if (!m || typeof m.name !== \"string\") return null;\n if (typeof m.at !== \"number\" || !Number.isFinite(m.at)) return null;\n if (!isAllowedMarkerName(m.name)) return null;\n const base: IrMarkerName = isIrMarkerName(m.name) ? m.name : submarkAggregate(m.name)!;\n const allowed: readonly string[] = IR_MARKERS[base];\n const attrs = m.attrs;\n if (attrs !== undefined) {\n if (typeof attrs !== \"object\" || attrs === null) return null;\n for (const key of Object.keys(attrs)) {\n if (!allowed.includes(key)) return null; // out-of-schema attribute → drop the whole marker\n }\n }\n return { name: m.name, at: m.at, ...(attrs !== undefined ? { attrs } : {}) };\n}\n\n/**\n * `ir.interactive = max(rootRenderCommit, reportReady)` (LP2-3). An app-called\n * `reportReady()` may only DELAY interactive — never advance it before the root\n * render commits. A `reportReady()` that fires early is recorded but takes effect\n * at the commit; if the app never reports, the commit alone stands. Budgets bind to\n * the later of the two, so an app can't game its budget by declaring itself ready\n * before it has rendered anything. PURE.\n */\nexport function resolveInteractive(rootRenderCommitAt: number, reportReadyAt?: number): number {\n if (reportReadyAt === undefined) return rootRenderCommitAt;\n return Math.max(rootRenderCommitAt, reportReadyAt);\n}\n"],"mappings":";AAgBO,MAAM,aAAa;AAAA,EACxB,WAAW,CAAC,OAAO,YAAY,MAAM,QAAQ,OAAO,SAAS;AAAA,EAC7D,YAAY,CAAC,UAAU,SAAS,gBAAgB,YAAY,YAAY;AAAA,EACxE,YAAY,CAAC,gBAAgB,gBAAgB;AAAA,EAC7C,mBAAmB,CAAC;AAAA,EACpB,gBAAgB,CAAC,eAAe,YAAY,WAAW,UAAU;AAAA,EACjE,WAAW,CAAC,YAAY,SAAS,gBAAgB,YAAY,KAAK;AAAA,EAClE,WAAW,CAAC,aAAa;AAAA,EACzB,UAAU,CAAC;AAAA,EACX,kBAAkB,CAAC,MAAM;AAAA,EACzB,aAAa,CAAC,UAAU,UAAU;AAAA,EAClC,cAAc,CAAC,OAAO;AACxB;AAQA,MAAM,aAAa;AACnB,MAAM,mBAAmB,CAAC,SACxB,KAAK,WAAW,kBAAkB,IAAI,iBAAiB,KAAK,WAAW,aAAa,IAAI,YAAY;AAG/F,MAAM,iBAAiB,CAAC,SAC7B,OAAO,UAAU,eAAe,KAAK,YAAY,IAAI;AAIhD,MAAM,sBAAsB,CAAC,SAClC,eAAe,IAAI,KAAK,WAAW,KAAK,IAAI;AAoBvC,SAAS,eAAe,GAA+D;AAC5F,MAAI,CAAC,KAAK,OAAO,EAAE,SAAS,SAAU,QAAO;AAC7C,MAAI,OAAO,EAAE,OAAO,YAAY,CAAC,OAAO,SAAS,EAAE,EAAE,EAAG,QAAO;AAC/D,MAAI,CAAC,oBAAoB,EAAE,IAAI,EAAG,QAAO;AACzC,QAAM,OAAqB,eAAe,EAAE,IAAI,IAAI,EAAE,OAAO,iBAAiB,EAAE,IAAI;AACpF,QAAM,UAA6B,WAAW,IAAI;AAClD,QAAM,QAAQ,EAAE;AAChB,MAAI,UAAU,QAAW;AACvB,QAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,eAAW,OAAO,OAAO,KAAK,KAAK,GAAG;AACpC,UAAI,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAAA,IACrC;AAAA,EACF;AACA,SAAO,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,IAAI,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG;AAC7E;AAUO,SAAS,mBAAmB,oBAA4B,eAAgC;AAC7F,MAAI,kBAAkB,OAAW,QAAO;AACxC,SAAO,KAAK,IAAI,oBAAoB,aAAa;AACnD;","names":[]}
|
package/dist/launch.cjs
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __typeError = (msg) => {
|
|
7
|
+
throw TypeError(msg);
|
|
8
|
+
};
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
23
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
24
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
25
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
26
|
+
var launch_exports = {};
|
|
27
|
+
__export(launch_exports, {
|
|
28
|
+
launch: () => launch
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(launch_exports);
|
|
31
|
+
var import_sandboxUtils = require("./sandboxUtils");
|
|
32
|
+
var _status, _dismissListeners, _ended;
|
|
33
|
+
const liveHandles = /* @__PURE__ */ new Map();
|
|
34
|
+
(0, import_sandboxUtils.addListener)("launch-ended", (m) => {
|
|
35
|
+
const h = liveHandles.get(m.launchId);
|
|
36
|
+
if (h) h._end(m.status);
|
|
37
|
+
});
|
|
38
|
+
class LaunchHandleImpl {
|
|
39
|
+
constructor(launchId) {
|
|
40
|
+
this.launchId = launchId;
|
|
41
|
+
__privateAdd(this, _status, "running");
|
|
42
|
+
__privateAdd(this, _dismissListeners, /* @__PURE__ */ new Set());
|
|
43
|
+
__privateAdd(this, _ended, false);
|
|
44
|
+
liveHandles.set(launchId, this);
|
|
45
|
+
}
|
|
46
|
+
get status() {
|
|
47
|
+
return __privateGet(this, _status);
|
|
48
|
+
}
|
|
49
|
+
dismiss() {
|
|
50
|
+
if (__privateGet(this, _ended)) return;
|
|
51
|
+
(0, import_sandboxUtils.sendMessage)("launch-dismiss", { launchId: this.launchId });
|
|
52
|
+
}
|
|
53
|
+
onDismiss(cb) {
|
|
54
|
+
if (__privateGet(this, _ended)) {
|
|
55
|
+
queueMicrotask(cb);
|
|
56
|
+
return () => {
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
__privateGet(this, _dismissListeners).add(cb);
|
|
60
|
+
return () => {
|
|
61
|
+
__privateGet(this, _dismissListeners).delete(cb);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Host-driven terminal transition — the only writer of `status`. Idempotent. */
|
|
65
|
+
_end(status) {
|
|
66
|
+
if (__privateGet(this, _ended)) return;
|
|
67
|
+
__privateSet(this, _ended, true);
|
|
68
|
+
__privateSet(this, _status, status);
|
|
69
|
+
liveHandles.delete(this.launchId);
|
|
70
|
+
const listeners = [...__privateGet(this, _dismissListeners)];
|
|
71
|
+
__privateGet(this, _dismissListeners).clear();
|
|
72
|
+
for (const l of listeners) {
|
|
73
|
+
try {
|
|
74
|
+
l();
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
_status = new WeakMap();
|
|
81
|
+
_dismissListeners = new WeakMap();
|
|
82
|
+
_ended = new WeakMap();
|
|
83
|
+
const launch = async (target, opts) => {
|
|
84
|
+
const res = await (0, import_sandboxUtils.protocolRequest)("launch", "create", [{ target, opts }]);
|
|
85
|
+
if (!res || res.ok !== true || !res.data?.launchId) {
|
|
86
|
+
const code = res && res.ok === false ? res.code ?? "unknown" : "unknown";
|
|
87
|
+
return { ok: false, code };
|
|
88
|
+
}
|
|
89
|
+
return new LaunchHandleImpl(res.data.launchId);
|
|
90
|
+
};
|
|
91
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
92
|
+
0 && (module.exports = {
|
|
93
|
+
launch
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=launch.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/launch.ts"],"sourcesContent":["// Launch — the TO-RUN twin of `invokeTask` (STANDING_APP_LIFECYCLE_SPEC §1–§6).\n// Where `invokeTask` is FOR-RESULT (await one value, callee exits), `launch` is\n// TO-RUN (non-blocking, standing): it starts a bound program RUNNING in a region\n// and hands back a HANDLE, never a typed value (R-SAL-1). The launched app earns\n// its OWN grants — the only launcher→launched data path is the explicitly\n// delegated `capDir`/`capFile` in `input` (§5), attenuated + tainted + gated host-\n// side. The host owns the whole lifecycle (visibility, budget, revocation,\n// teardown); the launcher only OBSERVES via the handle.\n//\n// A caller that wants a typed value back uses `invokeTask` instead — the two are\n// siblings, not a replacement.\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\n\n/** Where a launched program runs (§6). `overlay` covers the caller's own region\n * with opaque host chrome; `stage` replaces the focal app (the elevated into-\n * stage surface, §7 — refused above the stage-principal ceiling). */\nexport type LaunchRegion = 'overlay' | 'stage';\n\n/**\n * What to launch (§3) — binding-resolved, NEVER a caller-named app. Exactly one of:\n * - `entryPoint`: a sibling entry point of the caller's OWN repo (the mini-app\n * case, `AGENT_AUTHORING §5`) — **rejected `forbidden` until program-identity\n * `appKey` lands** (R-SAL-2a);\n * - `task`: a task contract (`open-project`, …), resolved through the user-\n * overridable `task.<name>` binding to whichever app the user bound (§3 kind 2).\n */\nexport interface LaunchTarget {\n /** Kind 1 — a sibling entry point of the caller's own repo (mini-app overlay). */\n entryPoint?: string;\n /** Kind 2 — a task contract name (the host resolves the bound provider). */\n task?: string;\n /** Optional accepted contract version for a `task` target (semver, e.g. `^1`). */\n version?: string;\n}\n\nexport interface LaunchOptions {\n /** Where it runs (§6). */\n region: LaunchRegion;\n /**\n * Delegations + plain data handed to the launched app. `$cap:'dir'|'file'`\n * markers (`capDir`/`capFile`, exported from `./tasks`) are resolved against the\n * launcher's OWN grants and minted as attenuated, `ro`-by-default chroots (§5) —\n * you can only delegate what you already hold. Everything else is plain data.\n */\n input?: Record<string, unknown>;\n}\n\n/** The live state of a launch (§2). Terminal states (`dismissed`/`revoked`/\n * `failed`) are reached identically for self-exit, user dismiss, and host revoke —\n * the host debounces so the launcher gets no timing oracle (R-SAL-1 / §6.4). */\nexport type LaunchStatus = 'running' | 'dismissed' | 'revoked' | 'failed';\n\n/** Machine codes a refused `launch` resolves with (§8). */\nexport type LaunchErrorCode =\n | 'forbidden' // undeclared target; sibling (pre-AA-01); self-resolving target; stage cap absent\n | 'unsupported' // no provider bound / entry point absent / unknown contract version\n | 'budget' // over an R-SAL-8 concurrency/rate limit\n | 'revoked' // a delegated mount was revoked during the create-and-bind window\n | 'cancelled' // the user dismissed the host launch affordance (into-stage only)\n | 'invalid-params' // a malformed `capDir`/`capFile` (`..`/absolute/mode-escalation)\n | 'unknown';\n\n/**\n * The control channel back to a launch — the ONLY thing a launcher gets (§2).\n * There is no typed return value (R-SAL-1); `status`/`onDismiss` are debounced so\n * they cannot time-distinguish a self-exit from a user dismiss.\n */\nexport interface LaunchHandle {\n /** Host-assigned id for this launch. */\n readonly launchId: string;\n /** The current lifecycle state (§2). */\n readonly status: LaunchStatus;\n /** Ask the host to tear this launch down. Idempotent (double-dismiss is a no-op). */\n dismiss(): void;\n /**\n * Observe the launch ending (self-exit / user dismiss / host revoke — fired\n * identically, R-SAL-1). Returns an unsubscribe fn. Fires at most once; if the\n * launch has already ended it fires on the next tick.\n */\n onDismiss(cb: () => void): () => void;\n}\n\ninterface LaunchEndedMessage {\n launchId: string;\n status: Exclude<LaunchStatus, 'running'>;\n}\n\n/** Live handles awaiting their terminal `launch-ended` message, keyed by launchId. */\nconst liveHandles = new Map<string, LaunchHandleImpl>();\n\n// The host delivers ONE `launch-ended` message per launch when it tears down —\n// the SAME message shape for self-exit, dismiss, and revoke (the host debounces\n// so the timing is not an oracle, §6.4). We fan it out to the matching handle.\naddListener('launch-ended', (m: LaunchEndedMessage) => {\n const h = liveHandles.get(m.launchId);\n if (h) h._end(m.status);\n});\n\nclass LaunchHandleImpl implements LaunchHandle {\n #status: LaunchStatus = 'running';\n #dismissListeners = new Set<() => void>();\n #ended = false;\n\n constructor(readonly launchId: string) {\n liveHandles.set(launchId, this);\n }\n\n get status(): LaunchStatus {\n return this.#status;\n }\n\n dismiss(): void {\n if (this.#ended) return;\n // Fire-and-forget: the host owns teardown and answers with `launch-ended`,\n // which drives `_end` (so status/onDismiss are host-authoritative, never\n // optimistically local — a dismiss the host refuses would otherwise desync).\n sendMessage('launch-dismiss', { launchId: this.launchId });\n }\n\n onDismiss(cb: () => void): () => void {\n if (this.#ended) {\n // Already ended: fire on the next tick so the contract (\"returns an\n // unsubscribe\") holds and the callback never runs synchronously mid-register.\n queueMicrotask(cb);\n return () => {};\n }\n this.#dismissListeners.add(cb);\n return () => {\n this.#dismissListeners.delete(cb);\n };\n }\n\n /** Host-driven terminal transition — the only writer of `status`. Idempotent. */\n _end(status: Exclude<LaunchStatus, 'running'>): void {\n if (this.#ended) return;\n this.#ended = true;\n this.#status = status;\n liveHandles.delete(this.launchId);\n const listeners = [...this.#dismissListeners];\n this.#dismissListeners.clear();\n for (const l of listeners) {\n try {\n l();\n } catch {\n /* a launcher's own callback must never wedge teardown */\n }\n }\n }\n}\n\n/**\n * Launch a bound program to RUN in a region (§2). Non-blocking: resolves once the\n * frame is created and bound, with a {@link LaunchHandle} — or a typed\n * `{ ok:false, code }` on refusal (§8), NEVER a throw for an ordinary refusal (so\n * a launcher branches on `code` without a try/catch). The launched app runs under\n * its OWN grants; the launcher's authority does not flow to it (R-SAL-4).\n *\n * const h = await launch({ task: 'open-project' }, {\n * region: 'stage',\n * input: { dir: capDir({ mountId: 'space:abc', relPath: 'proj' }, { mode: 'ro' }) },\n * });\n * if ('ok' in h && h.ok === false) { ...handle h.code... }\n * else { h.onDismiss(() => ...); }\n */\nexport const launch = async (\n target: LaunchTarget,\n opts: LaunchOptions,\n): Promise<LaunchHandle | { ok: false; code: LaunchErrorCode }> => {\n // The host wraps a successful handler return as `{ ok:true, data }` (the same\n // Recipe-B framing `invokeTask` uses); a refusal is `{ ok:false, code }`.\n const res = (await protocolRequest('launch', 'create', [{ target, opts }])) as\n | { ok: true; data: { launchId: string } }\n | { ok: false; code?: LaunchErrorCode }\n | undefined;\n if (!res || res.ok !== true || !res.data?.launchId) {\n const code = res && res.ok === false ? (res.code ?? 'unknown') : 'unknown';\n return { ok: false, code };\n }\n return new LaunchHandleImpl(res.data.launchId);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,0BAA0D;AAX1D;AAwFA,MAAM,cAAc,oBAAI,IAA8B;AAAA,IAKtD,iCAAY,gBAAgB,CAAC,MAA0B;AACrD,QAAM,IAAI,YAAY,IAAI,EAAE,QAAQ;AACpC,MAAI,EAAG,GAAE,KAAK,EAAE,MAAM;AACxB,CAAC;AAED,MAAM,iBAAyC;AAAA,EAK7C,YAAqB,UAAkB;AAAlB;AAJrB,gCAAwB;AACxB,0CAAoB,oBAAI,IAAgB;AACxC,+BAAS;AAGP,gBAAY,IAAI,UAAU,IAAI;AAAA,EAChC;AAAA,EAEA,IAAI,SAAuB;AACzB,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,UAAgB;AACd,QAAI,mBAAK,QAAQ;AAIjB,yCAAY,kBAAkB,EAAE,UAAU,KAAK,SAAS,CAAC;AAAA,EAC3D;AAAA,EAEA,UAAU,IAA4B;AACpC,QAAI,mBAAK,SAAQ;AAGf,qBAAe,EAAE;AACjB,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AACA,uBAAK,mBAAkB,IAAI,EAAE;AAC7B,WAAO,MAAM;AACX,yBAAK,mBAAkB,OAAO,EAAE;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAGA,KAAK,QAAgD;AACnD,QAAI,mBAAK,QAAQ;AACjB,uBAAK,QAAS;AACd,uBAAK,SAAU;AACf,gBAAY,OAAO,KAAK,QAAQ;AAChC,UAAM,YAAY,CAAC,GAAG,mBAAK,kBAAiB;AAC5C,uBAAK,mBAAkB,MAAM;AAC7B,eAAW,KAAK,WAAW;AACzB,UAAI;AACF,UAAE;AAAA,MACJ,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AAjDE;AACA;AACA;AA+DK,MAAM,SAAS,OACpB,QACA,SACiE;AAGjE,QAAM,MAAO,UAAM,qCAAgB,UAAU,UAAU,CAAC,EAAE,QAAQ,KAAK,CAAC,CAAC;AAIzE,MAAI,CAAC,OAAO,IAAI,OAAO,QAAQ,CAAC,IAAI,MAAM,UAAU;AAClD,UAAM,OAAO,OAAO,IAAI,OAAO,QAAS,IAAI,QAAQ,YAAa;AACjE,WAAO,EAAE,IAAI,OAAO,KAAK;AAAA,EAC3B;AACA,SAAO,IAAI,iBAAiB,IAAI,KAAK,QAAQ;AAC/C;","names":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Where a launched program runs (§6). `overlay` covers the caller's own region
|
|
2
|
+
* with opaque host chrome; `stage` replaces the focal app (the elevated into-
|
|
3
|
+
* stage surface, §7 — refused above the stage-principal ceiling). */
|
|
4
|
+
type LaunchRegion = 'overlay' | 'stage';
|
|
5
|
+
/**
|
|
6
|
+
* What to launch (§3) — binding-resolved, NEVER a caller-named app. Exactly one of:
|
|
7
|
+
* - `entryPoint`: a sibling entry point of the caller's OWN repo (the mini-app
|
|
8
|
+
* case, `AGENT_AUTHORING §5`) — **rejected `forbidden` until program-identity
|
|
9
|
+
* `appKey` lands** (R-SAL-2a);
|
|
10
|
+
* - `task`: a task contract (`open-project`, …), resolved through the user-
|
|
11
|
+
* overridable `task.<name>` binding to whichever app the user bound (§3 kind 2).
|
|
12
|
+
*/
|
|
13
|
+
interface LaunchTarget {
|
|
14
|
+
/** Kind 1 — a sibling entry point of the caller's own repo (mini-app overlay). */
|
|
15
|
+
entryPoint?: string;
|
|
16
|
+
/** Kind 2 — a task contract name (the host resolves the bound provider). */
|
|
17
|
+
task?: string;
|
|
18
|
+
/** Optional accepted contract version for a `task` target (semver, e.g. `^1`). */
|
|
19
|
+
version?: string;
|
|
20
|
+
}
|
|
21
|
+
interface LaunchOptions {
|
|
22
|
+
/** Where it runs (§6). */
|
|
23
|
+
region: LaunchRegion;
|
|
24
|
+
/**
|
|
25
|
+
* Delegations + plain data handed to the launched app. `$cap:'dir'|'file'`
|
|
26
|
+
* markers (`capDir`/`capFile`, exported from `./tasks`) are resolved against the
|
|
27
|
+
* launcher's OWN grants and minted as attenuated, `ro`-by-default chroots (§5) —
|
|
28
|
+
* you can only delegate what you already hold. Everything else is plain data.
|
|
29
|
+
*/
|
|
30
|
+
input?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/** The live state of a launch (§2). Terminal states (`dismissed`/`revoked`/
|
|
33
|
+
* `failed`) are reached identically for self-exit, user dismiss, and host revoke —
|
|
34
|
+
* the host debounces so the launcher gets no timing oracle (R-SAL-1 / §6.4). */
|
|
35
|
+
type LaunchStatus = 'running' | 'dismissed' | 'revoked' | 'failed';
|
|
36
|
+
/** Machine codes a refused `launch` resolves with (§8). */
|
|
37
|
+
type LaunchErrorCode = 'forbidden' | 'unsupported' | 'budget' | 'revoked' | 'cancelled' | 'invalid-params' | 'unknown';
|
|
38
|
+
/**
|
|
39
|
+
* The control channel back to a launch — the ONLY thing a launcher gets (§2).
|
|
40
|
+
* There is no typed return value (R-SAL-1); `status`/`onDismiss` are debounced so
|
|
41
|
+
* they cannot time-distinguish a self-exit from a user dismiss.
|
|
42
|
+
*/
|
|
43
|
+
interface LaunchHandle {
|
|
44
|
+
/** Host-assigned id for this launch. */
|
|
45
|
+
readonly launchId: string;
|
|
46
|
+
/** The current lifecycle state (§2). */
|
|
47
|
+
readonly status: LaunchStatus;
|
|
48
|
+
/** Ask the host to tear this launch down. Idempotent (double-dismiss is a no-op). */
|
|
49
|
+
dismiss(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Observe the launch ending (self-exit / user dismiss / host revoke — fired
|
|
52
|
+
* identically, R-SAL-1). Returns an unsubscribe fn. Fires at most once; if the
|
|
53
|
+
* launch has already ended it fires on the next tick.
|
|
54
|
+
*/
|
|
55
|
+
onDismiss(cb: () => void): () => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Launch a bound program to RUN in a region (§2). Non-blocking: resolves once the
|
|
59
|
+
* frame is created and bound, with a {@link LaunchHandle} — or a typed
|
|
60
|
+
* `{ ok:false, code }` on refusal (§8), NEVER a throw for an ordinary refusal (so
|
|
61
|
+
* a launcher branches on `code` without a try/catch). The launched app runs under
|
|
62
|
+
* its OWN grants; the launcher's authority does not flow to it (R-SAL-4).
|
|
63
|
+
*
|
|
64
|
+
* const h = await launch({ task: 'open-project' }, {
|
|
65
|
+
* region: 'stage',
|
|
66
|
+
* input: { dir: capDir({ mountId: 'space:abc', relPath: 'proj' }, { mode: 'ro' }) },
|
|
67
|
+
* });
|
|
68
|
+
* if ('ok' in h && h.ok === false) { ...handle h.code... }
|
|
69
|
+
* else { h.onDismiss(() => ...); }
|
|
70
|
+
*/
|
|
71
|
+
declare const launch: (target: LaunchTarget, opts: LaunchOptions) => Promise<LaunchHandle | {
|
|
72
|
+
ok: false;
|
|
73
|
+
code: LaunchErrorCode;
|
|
74
|
+
}>;
|
|
75
|
+
|
|
76
|
+
export { type LaunchErrorCode, type LaunchHandle, type LaunchOptions, type LaunchRegion, type LaunchStatus, type LaunchTarget, launch };
|
package/dist/launch.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Where a launched program runs (§6). `overlay` covers the caller's own region
|
|
2
|
+
* with opaque host chrome; `stage` replaces the focal app (the elevated into-
|
|
3
|
+
* stage surface, §7 — refused above the stage-principal ceiling). */
|
|
4
|
+
type LaunchRegion = 'overlay' | 'stage';
|
|
5
|
+
/**
|
|
6
|
+
* What to launch (§3) — binding-resolved, NEVER a caller-named app. Exactly one of:
|
|
7
|
+
* - `entryPoint`: a sibling entry point of the caller's OWN repo (the mini-app
|
|
8
|
+
* case, `AGENT_AUTHORING §5`) — **rejected `forbidden` until program-identity
|
|
9
|
+
* `appKey` lands** (R-SAL-2a);
|
|
10
|
+
* - `task`: a task contract (`open-project`, …), resolved through the user-
|
|
11
|
+
* overridable `task.<name>` binding to whichever app the user bound (§3 kind 2).
|
|
12
|
+
*/
|
|
13
|
+
interface LaunchTarget {
|
|
14
|
+
/** Kind 1 — a sibling entry point of the caller's own repo (mini-app overlay). */
|
|
15
|
+
entryPoint?: string;
|
|
16
|
+
/** Kind 2 — a task contract name (the host resolves the bound provider). */
|
|
17
|
+
task?: string;
|
|
18
|
+
/** Optional accepted contract version for a `task` target (semver, e.g. `^1`). */
|
|
19
|
+
version?: string;
|
|
20
|
+
}
|
|
21
|
+
interface LaunchOptions {
|
|
22
|
+
/** Where it runs (§6). */
|
|
23
|
+
region: LaunchRegion;
|
|
24
|
+
/**
|
|
25
|
+
* Delegations + plain data handed to the launched app. `$cap:'dir'|'file'`
|
|
26
|
+
* markers (`capDir`/`capFile`, exported from `./tasks`) are resolved against the
|
|
27
|
+
* launcher's OWN grants and minted as attenuated, `ro`-by-default chroots (§5) —
|
|
28
|
+
* you can only delegate what you already hold. Everything else is plain data.
|
|
29
|
+
*/
|
|
30
|
+
input?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/** The live state of a launch (§2). Terminal states (`dismissed`/`revoked`/
|
|
33
|
+
* `failed`) are reached identically for self-exit, user dismiss, and host revoke —
|
|
34
|
+
* the host debounces so the launcher gets no timing oracle (R-SAL-1 / §6.4). */
|
|
35
|
+
type LaunchStatus = 'running' | 'dismissed' | 'revoked' | 'failed';
|
|
36
|
+
/** Machine codes a refused `launch` resolves with (§8). */
|
|
37
|
+
type LaunchErrorCode = 'forbidden' | 'unsupported' | 'budget' | 'revoked' | 'cancelled' | 'invalid-params' | 'unknown';
|
|
38
|
+
/**
|
|
39
|
+
* The control channel back to a launch — the ONLY thing a launcher gets (§2).
|
|
40
|
+
* There is no typed return value (R-SAL-1); `status`/`onDismiss` are debounced so
|
|
41
|
+
* they cannot time-distinguish a self-exit from a user dismiss.
|
|
42
|
+
*/
|
|
43
|
+
interface LaunchHandle {
|
|
44
|
+
/** Host-assigned id for this launch. */
|
|
45
|
+
readonly launchId: string;
|
|
46
|
+
/** The current lifecycle state (§2). */
|
|
47
|
+
readonly status: LaunchStatus;
|
|
48
|
+
/** Ask the host to tear this launch down. Idempotent (double-dismiss is a no-op). */
|
|
49
|
+
dismiss(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Observe the launch ending (self-exit / user dismiss / host revoke — fired
|
|
52
|
+
* identically, R-SAL-1). Returns an unsubscribe fn. Fires at most once; if the
|
|
53
|
+
* launch has already ended it fires on the next tick.
|
|
54
|
+
*/
|
|
55
|
+
onDismiss(cb: () => void): () => void;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Launch a bound program to RUN in a region (§2). Non-blocking: resolves once the
|
|
59
|
+
* frame is created and bound, with a {@link LaunchHandle} — or a typed
|
|
60
|
+
* `{ ok:false, code }` on refusal (§8), NEVER a throw for an ordinary refusal (so
|
|
61
|
+
* a launcher branches on `code` without a try/catch). The launched app runs under
|
|
62
|
+
* its OWN grants; the launcher's authority does not flow to it (R-SAL-4).
|
|
63
|
+
*
|
|
64
|
+
* const h = await launch({ task: 'open-project' }, {
|
|
65
|
+
* region: 'stage',
|
|
66
|
+
* input: { dir: capDir({ mountId: 'space:abc', relPath: 'proj' }, { mode: 'ro' }) },
|
|
67
|
+
* });
|
|
68
|
+
* if ('ok' in h && h.ok === false) { ...handle h.code... }
|
|
69
|
+
* else { h.onDismiss(() => ...); }
|
|
70
|
+
*/
|
|
71
|
+
declare const launch: (target: LaunchTarget, opts: LaunchOptions) => Promise<LaunchHandle | {
|
|
72
|
+
ok: false;
|
|
73
|
+
code: LaunchErrorCode;
|
|
74
|
+
}>;
|
|
75
|
+
|
|
76
|
+
export { type LaunchErrorCode, type LaunchHandle, type LaunchOptions, type LaunchRegion, type LaunchStatus, type LaunchTarget, launch };
|
package/dist/launch.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__privateAdd,
|
|
3
|
+
__privateGet,
|
|
4
|
+
__privateSet
|
|
5
|
+
} from "./chunk-VHAA22YE.js";
|
|
6
|
+
var _status, _dismissListeners, _ended;
|
|
7
|
+
import { protocolRequest, sendMessage, addListener } from "./sandboxUtils";
|
|
8
|
+
const liveHandles = /* @__PURE__ */ new Map();
|
|
9
|
+
addListener("launch-ended", (m) => {
|
|
10
|
+
const h = liveHandles.get(m.launchId);
|
|
11
|
+
if (h) h._end(m.status);
|
|
12
|
+
});
|
|
13
|
+
class LaunchHandleImpl {
|
|
14
|
+
constructor(launchId) {
|
|
15
|
+
this.launchId = launchId;
|
|
16
|
+
__privateAdd(this, _status, "running");
|
|
17
|
+
__privateAdd(this, _dismissListeners, /* @__PURE__ */ new Set());
|
|
18
|
+
__privateAdd(this, _ended, false);
|
|
19
|
+
liveHandles.set(launchId, this);
|
|
20
|
+
}
|
|
21
|
+
get status() {
|
|
22
|
+
return __privateGet(this, _status);
|
|
23
|
+
}
|
|
24
|
+
dismiss() {
|
|
25
|
+
if (__privateGet(this, _ended)) return;
|
|
26
|
+
sendMessage("launch-dismiss", { launchId: this.launchId });
|
|
27
|
+
}
|
|
28
|
+
onDismiss(cb) {
|
|
29
|
+
if (__privateGet(this, _ended)) {
|
|
30
|
+
queueMicrotask(cb);
|
|
31
|
+
return () => {
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
__privateGet(this, _dismissListeners).add(cb);
|
|
35
|
+
return () => {
|
|
36
|
+
__privateGet(this, _dismissListeners).delete(cb);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Host-driven terminal transition — the only writer of `status`. Idempotent. */
|
|
40
|
+
_end(status) {
|
|
41
|
+
if (__privateGet(this, _ended)) return;
|
|
42
|
+
__privateSet(this, _ended, true);
|
|
43
|
+
__privateSet(this, _status, status);
|
|
44
|
+
liveHandles.delete(this.launchId);
|
|
45
|
+
const listeners = [...__privateGet(this, _dismissListeners)];
|
|
46
|
+
__privateGet(this, _dismissListeners).clear();
|
|
47
|
+
for (const l of listeners) {
|
|
48
|
+
try {
|
|
49
|
+
l();
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
_status = new WeakMap();
|
|
56
|
+
_dismissListeners = new WeakMap();
|
|
57
|
+
_ended = new WeakMap();
|
|
58
|
+
const launch = async (target, opts) => {
|
|
59
|
+
const res = await protocolRequest("launch", "create", [{ target, opts }]);
|
|
60
|
+
if (!res || res.ok !== true || !res.data?.launchId) {
|
|
61
|
+
const code = res && res.ok === false ? res.code ?? "unknown" : "unknown";
|
|
62
|
+
return { ok: false, code };
|
|
63
|
+
}
|
|
64
|
+
return new LaunchHandleImpl(res.data.launchId);
|
|
65
|
+
};
|
|
66
|
+
export {
|
|
67
|
+
launch
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=launch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/launch.ts"],"sourcesContent":["// Launch — the TO-RUN twin of `invokeTask` (STANDING_APP_LIFECYCLE_SPEC §1–§6).\n// Where `invokeTask` is FOR-RESULT (await one value, callee exits), `launch` is\n// TO-RUN (non-blocking, standing): it starts a bound program RUNNING in a region\n// and hands back a HANDLE, never a typed value (R-SAL-1). The launched app earns\n// its OWN grants — the only launcher→launched data path is the explicitly\n// delegated `capDir`/`capFile` in `input` (§5), attenuated + tainted + gated host-\n// side. The host owns the whole lifecycle (visibility, budget, revocation,\n// teardown); the launcher only OBSERVES via the handle.\n//\n// A caller that wants a typed value back uses `invokeTask` instead — the two are\n// siblings, not a replacement.\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\n\n/** Where a launched program runs (§6). `overlay` covers the caller's own region\n * with opaque host chrome; `stage` replaces the focal app (the elevated into-\n * stage surface, §7 — refused above the stage-principal ceiling). */\nexport type LaunchRegion = 'overlay' | 'stage';\n\n/**\n * What to launch (§3) — binding-resolved, NEVER a caller-named app. Exactly one of:\n * - `entryPoint`: a sibling entry point of the caller's OWN repo (the mini-app\n * case, `AGENT_AUTHORING §5`) — **rejected `forbidden` until program-identity\n * `appKey` lands** (R-SAL-2a);\n * - `task`: a task contract (`open-project`, …), resolved through the user-\n * overridable `task.<name>` binding to whichever app the user bound (§3 kind 2).\n */\nexport interface LaunchTarget {\n /** Kind 1 — a sibling entry point of the caller's own repo (mini-app overlay). */\n entryPoint?: string;\n /** Kind 2 — a task contract name (the host resolves the bound provider). */\n task?: string;\n /** Optional accepted contract version for a `task` target (semver, e.g. `^1`). */\n version?: string;\n}\n\nexport interface LaunchOptions {\n /** Where it runs (§6). */\n region: LaunchRegion;\n /**\n * Delegations + plain data handed to the launched app. `$cap:'dir'|'file'`\n * markers (`capDir`/`capFile`, exported from `./tasks`) are resolved against the\n * launcher's OWN grants and minted as attenuated, `ro`-by-default chroots (§5) —\n * you can only delegate what you already hold. Everything else is plain data.\n */\n input?: Record<string, unknown>;\n}\n\n/** The live state of a launch (§2). Terminal states (`dismissed`/`revoked`/\n * `failed`) are reached identically for self-exit, user dismiss, and host revoke —\n * the host debounces so the launcher gets no timing oracle (R-SAL-1 / §6.4). */\nexport type LaunchStatus = 'running' | 'dismissed' | 'revoked' | 'failed';\n\n/** Machine codes a refused `launch` resolves with (§8). */\nexport type LaunchErrorCode =\n | 'forbidden' // undeclared target; sibling (pre-AA-01); self-resolving target; stage cap absent\n | 'unsupported' // no provider bound / entry point absent / unknown contract version\n | 'budget' // over an R-SAL-8 concurrency/rate limit\n | 'revoked' // a delegated mount was revoked during the create-and-bind window\n | 'cancelled' // the user dismissed the host launch affordance (into-stage only)\n | 'invalid-params' // a malformed `capDir`/`capFile` (`..`/absolute/mode-escalation)\n | 'unknown';\n\n/**\n * The control channel back to a launch — the ONLY thing a launcher gets (§2).\n * There is no typed return value (R-SAL-1); `status`/`onDismiss` are debounced so\n * they cannot time-distinguish a self-exit from a user dismiss.\n */\nexport interface LaunchHandle {\n /** Host-assigned id for this launch. */\n readonly launchId: string;\n /** The current lifecycle state (§2). */\n readonly status: LaunchStatus;\n /** Ask the host to tear this launch down. Idempotent (double-dismiss is a no-op). */\n dismiss(): void;\n /**\n * Observe the launch ending (self-exit / user dismiss / host revoke — fired\n * identically, R-SAL-1). Returns an unsubscribe fn. Fires at most once; if the\n * launch has already ended it fires on the next tick.\n */\n onDismiss(cb: () => void): () => void;\n}\n\ninterface LaunchEndedMessage {\n launchId: string;\n status: Exclude<LaunchStatus, 'running'>;\n}\n\n/** Live handles awaiting their terminal `launch-ended` message, keyed by launchId. */\nconst liveHandles = new Map<string, LaunchHandleImpl>();\n\n// The host delivers ONE `launch-ended` message per launch when it tears down —\n// the SAME message shape for self-exit, dismiss, and revoke (the host debounces\n// so the timing is not an oracle, §6.4). We fan it out to the matching handle.\naddListener('launch-ended', (m: LaunchEndedMessage) => {\n const h = liveHandles.get(m.launchId);\n if (h) h._end(m.status);\n});\n\nclass LaunchHandleImpl implements LaunchHandle {\n #status: LaunchStatus = 'running';\n #dismissListeners = new Set<() => void>();\n #ended = false;\n\n constructor(readonly launchId: string) {\n liveHandles.set(launchId, this);\n }\n\n get status(): LaunchStatus {\n return this.#status;\n }\n\n dismiss(): void {\n if (this.#ended) return;\n // Fire-and-forget: the host owns teardown and answers with `launch-ended`,\n // which drives `_end` (so status/onDismiss are host-authoritative, never\n // optimistically local — a dismiss the host refuses would otherwise desync).\n sendMessage('launch-dismiss', { launchId: this.launchId });\n }\n\n onDismiss(cb: () => void): () => void {\n if (this.#ended) {\n // Already ended: fire on the next tick so the contract (\"returns an\n // unsubscribe\") holds and the callback never runs synchronously mid-register.\n queueMicrotask(cb);\n return () => {};\n }\n this.#dismissListeners.add(cb);\n return () => {\n this.#dismissListeners.delete(cb);\n };\n }\n\n /** Host-driven terminal transition — the only writer of `status`. Idempotent. */\n _end(status: Exclude<LaunchStatus, 'running'>): void {\n if (this.#ended) return;\n this.#ended = true;\n this.#status = status;\n liveHandles.delete(this.launchId);\n const listeners = [...this.#dismissListeners];\n this.#dismissListeners.clear();\n for (const l of listeners) {\n try {\n l();\n } catch {\n /* a launcher's own callback must never wedge teardown */\n }\n }\n }\n}\n\n/**\n * Launch a bound program to RUN in a region (§2). Non-blocking: resolves once the\n * frame is created and bound, with a {@link LaunchHandle} — or a typed\n * `{ ok:false, code }` on refusal (§8), NEVER a throw for an ordinary refusal (so\n * a launcher branches on `code` without a try/catch). The launched app runs under\n * its OWN grants; the launcher's authority does not flow to it (R-SAL-4).\n *\n * const h = await launch({ task: 'open-project' }, {\n * region: 'stage',\n * input: { dir: capDir({ mountId: 'space:abc', relPath: 'proj' }, { mode: 'ro' }) },\n * });\n * if ('ok' in h && h.ok === false) { ...handle h.code... }\n * else { h.onDismiss(() => ...); }\n */\nexport const launch = async (\n target: LaunchTarget,\n opts: LaunchOptions,\n): Promise<LaunchHandle | { ok: false; code: LaunchErrorCode }> => {\n // The host wraps a successful handler return as `{ ok:true, data }` (the same\n // Recipe-B framing `invokeTask` uses); a refusal is `{ ok:false, code }`.\n const res = (await protocolRequest('launch', 'create', [{ target, opts }])) as\n | { ok: true; data: { launchId: string } }\n | { ok: false; code?: LaunchErrorCode }\n | undefined;\n if (!res || res.ok !== true || !res.data?.launchId) {\n const code = res && res.ok === false ? (res.code ?? 'unknown') : 'unknown';\n return { ok: false, code };\n }\n return new LaunchHandleImpl(res.data.launchId);\n};\n"],"mappings":";;;;;AAAA;AAWA,SAAS,iBAAiB,aAAa,mBAAmB;AA6E1D,MAAM,cAAc,oBAAI,IAA8B;AAKtD,YAAY,gBAAgB,CAAC,MAA0B;AACrD,QAAM,IAAI,YAAY,IAAI,EAAE,QAAQ;AACpC,MAAI,EAAG,GAAE,KAAK,EAAE,MAAM;AACxB,CAAC;AAED,MAAM,iBAAyC;AAAA,EAK7C,YAAqB,UAAkB;AAAlB;AAJrB,gCAAwB;AACxB,0CAAoB,oBAAI,IAAgB;AACxC,+BAAS;AAGP,gBAAY,IAAI,UAAU,IAAI;AAAA,EAChC;AAAA,EAEA,IAAI,SAAuB;AACzB,WAAO,mBAAK;AAAA,EACd;AAAA,EAEA,UAAgB;AACd,QAAI,mBAAK,QAAQ;AAIjB,gBAAY,kBAAkB,EAAE,UAAU,KAAK,SAAS,CAAC;AAAA,EAC3D;AAAA,EAEA,UAAU,IAA4B;AACpC,QAAI,mBAAK,SAAQ;AAGf,qBAAe,EAAE;AACjB,aAAO,MAAM;AAAA,MAAC;AAAA,IAChB;AACA,uBAAK,mBAAkB,IAAI,EAAE;AAC7B,WAAO,MAAM;AACX,yBAAK,mBAAkB,OAAO,EAAE;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAGA,KAAK,QAAgD;AACnD,QAAI,mBAAK,QAAQ;AACjB,uBAAK,QAAS;AACd,uBAAK,SAAU;AACf,gBAAY,OAAO,KAAK,QAAQ;AAChC,UAAM,YAAY,CAAC,GAAG,mBAAK,kBAAiB;AAC5C,uBAAK,mBAAkB,MAAM;AAC7B,eAAW,KAAK,WAAW;AACzB,UAAI;AACF,UAAE;AAAA,MACJ,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;AAjDE;AACA;AACA;AA+DK,MAAM,SAAS,OACpB,QACA,SACiE;AAGjE,QAAM,MAAO,MAAM,gBAAgB,UAAU,UAAU,CAAC,EAAE,QAAQ,KAAK,CAAC,CAAC;AAIzE,MAAI,CAAC,OAAO,IAAI,OAAO,QAAQ,CAAC,IAAI,MAAM,UAAU;AAClD,UAAM,OAAO,OAAO,IAAI,OAAO,QAAS,IAAI,QAAQ,YAAa;AACjE,WAAO,EAAE,IAAI,OAAO,KAAK;AAAA,EAC3B;AACA,SAAO,IAAI,iBAAiB,IAAI,KAAK,QAAQ;AAC/C;","names":[]}
|
package/dist/llm.js
CHANGED
package/dist/llm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n * advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n * advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n | { type: 'text'; text: string }\n | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n // A tool call the model emitted on a prior `assistant` turn — replay it in the\n // conversation so a follow-up request carries the agentic history. Pairs with the\n // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n // The result of executing a `tool-use`, fed back so the model can continue. Carried\n // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n role: ChatRole;\n content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n name: string;\n description?: string;\n /** JSON-Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n * response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n messages: ChatMessage[];\n /** Honored only when the resolved provider advertises `features.tools`. */\n tools?: ToolDef[];\n /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n responseFormat?: 'text' | 'json';\n maxTokens?: number;\n /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n * model on the resolved provider. Omit to take the provider's default. */\n modelHint?: 'fast' | 'smart';\n /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel\n * frame so the host aborts the upstream provider request and STOPS BILLING the\n * user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3\n * \"abort the in-flight LLM request\", R3-224). Not sent over the wire (an\n * `AbortSignal` isn't serializable); handled SDK-side. */\n signal?: AbortSignal;\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n | { type: 'text-delta'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'usage'; inputTokens: number; outputTokens: number };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n * if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound the host fails the\n * stream into the SP-7 connect-me prompt (the user adds a key) — the generator\n * throws with `code: 'auth-required'`; an un-granted call throws `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n // Peel `signal` out of the request before it becomes wire params — an AbortSignal\n // can't cross the postMessage boundary as data; it drives the SDK-side cancel frame.\n const { signal, ...params } = req;\n return invokeStream<ChatDelta, ChatResult>(\n 'llm:chat',\n params as unknown as Record<string, unknown>,\n signal,\n );\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n * to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n vision: boolean;\n tools: boolean;\n jsonMode: boolean;\n maxContextTokens: number;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n * is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n providerId: string;\n /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n * whose `features` are an untrusted claim. */\n hostVouched: boolean;\n features: ChatFeatures;\n}\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\" (distinct from \"not yet answered\", which keeps the `initial` null).\nconst channel = createPushChannel<ChatProviderInfo | null>({\n pushType: 'llm-provider',\n requestType: 'request-llm-provider',\n initial: null,\n parse: (msg) =>\n 'provider' in msg ? (msg.provider as ChatProviderInfo | null) : undefined,\n});\n\n/** The provider the host resolved for this app (or `null` if none bound). Poll for a\n * one-off read; use {@link onChatProviderChange}/{@link useChatProvider} to react. */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n * immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (\n listener: (provider: ChatProviderInfo | null) => void,\n): (() => void) => channel.onChange(listener);\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n * change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n"],"mappings":"AAeA,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AAiF3B,SAAS,KAAK,KAA+D;AAGlF,QAAM,EAAE,QAAQ,GAAG,OAAO,IAAI;AAC9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA0BA,MAAM,UAAU,kBAA2C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QACN,cAAc,MAAO,IAAI,WAAuC;AACpE,CAAC;AAIM,MAAM,eAAe,MAA+B,QAAQ,IAAI;AAIhE,MAAM,uBAAuB,CAClC,aACiB,QAAQ,SAAS,QAAQ;AAIrC,MAAM,kBAAkB,MAA+B,QAAQ,IAAI;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/llm.ts"],"sourcesContent":["// Provider-agnostic LLM chat — the `llm.chat@1` slot (SERVICE_PROVIDERS_SPEC;\n// LLM_AND_AGENTS_SPEC §8 D5).\n//\n// An app calls ONE chat slot and never worries about which provider the user has a\n// key for: the HOST resolves which vendor answers from the key the user holds\n// (`SecretView.boundOrigin`) plus their `preferredImplementation` choice, normalizes\n// the wire format, injects the key host-side at the §6 net:fetch point (the\n// look-at-nothing proxy), and streams normalized deltas back. The app never names a\n// vendor, never sees the key, and needs NO `net:fetch`/`secrets` grant of its own —\n// only the `llm:chat` capability (elevated, app-scoped: a fork earns it by consent).\n//\n// Inert until the host implements `protocol-llm` (the `chat` stream) + the\n// `llm-provider` describe channel; the contract ships here so apps (the file-explorer\n// summarize fork) can be written against it — exactly how `secrets.ts` shipped ahead\n// of `protocol-secrets`.\nimport { invokeStream } from './catalog';\nimport { createPushChannel } from './pushChannel';\n\n/** Who authored a {@link ChatMessage}. */\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/** A part of a message. `image` is only honored when the resolved provider\n * advertises `features.vision` (§2.5); `tool-use`/`tool-result` only when it\n * advertises `features.tools` — branch on {@link describeChat} first. */\nexport type ContentPart =\n | { type: 'text'; text: string }\n | { type: 'image'; mimeType: string; data: string } // data: base64, no data: URL prefix\n // A tool call the model emitted on a prior `assistant` turn — replay it in the\n // conversation so a follow-up request carries the agentic history. Pairs with the\n // streamed `tool-call` {@link ChatDelta} that first surfaced it.\n | { type: 'tool-use'; id: string; name: string; input: Record<string, unknown> }\n // The result of executing a `tool-use`, fed back so the model can continue. Carried\n // on a `user`/`tool`-role message; `toolCallId` matches the `tool-use` `id`.\n | { type: 'tool-result'; toolCallId: string; content: string; isError?: boolean };\n\n/** One message in a {@link ChatRequest}: a role plus its content parts. */\nexport interface ChatMessage {\n role: ChatRole;\n content: ContentPart[];\n}\n\n/** A tool the model may call — honored only when `features.tools`. */\nexport interface ToolDef {\n name: string;\n description?: string;\n /** JSON-Schema for the tool's arguments. */\n inputSchema: Record<string, unknown>;\n}\n\n/** A host-brokered chat completion request: the messages plus optional tools,\n * response format, and model hint (each honored per the provider's features). */\nexport interface ChatRequest {\n messages: ChatMessage[];\n /** Honored only when the resolved provider advertises `features.tools`. */\n tools?: ToolDef[];\n /** `'json'` honored only when `features.jsonMode`. Defaults to `'text'`. */\n responseFormat?: 'text' | 'json';\n maxTokens?: number;\n /** An ABSTRACT tier hint, never a vendor model id — the host maps it to a concrete\n * model on the resolved provider. Omit to take the provider's default. */\n modelHint?: 'fast' | 'smart';\n /** Abort the completion mid-stream. When it fires, the SDK sends the host a cancel\n * frame so the host aborts the upstream provider request and STOPS BILLING the\n * user's key — not merely stops the app-side iterator (LLM_AND_AGENTS_SPEC §3.3\n * \"abort the in-flight LLM request\", R3-224). Not sent over the wire (an\n * `AbortSignal` isn't serializable); handled SDK-side. */\n signal?: AbortSignal;\n}\n\n/** One streamed chunk. Consumers typically accumulate `text-delta`s. */\nexport type ChatDelta =\n | { type: 'text-delta'; text: string }\n | { type: 'tool-call'; id: string; name: string; input: unknown }\n | { type: 'usage'; inputTokens: number; outputTokens: number };\n\n/** Why generation stopped: natural `end`, `length` cap, a `tool` call, or content `filtered`. */\nexport type ChatStopReason = 'end' | 'length' | 'tool' | 'filtered';\n\n/** The terminal value of the {@link chat} stream. */\nexport interface ChatResult {\n stopReason: ChatStopReason;\n}\n\n/**\n * Stream a chat completion from whichever provider the user has configured.\n *\n * ```ts\n * let summary = '';\n * for await (const d of chat({ messages: [{ role: 'user', content: [{ type: 'text', text }] }] })) {\n * if (d.type === 'text-delta') summary += d.text;\n * }\n * ```\n *\n * Requires the `llm:chat` capability. If no provider is bound the host fails the\n * stream into the SP-7 connect-me prompt (the user adds a key) — the generator\n * throws with `code: 'auth-required'`; an un-granted call throws `forbidden`.\n */\nexport function chat(req: ChatRequest): AsyncGenerator<ChatDelta, ChatResult, void> {\n // Peel `signal` out of the request before it becomes wire params — an AbortSignal\n // can't cross the postMessage boundary as data; it drives the SDK-side cancel frame.\n const { signal, ...params } = req;\n return invokeStream<ChatDelta, ChatResult>(\n 'llm:chat',\n params as unknown as Record<string, unknown>,\n signal,\n );\n}\n\n/** The resolved provider's advertised abilities (SERVICE_PROVIDERS_SPEC §2.5) — read\n * to branch/degrade (offer image upload only when `vision`). */\nexport interface ChatFeatures {\n vision: boolean;\n tools: boolean;\n jsonMode: boolean;\n maxContextTokens: number;\n}\n\n/** Info about the provider the host resolved for this app. `null` when no provider\n * is bound (SP-7: prompt the user to add a key before calling {@link chat}). */\nexport interface ChatProviderInfo {\n /** Opaque provider id, e.g. `llm.chat.anthropic` — never a vendor secret or model id. */\n providerId: string;\n /** True for Host-proxied providers (host-vouched, SP-9); false for app-level ones,\n * whose `features` are an untrusted claim. */\n hostVouched: boolean;\n features: ChatFeatures;\n}\n\n// The `llm-provider` describe channel (Recipe A): the host pushes the resolved\n// provider info on change and replays it on register-frame, gated by `llm:chat`.\n// A message with no `provider` key is ignored; an explicit `null` means \"no provider\n// bound\" (distinct from \"not yet answered\", which keeps the `initial` null).\nconst channel = createPushChannel<ChatProviderInfo | null>({\n pushType: 'llm-provider',\n requestType: 'request-llm-provider',\n initial: null,\n parse: (msg) =>\n 'provider' in msg ? (msg.provider as ChatProviderInfo | null) : undefined,\n});\n\n/** The provider the host resolved for this app (or `null` if none bound). Poll for a\n * one-off read; use {@link onChatProviderChange}/{@link useChatProvider} to react. */\nexport const describeChat = (): ChatProviderInfo | null => channel.get();\n\n/** Subscribe to provider changes (key added/revoked, preference changed). Invoked\n * immediately with the current value, then on every change. Returns unsubscribe. */\nexport const onChatProviderChange = (\n listener: (provider: ChatProviderInfo | null) => void,\n): (() => void) => channel.onChange(listener);\n\n/** React hook returning the resolved chat provider (or `null`), re-rendering on\n * change — gate the summarize affordance on `provider !== null`. */\nexport const useChatProvider = (): ChatProviderInfo | null => channel.use();\n"],"mappings":";AAeA,SAAS,oBAAoB;AAC7B,SAAS,yBAAyB;AAiF3B,SAAS,KAAK,KAA+D;AAGlF,QAAM,EAAE,QAAQ,GAAG,OAAO,IAAI;AAC9B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA0BA,MAAM,UAAU,kBAA2C;AAAA,EACzD,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,OAAO,CAAC,QACN,cAAc,MAAO,IAAI,WAAuC;AACpE,CAAC;AAIM,MAAM,eAAe,MAA+B,QAAQ,IAAI;AAIhE,MAAM,uBAAuB,CAClC,aACiB,QAAQ,SAAS,QAAQ;AAIrC,MAAM,kBAAkB,MAA+B,QAAQ,IAAI;","names":[]}
|
package/dist/loading.js
CHANGED
package/dist/loading.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/loading.tsx"],"sourcesContent":["// In-app loading primitives (LOADING_UX_SPEC §9, Surface E; design brief 19).\n//\n// Opt-in, presentational components an app author drops in for IN-APP waits (a\n// React.lazy chunk, a slow fetch, a pending action) so they match the platform's\n// loading language for free. Per product_values §3 this is convenience — a plain\n// app that ignores it still works.\n//\n// TRUST BOUNDARY (read brief 19): these render INSIDE the app's iframe, under the\n// app's principal — presentational only, NO capability, NO host round-trip. They\n// share the token VOCABULARY (radii, the shimmer sweep, soft placeholder fills)\n// with the host skeletons, but they are deliberately NOT the host's trusted chrome:\n// no reserved landmark, no wordmark, no \"platform is loading\" framing. They use\n// ordinary app a11y (`aria-hidden` for decorative skeletons, `role=\"status\"` for an\n// indeterminate indicator) — never the host's reserved landmark/accessible name\n// (that is the trusted-path control, reserved to the host). An in-app loader that\n// mimicked host chrome would be a spoofing vector.\n//\n// Self-contained: the SDK ships no CSS pipeline, so the keyframes are injected once\n// into the document and everything else is inline styles — zero config for the author.\nimport {\n useEffect,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\n/** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22\n * design bundle). Exported so authors who hand-roll still match the platform. */\nexport const LOADING_TIMINGS = {\n /** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */\n spinThresholdMs: 150,\n /** The reveal cross-fade duration the host uses. */\n fadeMs: 150,\n /** One slow shimmer sweep across a placeholder. */\n shimmerMs: 1900,\n} as const;\n\n/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */\nexport type SkeletonArchetype =\n | \"panel.list\"\n | \"panel.tree\"\n | \"panel.editor\"\n | \"panel.conversation\"\n | \"generic\";\n\nconst STYLE_ID = \"ir-sdk-loading-styles\";\nconst STYLE_TEXT = `\n@keyframes ir-sdk-sweep{0%{transform:translateX(-130%)}60%,100%{transform:translateX(130%)}}\n@keyframes ir-sdk-spin{to{transform:rotate(360deg)}}\n.ir-sdk-shim{position:relative;overflow:hidden}\n.ir-sdk-shim::after{content:\"\";position:absolute;inset:0;background:linear-gradient(100deg,transparent 28%,rgba(127,127,127,.18) 50%,transparent 72%);transform:translateX(-130%);animation:ir-sdk-sweep ${LOADING_TIMINGS.shimmerMs}ms ease-in-out infinite}\n.ir-sdk-spin{animation:ir-sdk-spin .8s linear infinite}\n@media (prefers-reduced-motion:reduce){.ir-sdk-shim::after{display:none}.ir-sdk-spin{animation:none}}\n`;\n\n/** Inject the shimmer/spin keyframes once (idempotent, browser-only). */\nfunction ensureLoadingStyles(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = STYLE_TEXT;\n document.head.appendChild(el);\n}\n\n/** Ensure the loading keyframes exist before the component paints. */\nfunction useLoadingStyles(): void {\n useLayoutEffect(() => {\n ensureLoadingStyles();\n }, []);\n}\n\n// A soft, low-contrast placeholder fill that reads on a light OR dark app surface.\nconst PLACEHOLDER: CSSProperties = {\n background: \"rgba(127,127,127,0.20)\",\n borderRadius: 6,\n};\n\n/** A single placeholder bar — compose these into a custom skeleton shape. */\nexport function SkeletonRow({\n width = \"100%\",\n height = 12,\n style,\n}: {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n return (\n <div\n className=\"ir-sdk-shim\"\n aria-hidden=\"true\"\n style={{ ...PLACEHOLDER, width, height, ...style }}\n />\n );\n}\n\nfunction rows(specs: Array<CSSProperties & { key: number }>): ReactNode {\n return specs.map(({ key, ...s }) => (\n <div key={key} className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, ...s }} />\n ));\n}\n\n/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy\n * region (e.g. `<Suspense fallback={<Skeleton archetype=\"panel.list\" />}>`).\n * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */\nexport function Skeleton({\n archetype = \"generic\",\n style,\n}: {\n archetype?: SkeletonArchetype;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n const wrap: CSSProperties = {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n padding: 12,\n width: \"100%\",\n boxSizing: \"border-box\",\n ...style,\n };\n let body: ReactNode;\n switch (archetype) {\n case \"panel.list\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14 })));\n break;\n case \"panel.tree\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14, marginLeft: (key % 3) * 16 })));\n break;\n case \"panel.editor\":\n body = rows([62, 88, 73, 41, 80].map((w, key) => ({ key, height: 11, width: `${w}%` })));\n break;\n case \"panel.conversation\":\n body = rows(\n [0, 1, 2, 3].map((key) => ({\n key,\n height: 40,\n width: \"70%\",\n borderRadius: 12,\n alignSelf: key % 2 ? \"flex-end\" : \"flex-start\",\n })),\n );\n break;\n case \"generic\":\n default:\n body = (\n <>\n <div className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, height: 16, width: \"45%\", borderRadius: 4 }} />\n {rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))}\n </>\n );\n }\n return (\n <div aria-hidden=\"true\" style={wrap}>\n {body}\n </div>\n );\n}\n\n/** Become true after `ms`, so a fast wait never flashes an indicator (§6.2 floor). */\nfunction useDelayedFlag(ms: number): boolean {\n const [on, setOn] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setOn(true), ms);\n return () => clearTimeout(t);\n }, [ms]);\n return on;\n}\n\n/** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending\n * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it\n * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion\n * stills the rotation (the ring stays as a static indicator). In-app a11y only. */\nexport function Spinner({\n size = 18,\n thresholdMs = LOADING_TIMINGS.spinThresholdMs,\n label = \"Loading\",\n}: {\n size?: number;\n thresholdMs?: number;\n label?: string;\n}) {\n useLoadingStyles();\n const show = useDelayedFlag(thresholdMs);\n if (!show) return null;\n return (\n <span\n className=\"ir-sdk-spin\"\n role=\"status\"\n aria-label={label}\n style={{\n display: \"inline-block\",\n width: size,\n height: size,\n border: \"2px solid rgba(127,127,127,0.3)\",\n borderTopColor: \"currentColor\",\n borderRadius: \"50%\",\n boxSizing: \"border-box\",\n }}\n />\n );\n}\n\n/** Wrap an in-app region whose content is still loading: shows a centered spinner\n * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped\n * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */\nexport function LoadingRegion({\n loading,\n fallback,\n label = \"Loading\",\n children,\n}: {\n loading: boolean;\n fallback?: ReactNode;\n label?: string;\n children?: ReactNode;\n}) {\n useLoadingStyles();\n if (!loading) return <>{children}</>;\n return (\n <div\n aria-busy=\"true\"\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", width: \"100%\", height: \"100%\", minHeight: 48 }}\n >\n {fallback ?? <Spinner label={label} />}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/loading.tsx"],"sourcesContent":["// In-app loading primitives (LOADING_UX_SPEC §9, Surface E; design brief 19).\n//\n// Opt-in, presentational components an app author drops in for IN-APP waits (a\n// React.lazy chunk, a slow fetch, a pending action) so they match the platform's\n// loading language for free. Per product_values §3 this is convenience — a plain\n// app that ignores it still works.\n//\n// TRUST BOUNDARY (read brief 19): these render INSIDE the app's iframe, under the\n// app's principal — presentational only, NO capability, NO host round-trip. They\n// share the token VOCABULARY (radii, the shimmer sweep, soft placeholder fills)\n// with the host skeletons, but they are deliberately NOT the host's trusted chrome:\n// no reserved landmark, no wordmark, no \"platform is loading\" framing. They use\n// ordinary app a11y (`aria-hidden` for decorative skeletons, `role=\"status\"` for an\n// indeterminate indicator) — never the host's reserved landmark/accessible name\n// (that is the trusted-path control, reserved to the host). An in-app loader that\n// mimicked host chrome would be a spoofing vector.\n//\n// Self-contained: the SDK ships no CSS pipeline, so the keyframes are injected once\n// into the document and everything else is inline styles — zero config for the author.\nimport {\n useEffect,\n useLayoutEffect,\n useState,\n type CSSProperties,\n type ReactNode,\n} from \"react\";\n\n/** Loading timing constants, matching the host (LOADING_UX_SPEC §3, the 2026-06-22\n * design bundle). Exported so authors who hand-roll still match the platform. */\nexport const LOADING_TIMINGS = {\n /** Below this, NO spinner appears — a fast wait never flashes one (§6.2 floor). */\n spinThresholdMs: 150,\n /** The reveal cross-fade duration the host uses. */\n fadeMs: 150,\n /** One slow shimmer sweep across a placeholder. */\n shimmerMs: 1900,\n} as const;\n\n/** The in-app skeleton archetypes (the same shapes as the host §4.1 catalog). */\nexport type SkeletonArchetype =\n | \"panel.list\"\n | \"panel.tree\"\n | \"panel.editor\"\n | \"panel.conversation\"\n | \"generic\";\n\nconst STYLE_ID = \"ir-sdk-loading-styles\";\nconst STYLE_TEXT = `\n@keyframes ir-sdk-sweep{0%{transform:translateX(-130%)}60%,100%{transform:translateX(130%)}}\n@keyframes ir-sdk-spin{to{transform:rotate(360deg)}}\n.ir-sdk-shim{position:relative;overflow:hidden}\n.ir-sdk-shim::after{content:\"\";position:absolute;inset:0;background:linear-gradient(100deg,transparent 28%,rgba(127,127,127,.18) 50%,transparent 72%);transform:translateX(-130%);animation:ir-sdk-sweep ${LOADING_TIMINGS.shimmerMs}ms ease-in-out infinite}\n.ir-sdk-spin{animation:ir-sdk-spin .8s linear infinite}\n@media (prefers-reduced-motion:reduce){.ir-sdk-shim::after{display:none}.ir-sdk-spin{animation:none}}\n`;\n\n/** Inject the shimmer/spin keyframes once (idempotent, browser-only). */\nfunction ensureLoadingStyles(): void {\n if (typeof document === \"undefined\") return;\n if (document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = STYLE_TEXT;\n document.head.appendChild(el);\n}\n\n/** Ensure the loading keyframes exist before the component paints. */\nfunction useLoadingStyles(): void {\n useLayoutEffect(() => {\n ensureLoadingStyles();\n }, []);\n}\n\n// A soft, low-contrast placeholder fill that reads on a light OR dark app surface.\nconst PLACEHOLDER: CSSProperties = {\n background: \"rgba(127,127,127,0.20)\",\n borderRadius: 6,\n};\n\n/** A single placeholder bar — compose these into a custom skeleton shape. */\nexport function SkeletonRow({\n width = \"100%\",\n height = 12,\n style,\n}: {\n width?: number | string;\n height?: number | string;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n return (\n <div\n className=\"ir-sdk-shim\"\n aria-hidden=\"true\"\n style={{ ...PLACEHOLDER, width, height, ...style }}\n />\n );\n}\n\nfunction rows(specs: Array<CSSProperties & { key: number }>): ReactNode {\n return specs.map(({ key, ...s }) => (\n <div key={key} className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, ...s }} />\n ));\n}\n\n/** A shaped, in-app skeleton matching the host archetypes — for an app's own lazy\n * region (e.g. `<Suspense fallback={<Skeleton archetype=\"panel.list\" />}>`).\n * Decorative (`aria-hidden`); pair it with `aria-busy` on the region it stands in. */\nexport function Skeleton({\n archetype = \"generic\",\n style,\n}: {\n archetype?: SkeletonArchetype;\n style?: CSSProperties;\n}) {\n useLoadingStyles();\n const wrap: CSSProperties = {\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n padding: 12,\n width: \"100%\",\n boxSizing: \"border-box\",\n ...style,\n };\n let body: ReactNode;\n switch (archetype) {\n case \"panel.list\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14 })));\n break;\n case \"panel.tree\":\n body = rows([0, 1, 2, 3, 4].map((key) => ({ key, height: 14, marginLeft: (key % 3) * 16 })));\n break;\n case \"panel.editor\":\n body = rows([62, 88, 73, 41, 80].map((w, key) => ({ key, height: 11, width: `${w}%` })));\n break;\n case \"panel.conversation\":\n body = rows(\n [0, 1, 2, 3].map((key) => ({\n key,\n height: 40,\n width: \"70%\",\n borderRadius: 12,\n alignSelf: key % 2 ? \"flex-end\" : \"flex-start\",\n })),\n );\n break;\n case \"generic\":\n default:\n body = (\n <>\n <div className=\"ir-sdk-shim\" aria-hidden=\"true\" style={{ ...PLACEHOLDER, height: 16, width: \"45%\", borderRadius: 4 }} />\n {rows([0, 1].map((key) => ({ key, height: 56, borderRadius: 8 })))}\n </>\n );\n }\n return (\n <div aria-hidden=\"true\" style={wrap}>\n {body}\n </div>\n );\n}\n\n/** Become true after `ms`, so a fast wait never flashes an indicator (§6.2 floor). */\nfunction useDelayedFlag(ms: number): boolean {\n const [on, setOn] = useState(false);\n useEffect(() => {\n const t = setTimeout(() => setOn(true), ms);\n return () => clearTimeout(t);\n }, [ms]);\n return on;\n}\n\n/** An indeterminate spinner for waits where a shaped skeleton doesn't fit (a pending\n * button, a small inline fetch). Wired to the host's ~150 ms-before-spin rule: it\n * renders nothing until the threshold, so a fast wait shows no flash. Reduced motion\n * stills the rotation (the ring stays as a static indicator). In-app a11y only. */\nexport function Spinner({\n size = 18,\n thresholdMs = LOADING_TIMINGS.spinThresholdMs,\n label = \"Loading\",\n}: {\n size?: number;\n thresholdMs?: number;\n label?: string;\n}) {\n useLoadingStyles();\n const show = useDelayedFlag(thresholdMs);\n if (!show) return null;\n return (\n <span\n className=\"ir-sdk-spin\"\n role=\"status\"\n aria-label={label}\n style={{\n display: \"inline-block\",\n width: size,\n height: size,\n border: \"2px solid rgba(127,127,127,0.3)\",\n borderTopColor: \"currentColor\",\n borderRadius: \"50%\",\n boxSizing: \"border-box\",\n }}\n />\n );\n}\n\n/** Wrap an in-app region whose content is still loading: shows a centered spinner\n * (past the 150 ms floor) with `aria-busy`, then reveals `children`. For a shaped\n * wait, pass a `<Skeleton>` as `fallback` instead. App a11y only — not host chrome. */\nexport function LoadingRegion({\n loading,\n fallback,\n label = \"Loading\",\n children,\n}: {\n loading: boolean;\n fallback?: ReactNode;\n label?: string;\n children?: ReactNode;\n}) {\n useLoadingStyles();\n if (!loading) return <>{children}</>;\n return (\n <div\n aria-busy=\"true\"\n style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", width: \"100%\", height: \"100%\", minHeight: 48 }}\n >\n {fallback ?? <Spinner label={label} />}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/markers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./chunk-VHAA22YE.js";
|
|
1
2
|
import { sendMessage as defaultSend } from "./sandboxUtils";
|
|
2
3
|
import { isIrMarkerName } from "./irMarkers";
|
|
3
4
|
const realNow = () => typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
|
package/dist/markers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/markers.ts"],"sourcesContent":["// Runtime emission of the boot `ir.*` markers (LOAD_PROFILING_SPEC §3/§3.2, R3-46).\n//\n// The host already MERGED the receive end: it validates a forwarded\n// `{ type:'ir-marker', name, at, attrs? }` against the LP-5 allowlist\n// (`irMarkers.ts`) and brackets request→interactive, treating an accepted\n// `ir.interactive` as the root-render-commit signal (site-main\n// `routePerfMessage`/`perfTimeline`). This module is the SANDBOX-side emitter for\n// the boot marks the SDK owns — `ir.fmp` (first meaningful paint) and\n// `ir.interactive` (root render commit) — forwarded over the same transport as\n// `reportReady()` (`ready.ts`). The per-marker timestamp is the sandbox-relative\n// `performance.now()` at emission (§3.2).\n//\n// Mirrors `ready.ts`'s dependency seam so the emission is unit-testable without a\n// host transport, and is idempotent per name so React StrictMode's double-invoke\n// (or any re-commit) can't mint a boot mark twice.\nimport { sendMessage as defaultSend } from './sandboxUtils';\nimport { isIrMarkerName, type IrMarkerName } from './irMarkers';\n\ninterface MarkerDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\nconst defaultDeps: MarkerDeps = { send: defaultSend, now: realNow };\n\nlet deps: MarkerDeps = defaultDeps;\nconst emitted = new Set<string>();\n\n/**\n * Forward one `ir.*` marker to the host (`ir-marker`). The host re-validates against\n * the allowlist, so a bad name/attr is dropped there; we only emit names the SDK\n * itself owns. A transport that isn't ready yet is swallowed — a missing boot mark\n * must never break the app's boot.\n */\nexport function emitMarker(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (!isIrMarkerName(name)) return;\n try {\n deps.send('ir-marker', { name, at: deps.now(), ...(attrs !== undefined ? { attrs } : {}) });\n } catch {\n /* transport not ready — boot continues; the host simply lacks this mark */\n }\n}\n\n/** Emit a boot one-shot at most once per name (idempotent across StrictMode/re-commit). */\nexport function emitMarkerOnce(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (emitted.has(name)) return;\n emitted.add(name);\n emitMarker(name, attrs);\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setMarkerDeps(d: Partial<MarkerDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state (the once-guard + deps) between cases. */\nexport function __resetMarkers(): void {\n deps = defaultDeps;\n emitted.clear();\n}\n"],"mappings":"AAeA,SAAS,eAAe,mBAAmB;AAC3C,SAAS,sBAAyC;AAOlD,MAAM,UAAU,MACd,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,aAC7D,YAAY,IAAI,IAChB,KAAK,IAAI;AAEf,MAAM,cAA0B,EAAE,MAAM,aAAa,KAAK,QAAQ;AAElE,IAAI,OAAmB;AACvB,MAAM,UAAU,oBAAI,IAAY;AAQzB,SAAS,WAAW,MAAoB,OAAuC;AACpF,MAAI,CAAC,eAAe,IAAI,EAAG;AAC3B,MAAI;AACF,SAAK,KAAK,aAAa,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAAA,EAC5F,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,eAAe,MAAoB,OAAuC;AACxF,MAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,UAAQ,IAAI,IAAI;AAChB,aAAW,MAAM,KAAK;AACxB;AAGO,SAAS,gBAAgB,GAA8B;AAC5D,SAAO,EAAE,GAAG,aAAa,GAAG,EAAE;AAChC;AAGO,SAAS,iBAAuB;AACrC,SAAO;AACP,UAAQ,MAAM;AAChB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/markers.ts"],"sourcesContent":["// Runtime emission of the boot `ir.*` markers (LOAD_PROFILING_SPEC §3/§3.2, R3-46).\n//\n// The host already MERGED the receive end: it validates a forwarded\n// `{ type:'ir-marker', name, at, attrs? }` against the LP-5 allowlist\n// (`irMarkers.ts`) and brackets request→interactive, treating an accepted\n// `ir.interactive` as the root-render-commit signal (site-main\n// `routePerfMessage`/`perfTimeline`). This module is the SANDBOX-side emitter for\n// the boot marks the SDK owns — `ir.fmp` (first meaningful paint) and\n// `ir.interactive` (root render commit) — forwarded over the same transport as\n// `reportReady()` (`ready.ts`). The per-marker timestamp is the sandbox-relative\n// `performance.now()` at emission (§3.2).\n//\n// Mirrors `ready.ts`'s dependency seam so the emission is unit-testable without a\n// host transport, and is idempotent per name so React StrictMode's double-invoke\n// (or any re-commit) can't mint a boot mark twice.\nimport { sendMessage as defaultSend } from './sandboxUtils';\nimport { isIrMarkerName, type IrMarkerName } from './irMarkers';\n\ninterface MarkerDeps {\n send: (type: string, data?: Record<string, unknown>) => void;\n now: () => number;\n}\n\nconst realNow = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function'\n ? performance.now()\n : Date.now();\n\nconst defaultDeps: MarkerDeps = { send: defaultSend, now: realNow };\n\nlet deps: MarkerDeps = defaultDeps;\nconst emitted = new Set<string>();\n\n/**\n * Forward one `ir.*` marker to the host (`ir-marker`). The host re-validates against\n * the allowlist, so a bad name/attr is dropped there; we only emit names the SDK\n * itself owns. A transport that isn't ready yet is swallowed — a missing boot mark\n * must never break the app's boot.\n */\nexport function emitMarker(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (!isIrMarkerName(name)) return;\n try {\n deps.send('ir-marker', { name, at: deps.now(), ...(attrs !== undefined ? { attrs } : {}) });\n } catch {\n /* transport not ready — boot continues; the host simply lacks this mark */\n }\n}\n\n/** Emit a boot one-shot at most once per name (idempotent across StrictMode/re-commit). */\nexport function emitMarkerOnce(name: IrMarkerName, attrs?: Record<string, unknown>): void {\n if (emitted.has(name)) return;\n emitted.add(name);\n emitMarker(name, attrs);\n}\n\n/** Test seam: override the transport/clock. */\nexport function __setMarkerDeps(d: Partial<MarkerDeps>): void {\n deps = { ...defaultDeps, ...d };\n}\n\n/** Test seam: reset module state (the once-guard + deps) between cases. */\nexport function __resetMarkers(): void {\n deps = defaultDeps;\n emitted.clear();\n}\n"],"mappings":";AAeA,SAAS,eAAe,mBAAmB;AAC3C,SAAS,sBAAyC;AAOlD,MAAM,UAAU,MACd,OAAO,gBAAgB,eAAe,OAAO,YAAY,QAAQ,aAC7D,YAAY,IAAI,IAChB,KAAK,IAAI;AAEf,MAAM,cAA0B,EAAE,MAAM,aAAa,KAAK,QAAQ;AAElE,IAAI,OAAmB;AACvB,MAAM,UAAU,oBAAI,IAAY;AAQzB,SAAS,WAAW,MAAoB,OAAuC;AACpF,MAAI,CAAC,eAAe,IAAI,EAAG;AAC3B,MAAI;AACF,SAAK,KAAK,aAAa,EAAE,MAAM,IAAI,KAAK,IAAI,GAAG,GAAI,UAAU,SAAY,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAAA,EAC5F,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,eAAe,MAAoB,OAAuC;AACxF,MAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,UAAQ,IAAI,IAAI;AAChB,aAAW,MAAM,KAAK;AACxB;AAGO,SAAS,gBAAgB,GAA8B;AAC5D,SAAO,EAAE,GAAG,aAAa,GAAG,EAAE;AAChC;AAGO,SAAS,iBAAuB;AACrC,SAAO;AACP,UAAQ,MAAM;AAChB;","names":[]}
|
package/dist/moduleCache.js
CHANGED
package/dist/moduleCache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/moduleCache.tsx"],"sourcesContent":["// based on: https://www.bbss.dev/posts/react-learn-suspense/#fetchcache-provider\n\nimport { createContext, ReactNode, useCallback, useState } from \"react\"\nimport { EvaluationContext } from \"./sandboxTypes\";\nimport { addListener } from \"./sandboxUtils\";\n\nexport class ModuleCache {\n nameResolutionPromises: Record<string, Promise<string>> = {}\n evaluationContextPromises: Record<string, Promise<EvaluationContext>> = {}\n\n constructor() {\n // reset cache on compile\n addListener('compile', () => {\n // NOTE: THIS CAUSES AN UNNECESSARY RELOAD\n // the <Include> component's module evaluation context promise is replaced\n // with a new promise for the same value when a compilation occurs which\n // doesn't affect the current module. Commenting out the following lines\n // eliminates the unnecessary component state loss.\n // this.nameResolutionPromises = {};\n // this.evaluationContextPromises = {};\n })\n }\n\n private getCacheKey(mod:EvaluationContext, moduleName:string):string {\n return `${mod.evaluation.module.filepath}|${moduleName}`;\n }\n\n resolveModuleName(moduleName:string, baseModule?:EvaluationContext):Promise<string> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.nameResolutionPromises)) {\n this.nameResolutionPromises[cacheKey] = mod.resolve(moduleName);\n }\n return this.nameResolutionPromises[cacheKey];\n }\n\n getEvaluationContext(moduleName:string, baseModule?:EvaluationContext):Promise<EvaluationContext> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.evaluationContextPromises)) {\n this.evaluationContextPromises[cacheKey] = mod.getModuleEvaluationContext(moduleName);\n }\n return this.evaluationContextPromises[cacheKey];\n\n }\n}\n\nexport const ModuleCacheContext = createContext<null|ModuleCache>(null)\n\nexport const ModuleCacheContextProvider = ({ children, moduleCache }:{ children:ReactNode, moduleCache: ModuleCache }) => {\n\n return (\n <ModuleCacheContext.Provider value={moduleCache}>\n {children}\n </ModuleCacheContext.Provider>\n )\n}\n"],"mappings":"AAwDQ;AAtDR,SAAS,qBAAuD;AAEhE,SAAS,mBAAmB;AAErB,MAAM,YAAY;AAAA,EAIvB,cAAc;AAHd,kCAA0D,CAAC;AAC3D,qCAAwE,CAAC;AAIvE,gBAAY,WAAW,MAAM;AAAA,IAQ7B,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,KAAuB,YAA0B;AACnE,WAAO,GAAG,IAAI,WAAW,OAAO,QAAQ,IAAI,UAAU;AAAA,EACxD;AAAA,EAEA,kBAAkB,YAAmB,YAA+C;AAGlF,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,yBAAyB;AAC9C,WAAK,uBAAuB,QAAQ,IAAI,IAAI,QAAQ,UAAU;AAAA,IAChE;AACA,WAAO,KAAK,uBAAuB,QAAQ;AAAA,EAC7C;AAAA,EAEA,qBAAqB,YAAmB,YAA0D;AAGhG,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,4BAA4B;AACjD,WAAK,0BAA0B,QAAQ,IAAI,IAAI,2BAA2B,UAAU;AAAA,IACtF;AACA,WAAO,KAAK,0BAA0B,QAAQ;AAAA,EAEhD;AACF;AAEO,MAAM,qBAAqB,cAAgC,IAAI;AAE/D,MAAM,6BAA6B,CAAC,EAAE,UAAU,YAAY,MAAuD;AAEtH,SACI,oBAAC,mBAAmB,UAAnB,EAA4B,OAAO,aAC/B,UACL;AAER;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/moduleCache.tsx"],"sourcesContent":["// based on: https://www.bbss.dev/posts/react-learn-suspense/#fetchcache-provider\n\nimport { createContext, ReactNode, useCallback, useState } from \"react\"\nimport { EvaluationContext } from \"./sandboxTypes\";\nimport { addListener } from \"./sandboxUtils\";\n\nexport class ModuleCache {\n nameResolutionPromises: Record<string, Promise<string>> = {}\n evaluationContextPromises: Record<string, Promise<EvaluationContext>> = {}\n\n constructor() {\n // reset cache on compile\n addListener('compile', () => {\n // NOTE: THIS CAUSES AN UNNECESSARY RELOAD\n // the <Include> component's module evaluation context promise is replaced\n // with a new promise for the same value when a compilation occurs which\n // doesn't affect the current module. Commenting out the following lines\n // eliminates the unnecessary component state loss.\n // this.nameResolutionPromises = {};\n // this.evaluationContextPromises = {};\n })\n }\n\n private getCacheKey(mod:EvaluationContext, moduleName:string):string {\n return `${mod.evaluation.module.filepath}|${moduleName}`;\n }\n\n resolveModuleName(moduleName:string, baseModule?:EvaluationContext):Promise<string> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.nameResolutionPromises)) {\n this.nameResolutionPromises[cacheKey] = mod.resolve(moduleName);\n }\n return this.nameResolutionPromises[cacheKey];\n }\n\n getEvaluationContext(moduleName:string, baseModule?:EvaluationContext):Promise<EvaluationContext> {\n // note: uses current module as base module if none specified by caller\n // @ts-ignore\n const mod = baseModule ?? (module as EvaluationContext);\n const cacheKey = this.getCacheKey(mod, moduleName);\n if (!(cacheKey in this.evaluationContextPromises)) {\n this.evaluationContextPromises[cacheKey] = mod.getModuleEvaluationContext(moduleName);\n }\n return this.evaluationContextPromises[cacheKey];\n\n }\n}\n\nexport const ModuleCacheContext = createContext<null|ModuleCache>(null)\n\nexport const ModuleCacheContextProvider = ({ children, moduleCache }:{ children:ReactNode, moduleCache: ModuleCache }) => {\n\n return (\n <ModuleCacheContext.Provider value={moduleCache}>\n {children}\n </ModuleCacheContext.Provider>\n )\n}\n"],"mappings":";AAwDQ;AAtDR,SAAS,qBAAuD;AAEhE,SAAS,mBAAmB;AAErB,MAAM,YAAY;AAAA,EAIvB,cAAc;AAHd,kCAA0D,CAAC;AAC3D,qCAAwE,CAAC;AAIvE,gBAAY,WAAW,MAAM;AAAA,IAQ7B,CAAC;AAAA,EACH;AAAA,EAEQ,YAAY,KAAuB,YAA0B;AACnE,WAAO,GAAG,IAAI,WAAW,OAAO,QAAQ,IAAI,UAAU;AAAA,EACxD;AAAA,EAEA,kBAAkB,YAAmB,YAA+C;AAGlF,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,yBAAyB;AAC9C,WAAK,uBAAuB,QAAQ,IAAI,IAAI,QAAQ,UAAU;AAAA,IAChE;AACA,WAAO,KAAK,uBAAuB,QAAQ;AAAA,EAC7C;AAAA,EAEA,qBAAqB,YAAmB,YAA0D;AAGhG,UAAM,MAAM,cAAe;AAC3B,UAAM,WAAW,KAAK,YAAY,KAAK,UAAU;AACjD,QAAI,EAAE,YAAY,KAAK,4BAA4B;AACjD,WAAK,0BAA0B,QAAQ,IAAI,IAAI,2BAA2B,UAAU;AAAA,IACtF;AACA,WAAO,KAAK,0BAA0B,QAAQ;AAAA,EAEhD;AACF;AAEO,MAAM,qBAAqB,cAAgC,IAAI;AAE/D,MAAM,6BAA6B,CAAC,EAAE,UAAU,YAAY,MAAuD;AAEtH,SACI,oBAAC,mBAAmB,UAAnB,EAA4B,OAAO,aAC/B,UACL;AAER;","names":[]}
|
package/dist/mountMatch.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "./chunk-VHAA22YE.js";
|
|
1
2
|
const mountMatches = (mount, query) => (query.type === void 0 || mount.type === query.type) && (query.id === void 0 || mount.id === query.id) && (query.path === void 0 || mount.path === query.path) && (query.name === void 0 || mount.name === query.name);
|
|
2
3
|
export {
|
|
3
4
|
mountMatches
|
package/dist/mountMatch.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mountMatch.ts"],"sourcesContent":["/**\n * Pure predicate behind {@link findMount} / {@link waitForMount}: does `mount`\n * satisfy every field present on `query`? An absent query field matches anything;\n * a present one must equal the mount's value. Kept framework-free and free of the\n * injected sandbox runtime so it is unit-testable in isolation (ways_of_working\n * §5 — separate pure logic from the effectful service).\n *\n * `name` (the human-readable mount label, R3-69) is matchable alongside the\n * `type`/`id`/`path` coordinates so an app/agent can locate a mount by the name a\n * user would recognise — e.g. `findMount({ name: 'Design notes' })` — rather than\n * an opaque `/mnt/{hash}` address or a non-unique space name guessed by hand.\n */\nexport interface MountMatchFields {\n type?: string;\n id?: string;\n path?: string;\n name?: string;\n}\n\nexport const mountMatches = (\n mount: MountMatchFields,\n query: MountMatchFields,\n): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path) &&\n (query.name === undefined || mount.name === query.name);\n"],"mappings":"AAmBO,MAAM,eAAe,CAC1B,OACA,WAEC,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,OAAO,UAAa,MAAM,OAAO,MAAM,QAC7C,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/mountMatch.ts"],"sourcesContent":["/**\n * Pure predicate behind {@link findMount} / {@link waitForMount}: does `mount`\n * satisfy every field present on `query`? An absent query field matches anything;\n * a present one must equal the mount's value. Kept framework-free and free of the\n * injected sandbox runtime so it is unit-testable in isolation (ways_of_working\n * §5 — separate pure logic from the effectful service).\n *\n * `name` (the human-readable mount label, R3-69) is matchable alongside the\n * `type`/`id`/`path` coordinates so an app/agent can locate a mount by the name a\n * user would recognise — e.g. `findMount({ name: 'Design notes' })` — rather than\n * an opaque `/mnt/{hash}` address or a non-unique space name guessed by hand.\n */\nexport interface MountMatchFields {\n type?: string;\n id?: string;\n path?: string;\n name?: string;\n}\n\nexport const mountMatches = (\n mount: MountMatchFields,\n query: MountMatchFields,\n): boolean =>\n (query.type === undefined || mount.type === query.type) &&\n (query.id === undefined || mount.id === query.id) &&\n (query.path === undefined || mount.path === query.path) &&\n (query.name === undefined || mount.name === query.name);\n"],"mappings":";AAmBO,MAAM,eAAe,CAC1B,OACA,WAEC,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,OAAO,UAAa,MAAM,OAAO,MAAM,QAC7C,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM,UACjD,MAAM,SAAS,UAAa,MAAM,SAAS,MAAM;","names":[]}
|
package/dist/mounts.js
CHANGED