@immediately-run/sdk 0.60.0 → 0.64.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/agentChatClient.d.cts +1 -0
- package/dist/agentChatClient.d.ts +1 -0
- package/dist/agentLoop.cjs +7 -1
- package/dist/agentLoop.cjs.map +1 -1
- package/dist/agentLoop.d.cts +21 -0
- package/dist/agentLoop.d.ts +21 -0
- package/dist/agentLoop.js +7 -1
- package/dist/agentLoop.js.map +1 -1
- package/dist/agentPause.cjs +68 -0
- package/dist/agentPause.cjs.map +1 -0
- package/dist/agentPause.d.cts +37 -0
- package/dist/agentPause.d.ts +37 -0
- package/dist/agentPause.js +45 -0
- package/dist/agentPause.js.map +1 -0
- package/dist/anchorClick.cjs +40 -0
- package/dist/anchorClick.cjs.map +1 -0
- package/dist/anchorClick.d.cts +23 -0
- package/dist/anchorClick.d.ts +23 -0
- package/dist/anchorClick.js +16 -0
- package/dist/anchorClick.js.map +1 -0
- package/dist/components/Link.cjs +4 -11
- package/dist/components/Link.cjs.map +1 -1
- package/dist/components/Link.d.cts +1 -1
- package/dist/components/Link.d.ts +1 -1
- package/dist/components/Link.js +3 -10
- package/dist/components/Link.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/llm.cjs +17 -1
- package/dist/llm.cjs.map +1 -1
- package/dist/llm.d.cts +34 -15
- package/dist/llm.d.ts +34 -15
- package/dist/llm.js +17 -1
- package/dist/llm.js.map +1 -1
- package/dist/openRepository.cjs +44 -0
- package/dist/openRepository.cjs.map +1 -0
- package/dist/openRepository.d.cts +36 -0
- package/dist/openRepository.d.ts +36 -0
- package/dist/openRepository.js +21 -0
- package/dist/openRepository.js.map +1 -0
- package/dist/platformLink.cjs +18 -3
- package/dist/platformLink.cjs.map +1 -1
- package/dist/platformLink.d.cts +33 -10
- package/dist/platformLink.d.ts +33 -10
- package/dist/platformLink.js +18 -3
- package/dist/platformLink.js.map +1 -1
- package/dist/protocolSchemes.cjs +1 -0
- package/dist/protocolSchemes.cjs.map +1 -1
- package/dist/protocolSchemes.d.cts +1 -0
- package/dist/protocolSchemes.d.ts +1 -0
- package/dist/protocolSchemes.js +2 -0
- package/dist/protocolSchemes.js.map +1 -1
- package/dist/recents.cjs +9 -1
- package/dist/recents.cjs.map +1 -1
- package/dist/recents.d.cts +6 -0
- package/dist/recents.d.ts +6 -0
- package/dist/recents.js +9 -1
- package/dist/recents.js.map +1 -1
- package/dist/region.cjs +22 -2
- package/dist/region.cjs.map +1 -1
- package/dist/region.d.cts +20 -1
- package/dist/region.d.ts +20 -1
- package/dist/region.js +18 -1
- package/dist/region.js.map +1 -1
- package/dist/urlUtils.cjs.map +1 -1
- package/dist/urlUtils.d.cts +6 -2
- package/dist/urlUtils.d.ts +6 -2
- package/dist/urlUtils.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 +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/openRepository.ts"],"sourcesContent":["// Host-mediated \"open this repository in a new tab\" (R3-476).\n//\n// An app frame cannot open a first-party tab itself. A `window.open` from inside the\n// sandboxed frame inherits the sandbox — the opened tab runs at the same opaque origin and\n// cannot load the host — and `target=\"_top\"` would replace the app's own frame rather than\n// open a tab. So the app asks the host, and the host performs the open from its own context.\n//\n// The app names COORDINATES and nothing else. It cannot pass a URL, a route prefix or a\n// path: the host builds the destination from its own route grammar, so this call can only\n// ever reach one of the platform's own repository routes. That is the point of the shape —\n// an app that could spell the destination could open anything.\n//\n// Two more conditions hold on the host side, and neither is something this call can assert\n// for itself: the open needs the HOST document's live transient user activation (a real\n// click, which the host samples rather than believes), and one gesture opens exactly one\n// tab. Both surface here as ordinary coded refusals.\nimport { protocolRequest } from './sandboxUtils';\nimport { PROTOCOL_OPENREPO } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** Where a repository lives. The same three fields a `RecentProject` carries — this is a\n * location the user may already have navigated to under their own authority. */\nexport interface RepositoryCoordinates {\n provider: string;\n namespace: string;\n repository: string;\n}\n\n/** Why the host refused to open a tab.\n *\n * - `invalid` — the coordinates are not three clean path segments.\n * - `no-activation` — no live user gesture on the host document. Call this from a click\n * handler; a timer or a boot path will always get this. A SECOND call inside one gesture\n * also lands here: `window.open` consumes the host's transient activation, so one click\n * buys exactly one tab and the next needs a real second click.\n * - `forbidden` — the app does not hold the baseline `route:read` capability.\n * - `unsupported` — this host has no repository-open surface wired.\n * - `unknown` — the host refused without naming a code. */\nexport type OpenRepositoryErrorCode = 'invalid' | 'no-activation' | 'forbidden' | 'unsupported' | 'unknown';\n\nexport interface OpenRepositoryError extends Error {\n code: OpenRepositoryErrorCode;\n}\n\n/** The host's reply: the envelope resolves INSIDE the promise, so a refusal is a resolved\n * `{ ok: false }` rather than a rejection at the transport layer. */\ntype OpenRepositoryReply = { ok: true; url?: string } | { ok: false; code?: string; message?: string };\n\n/**\n * Ask the host to open a repository in a new browser tab.\n *\n * Resolves once the host has performed the open; it does not wait for — and cannot observe —\n * the opened tab loading. Rejects with a typed {@link OpenRepositoryError} carrying `code`\n * when the host refuses.\n *\n * Call it directly from a user gesture. The host samples its own transient activation when\n * the request arrives, so anything that defers the call past the gesture (an `await` before\n * it, a `setTimeout`, a retry) will be refused `no-activation`. None of the refusals are\n * worth retrying: each names a condition a retry cannot change.\n */\nexport async function openRepository(coordinates: RepositoryCoordinates): Promise<void> {\n const { provider, namespace, repository } = coordinates;\n const res = (await protocolRequest(SCHEMES[PROTOCOL_OPENREPO], 'open', [\n { provider, namespace, repository },\n ])) as OpenRepositoryReply;\n // The refusal resolves inside the reply, so `res.ok !== true` is the only failure test\n // there is — a bare-promise shape here would swallow every coded refusal as a success.\n if (!res || res.ok !== true) {\n const err = new Error(\n (res && 'message' in res ? res.message : undefined) ?? 'repository open refused',\n ) as OpenRepositoryError;\n err.code = ((res && 'code' in res ? res.code : undefined) as OpenRepositoryErrorCode) ?? 'unknown';\n throw err;\n }\n}\n"],"mappings":";AAgBA,SAAS,uBAAuB;AAChC,SAAS,yBAAyB;AAClC,SAAS,eAAe;AA0CxB,eAAsB,eAAe,aAAmD;AACtF,QAAM,EAAE,UAAU,WAAW,WAAW,IAAI;AAC5C,QAAM,MAAO,MAAM,gBAAgB,QAAQ,iBAAiB,GAAG,QAAQ;AAAA,IACrE,EAAE,UAAU,WAAW,WAAW;AAAA,EACpC,CAAC;AAGD,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI;AAAA,OACb,OAAO,aAAa,MAAM,IAAI,UAAU,WAAc;AAAA,IACzD;AACA,QAAI,QAAS,OAAO,UAAU,MAAM,IAAI,OAAO,WAA0C;AACzF,UAAM;AAAA,EACR;AACF;","names":[]}
|
package/dist/platformLink.cjs
CHANGED
|
@@ -24,15 +24,30 @@ __export(platformLink_exports, {
|
|
|
24
24
|
module.exports = __toCommonJS(platformLink_exports);
|
|
25
25
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
26
26
|
var import_react = require("react");
|
|
27
|
+
var import_anchorClick = require("./anchorClick");
|
|
28
|
+
var import_routing = require("./routing");
|
|
27
29
|
var import_TinkerableContext = require("./TinkerableContext");
|
|
28
30
|
var import_urlUtils = require("./urlUtils");
|
|
29
31
|
const usePlatformHref = () => {
|
|
30
32
|
const { outerHref } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
31
33
|
return (path) => (0, import_urlUtils.platformHref)(outerHref, path);
|
|
32
34
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const SAME_CONTEXT_TARGETS = /* @__PURE__ */ new Set(["_top", "_self", "_parent"]);
|
|
36
|
+
function PlatformLink({ path, children, onClick, target = "_top", ...rest }) {
|
|
37
|
+
const { outerHref } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
|
|
38
|
+
const href = (0, import_urlUtils.platformHref)(outerHref, path);
|
|
39
|
+
const clickHandler = (0, import_anchorClick.useComposedAnchorClick)(
|
|
40
|
+
onClick,
|
|
41
|
+
(event) => {
|
|
42
|
+
if ((0, import_anchorClick.isBrowserGestureClick)(event)) return;
|
|
43
|
+
if (!SAME_CONTEXT_TARGETS.has(target)) return;
|
|
44
|
+
if (!outerHref) return;
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
(0, import_routing.navigate)(href);
|
|
47
|
+
},
|
|
48
|
+
[href, outerHref, target]
|
|
49
|
+
);
|
|
50
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { ...rest, href, target, onClick: clickHandler, children });
|
|
36
51
|
}
|
|
37
52
|
// Annotate the CommonJS export names for ESM import in node:
|
|
38
53
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/platformLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport { use } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport { platformHref } from './urlUtils';\n\n/**\n * Build a PLATFORM-space href (`/present/…`, `/edit/github/…`, `/home`) in the host's URL\n * space, reading `outerHref` from {@link TinkerableContext} the way `useTinkerableLink` does.\n * The returned closure is fresh each render (its output is pure, so identity churn is\n * harmless); an empty context (no host, `vite dev`) yields the path unchanged.\n *\n *
|
|
1
|
+
{"version":3,"sources":["../src/platformLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport { use } from 'react';\n\nimport { isBrowserGestureClick, useComposedAnchorClick } from './anchorClick';\nimport { navigate } from './routing';\nimport { TinkerableContext } from './TinkerableContext';\nimport { platformHref } from './urlUtils';\n\n/**\n * Build a PLATFORM-space href (`/present/…`, `/edit/github/…`, `/home`) in the host's URL\n * space, reading `outerHref` from {@link TinkerableContext} the way `useTinkerableLink` does.\n * The returned closure is fresh each render (its output is pure, so identity churn is\n * harmless); an empty context (no host, `vite dev`) yields the path unchanged.\n *\n * Prefer {@link PlatformLink} over calling this directly. An href alone does not reach a\n * platform route from inside the app frame (see that component), so a consumer that renders\n * its own anchor from this string must ask the host itself — otherwise it ships a link that\n * copies and opens-in-new-tab correctly and does nothing at all on a plain click. It is kept\n * exported because the wire and the module surface are additive-only\n * (`SDK_PACKAGING_SPEC` §9): an app pinned to an older SDK may already import it.\n */\nexport const usePlatformHref = (): ((path: string) => string) => {\n const { outerHref } = use(TinkerableContext);\n return (path: string) => platformHref(outerHref, path);\n};\n\n/**\n * Targets that reuse an existing browsing context. All three are unreachable from inside the\n * sandboxed app frame by the anchor alone — `_top`/`_parent` are refused outright, `_self`\n * merely moves the frame — so all three are asked of the host instead. Anything else opens a\n * new context, which the sandbox allows.\n */\nconst SAME_CONTEXT_TARGETS = new Set(['_top', '_self', '_parent']);\n\nexport interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n /** A root-relative platform path, e.g. `/present/github/acme/todo`. */\n path: string;\n children?: ReactNode;\n}\n\n/**\n * The ONE way to render an anchor to a PLATFORM route.\n *\n * It builds the href with {@link platformHref} (resolving against the host's outer origin)\n * and, on a plain left-click, asks the HOST to perform the navigation.\n *\n * **Why the anchor alone does not work (R3-568).** Until R3-568 this component relied solely\n * on `target=\"_top\"`. The app frame's sandbox omits `allow-top-navigation-by-user-activation`\n * deliberately — an app that can move the top-level window on its own schedule is a phishing\n * primitive — so the browser refuses outright and logs *\"Unsafe attempt to initiate\n * navigation…\"*. Every platform link in every app was inert on the host: measured on\n * production, no Open, no Fork, no Run, and no way to sign in.\n *\n * The host is therefore the only thing that can perform this navigation, and it is asked the\n * same way in-app routing asks — {@link navigate}. The host decides: it accepts a target\n * outside the app's own path prefix only when the target is same-origin, is a recognised\n * platform route, and the HOST's own `navigator.userActivation` says a person just acted.\n * Nothing the app asserts substitutes for that gesture, and a refusal is the host's to report.\n *\n * **The `href` and `target` stay.** They are what make copy-link, middle-click and\n * open-in-new-tab produce something that resolves for another reader — gestures the sandbox\n * does allow (`allow-popups`), which the handler below deliberately declines to intercept.\n * The href is also the correct behaviour with no host at all (`vite dev`), where there is\n * nobody to ask.\n *\n * External URLs (`https://…`) are not platform routes and should stay plain\n * `<a target=\"_blank\">` anchors.\n */\nexport function PlatformLink({ path, children, onClick, target = '_top', ...rest }: PlatformLinkProps) {\n const { outerHref } = use(TinkerableContext);\n const href = platformHref(outerHref, path);\n\n const clickHandler = useComposedAnchorClick(\n onClick,\n (event) => {\n // Open-in-new-tab gestures are the browser's — the sandbox allows those.\n if (isBrowserGestureClick(event)) return;\n // Intercept every target that stays in an EXISTING browsing context, not just the\n // default. `_top` and `_parent` both address the host document from inside the app\n // frame and are refused by the same missing sandbox flag; `_self` would navigate the\n // app frame itself to a host URL, framing the host inside its own sandbox — the\n // regression `components/Link.tsx` documents. Only a NEW context (`_blank`, a named\n // window) is genuinely the browser's, because that is what `allow-popups` permits.\n if (!SAME_CONTEXT_TARGETS.has(target)) return;\n // No host (`vite dev`): there is nobody to ask, and the anchor's own href is right.\n if (!outerHref) return;\n event.preventDefault();\n navigate(href);\n },\n [href, outerHref, target],\n );\n\n return (\n <a {...rest} href={href} target={target} onClick={clickHandler}>\n {children}\n </a>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6FI;AA5FJ,mBAAoB;AAEpB,yBAA8D;AAC9D,qBAAyB;AACzB,+BAAkC;AAClC,sBAA6B;AAetB,MAAM,kBAAkB,MAAkC;AAC/D,QAAM,EAAE,UAAU,QAAI,kBAAI,0CAAiB;AAC3C,SAAO,CAAC,aAAiB,8BAAa,WAAW,IAAI;AACvD;AAQA,MAAM,uBAAuB,oBAAI,IAAI,CAAC,QAAQ,SAAS,SAAS,CAAC;AAoC1D,SAAS,aAAa,EAAE,MAAM,UAAU,SAAS,SAAS,QAAQ,GAAG,KAAK,GAAsB;AACrG,QAAM,EAAE,UAAU,QAAI,kBAAI,0CAAiB;AAC3C,QAAM,WAAO,8BAAa,WAAW,IAAI;AAEzC,QAAM,mBAAe;AAAA,IACnB;AAAA,IACA,CAAC,UAAU;AAET,cAAI,0CAAsB,KAAK,EAAG;AAOlC,UAAI,CAAC,qBAAqB,IAAI,MAAM,EAAG;AAEvC,UAAI,CAAC,UAAW;AAChB,YAAM,eAAe;AACrB,mCAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,MAAM,WAAW,MAAM;AAAA,EAC1B;AAEA,SACE,4CAAC,OAAG,GAAG,MAAM,MAAY,QAAgB,SAAS,cAC/C,UACH;AAEJ;","names":[]}
|
package/dist/platformLink.d.cts
CHANGED
|
@@ -7,10 +7,12 @@ import { AnchorHTMLAttributes, ReactNode } from 'react';
|
|
|
7
7
|
* The returned closure is fresh each render (its output is pure, so identity churn is
|
|
8
8
|
* harmless); an empty context (no host, `vite dev`) yields the path unchanged.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Prefer {@link PlatformLink} over calling this directly. An href alone does not reach a
|
|
11
|
+
* platform route from inside the app frame (see that component), so a consumer that renders
|
|
12
|
+
* its own anchor from this string must ask the host itself — otherwise it ships a link that
|
|
13
|
+
* copies and opens-in-new-tab correctly and does nothing at all on a plain click. It is kept
|
|
14
|
+
* exported because the wire and the module surface are additive-only
|
|
15
|
+
* (`SDK_PACKAGING_SPEC` §9): an app pinned to an older SDK may already import it.
|
|
14
16
|
*/
|
|
15
17
|
declare const usePlatformHref: () => ((path: string) => string);
|
|
16
18
|
interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
|
|
@@ -19,12 +21,33 @@ interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>
|
|
|
19
21
|
children?: ReactNode;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
|
-
* The ONE way to render an anchor to a PLATFORM route
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* The ONE way to render an anchor to a PLATFORM route.
|
|
25
|
+
*
|
|
26
|
+
* It builds the href with {@link platformHref} (resolving against the host's outer origin)
|
|
27
|
+
* and, on a plain left-click, asks the HOST to perform the navigation.
|
|
28
|
+
*
|
|
29
|
+
* **Why the anchor alone does not work (R3-568).** Until R3-568 this component relied solely
|
|
30
|
+
* on `target="_top"`. The app frame's sandbox omits `allow-top-navigation-by-user-activation`
|
|
31
|
+
* deliberately — an app that can move the top-level window on its own schedule is a phishing
|
|
32
|
+
* primitive — so the browser refuses outright and logs *"Unsafe attempt to initiate
|
|
33
|
+
* navigation…"*. Every platform link in every app was inert on the host: measured on
|
|
34
|
+
* production, no Open, no Fork, no Run, and no way to sign in.
|
|
35
|
+
*
|
|
36
|
+
* The host is therefore the only thing that can perform this navigation, and it is asked the
|
|
37
|
+
* same way in-app routing asks — {@link navigate}. The host decides: it accepts a target
|
|
38
|
+
* outside the app's own path prefix only when the target is same-origin, is a recognised
|
|
39
|
+
* platform route, and the HOST's own `navigator.userActivation` says a person just acted.
|
|
40
|
+
* Nothing the app asserts substitutes for that gesture, and a refusal is the host's to report.
|
|
41
|
+
*
|
|
42
|
+
* **The `href` and `target` stay.** They are what make copy-link, middle-click and
|
|
43
|
+
* open-in-new-tab produce something that resolves for another reader — gestures the sandbox
|
|
44
|
+
* does allow (`allow-popups`), which the handler below deliberately declines to intercept.
|
|
45
|
+
* The href is also the correct behaviour with no host at all (`vite dev`), where there is
|
|
46
|
+
* nobody to ask.
|
|
47
|
+
*
|
|
48
|
+
* External URLs (`https://…`) are not platform routes and should stay plain
|
|
49
|
+
* `<a target="_blank">` anchors.
|
|
27
50
|
*/
|
|
28
|
-
declare function PlatformLink({ path, children, ...rest }: PlatformLinkProps): react.JSX.Element;
|
|
51
|
+
declare function PlatformLink({ path, children, onClick, target, ...rest }: PlatformLinkProps): react.JSX.Element;
|
|
29
52
|
|
|
30
53
|
export { PlatformLink, type PlatformLinkProps, usePlatformHref };
|
package/dist/platformLink.d.ts
CHANGED
|
@@ -7,10 +7,12 @@ import { AnchorHTMLAttributes, ReactNode } from 'react';
|
|
|
7
7
|
* The returned closure is fresh each render (its output is pure, so identity churn is
|
|
8
8
|
* harmless); an empty context (no host, `vite dev`) yields the path unchanged.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Prefer {@link PlatformLink} over calling this directly. An href alone does not reach a
|
|
11
|
+
* platform route from inside the app frame (see that component), so a consumer that renders
|
|
12
|
+
* its own anchor from this string must ask the host itself — otherwise it ships a link that
|
|
13
|
+
* copies and opens-in-new-tab correctly and does nothing at all on a plain click. It is kept
|
|
14
|
+
* exported because the wire and the module surface are additive-only
|
|
15
|
+
* (`SDK_PACKAGING_SPEC` §9): an app pinned to an older SDK may already import it.
|
|
14
16
|
*/
|
|
15
17
|
declare const usePlatformHref: () => ((path: string) => string);
|
|
16
18
|
interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
|
|
@@ -19,12 +21,33 @@ interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>
|
|
|
19
21
|
children?: ReactNode;
|
|
20
22
|
}
|
|
21
23
|
/**
|
|
22
|
-
* The ONE way to render an anchor to a PLATFORM route
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* The ONE way to render an anchor to a PLATFORM route.
|
|
25
|
+
*
|
|
26
|
+
* It builds the href with {@link platformHref} (resolving against the host's outer origin)
|
|
27
|
+
* and, on a plain left-click, asks the HOST to perform the navigation.
|
|
28
|
+
*
|
|
29
|
+
* **Why the anchor alone does not work (R3-568).** Until R3-568 this component relied solely
|
|
30
|
+
* on `target="_top"`. The app frame's sandbox omits `allow-top-navigation-by-user-activation`
|
|
31
|
+
* deliberately — an app that can move the top-level window on its own schedule is a phishing
|
|
32
|
+
* primitive — so the browser refuses outright and logs *"Unsafe attempt to initiate
|
|
33
|
+
* navigation…"*. Every platform link in every app was inert on the host: measured on
|
|
34
|
+
* production, no Open, no Fork, no Run, and no way to sign in.
|
|
35
|
+
*
|
|
36
|
+
* The host is therefore the only thing that can perform this navigation, and it is asked the
|
|
37
|
+
* same way in-app routing asks — {@link navigate}. The host decides: it accepts a target
|
|
38
|
+
* outside the app's own path prefix only when the target is same-origin, is a recognised
|
|
39
|
+
* platform route, and the HOST's own `navigator.userActivation` says a person just acted.
|
|
40
|
+
* Nothing the app asserts substitutes for that gesture, and a refusal is the host's to report.
|
|
41
|
+
*
|
|
42
|
+
* **The `href` and `target` stay.** They are what make copy-link, middle-click and
|
|
43
|
+
* open-in-new-tab produce something that resolves for another reader — gestures the sandbox
|
|
44
|
+
* does allow (`allow-popups`), which the handler below deliberately declines to intercept.
|
|
45
|
+
* The href is also the correct behaviour with no host at all (`vite dev`), where there is
|
|
46
|
+
* nobody to ask.
|
|
47
|
+
*
|
|
48
|
+
* External URLs (`https://…`) are not platform routes and should stay plain
|
|
49
|
+
* `<a target="_blank">` anchors.
|
|
27
50
|
*/
|
|
28
|
-
declare function PlatformLink({ path, children, ...rest }: PlatformLinkProps): react.JSX.Element;
|
|
51
|
+
declare function PlatformLink({ path, children, onClick, target, ...rest }: PlatformLinkProps): react.JSX.Element;
|
|
29
52
|
|
|
30
53
|
export { PlatformLink, type PlatformLinkProps, usePlatformHref };
|
package/dist/platformLink.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import "./chunk-VHAA22YE.js";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { use } from "react";
|
|
4
|
+
import { isBrowserGestureClick, useComposedAnchorClick } from "./anchorClick";
|
|
5
|
+
import { navigate } from "./routing";
|
|
4
6
|
import { TinkerableContext } from "./TinkerableContext";
|
|
5
7
|
import { platformHref } from "./urlUtils";
|
|
6
8
|
const usePlatformHref = () => {
|
|
7
9
|
const { outerHref } = use(TinkerableContext);
|
|
8
10
|
return (path) => platformHref(outerHref, path);
|
|
9
11
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const SAME_CONTEXT_TARGETS = /* @__PURE__ */ new Set(["_top", "_self", "_parent"]);
|
|
13
|
+
function PlatformLink({ path, children, onClick, target = "_top", ...rest }) {
|
|
14
|
+
const { outerHref } = use(TinkerableContext);
|
|
15
|
+
const href = platformHref(outerHref, path);
|
|
16
|
+
const clickHandler = useComposedAnchorClick(
|
|
17
|
+
onClick,
|
|
18
|
+
(event) => {
|
|
19
|
+
if (isBrowserGestureClick(event)) return;
|
|
20
|
+
if (!SAME_CONTEXT_TARGETS.has(target)) return;
|
|
21
|
+
if (!outerHref) return;
|
|
22
|
+
event.preventDefault();
|
|
23
|
+
navigate(href);
|
|
24
|
+
},
|
|
25
|
+
[href, outerHref, target]
|
|
26
|
+
);
|
|
27
|
+
return /* @__PURE__ */ jsx("a", { ...rest, href, target, onClick: clickHandler, children });
|
|
13
28
|
}
|
|
14
29
|
export {
|
|
15
30
|
PlatformLink,
|
package/dist/platformLink.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/platformLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport { use } from 'react';\n\nimport { TinkerableContext } from './TinkerableContext';\nimport { platformHref } from './urlUtils';\n\n/**\n * Build a PLATFORM-space href (`/present/…`, `/edit/github/…`, `/home`) in the host's URL\n * space, reading `outerHref` from {@link TinkerableContext} the way `useTinkerableLink` does.\n * The returned closure is fresh each render (its output is pure, so identity churn is\n * harmless); an empty context (no host, `vite dev`) yields the path unchanged.\n *\n *
|
|
1
|
+
{"version":3,"sources":["../src/platformLink.tsx"],"sourcesContent":["import type { AnchorHTMLAttributes, ReactNode } from 'react';\nimport { use } from 'react';\n\nimport { isBrowserGestureClick, useComposedAnchorClick } from './anchorClick';\nimport { navigate } from './routing';\nimport { TinkerableContext } from './TinkerableContext';\nimport { platformHref } from './urlUtils';\n\n/**\n * Build a PLATFORM-space href (`/present/…`, `/edit/github/…`, `/home`) in the host's URL\n * space, reading `outerHref` from {@link TinkerableContext} the way `useTinkerableLink` does.\n * The returned closure is fresh each render (its output is pure, so identity churn is\n * harmless); an empty context (no host, `vite dev`) yields the path unchanged.\n *\n * Prefer {@link PlatformLink} over calling this directly. An href alone does not reach a\n * platform route from inside the app frame (see that component), so a consumer that renders\n * its own anchor from this string must ask the host itself — otherwise it ships a link that\n * copies and opens-in-new-tab correctly and does nothing at all on a plain click. It is kept\n * exported because the wire and the module surface are additive-only\n * (`SDK_PACKAGING_SPEC` §9): an app pinned to an older SDK may already import it.\n */\nexport const usePlatformHref = (): ((path: string) => string) => {\n const { outerHref } = use(TinkerableContext);\n return (path: string) => platformHref(outerHref, path);\n};\n\n/**\n * Targets that reuse an existing browsing context. All three are unreachable from inside the\n * sandboxed app frame by the anchor alone — `_top`/`_parent` are refused outright, `_self`\n * merely moves the frame — so all three are asked of the host instead. Anything else opens a\n * new context, which the sandbox allows.\n */\nconst SAME_CONTEXT_TARGETS = new Set(['_top', '_self', '_parent']);\n\nexport interface PlatformLinkProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {\n /** A root-relative platform path, e.g. `/present/github/acme/todo`. */\n path: string;\n children?: ReactNode;\n}\n\n/**\n * The ONE way to render an anchor to a PLATFORM route.\n *\n * It builds the href with {@link platformHref} (resolving against the host's outer origin)\n * and, on a plain left-click, asks the HOST to perform the navigation.\n *\n * **Why the anchor alone does not work (R3-568).** Until R3-568 this component relied solely\n * on `target=\"_top\"`. The app frame's sandbox omits `allow-top-navigation-by-user-activation`\n * deliberately — an app that can move the top-level window on its own schedule is a phishing\n * primitive — so the browser refuses outright and logs *\"Unsafe attempt to initiate\n * navigation…\"*. Every platform link in every app was inert on the host: measured on\n * production, no Open, no Fork, no Run, and no way to sign in.\n *\n * The host is therefore the only thing that can perform this navigation, and it is asked the\n * same way in-app routing asks — {@link navigate}. The host decides: it accepts a target\n * outside the app's own path prefix only when the target is same-origin, is a recognised\n * platform route, and the HOST's own `navigator.userActivation` says a person just acted.\n * Nothing the app asserts substitutes for that gesture, and a refusal is the host's to report.\n *\n * **The `href` and `target` stay.** They are what make copy-link, middle-click and\n * open-in-new-tab produce something that resolves for another reader — gestures the sandbox\n * does allow (`allow-popups`), which the handler below deliberately declines to intercept.\n * The href is also the correct behaviour with no host at all (`vite dev`), where there is\n * nobody to ask.\n *\n * External URLs (`https://…`) are not platform routes and should stay plain\n * `<a target=\"_blank\">` anchors.\n */\nexport function PlatformLink({ path, children, onClick, target = '_top', ...rest }: PlatformLinkProps) {\n const { outerHref } = use(TinkerableContext);\n const href = platformHref(outerHref, path);\n\n const clickHandler = useComposedAnchorClick(\n onClick,\n (event) => {\n // Open-in-new-tab gestures are the browser's — the sandbox allows those.\n if (isBrowserGestureClick(event)) return;\n // Intercept every target that stays in an EXISTING browsing context, not just the\n // default. `_top` and `_parent` both address the host document from inside the app\n // frame and are refused by the same missing sandbox flag; `_self` would navigate the\n // app frame itself to a host URL, framing the host inside its own sandbox — the\n // regression `components/Link.tsx` documents. Only a NEW context (`_blank`, a named\n // window) is genuinely the browser's, because that is what `allow-popups` permits.\n if (!SAME_CONTEXT_TARGETS.has(target)) return;\n // No host (`vite dev`): there is nobody to ask, and the anchor's own href is right.\n if (!outerHref) return;\n event.preventDefault();\n navigate(href);\n },\n [href, outerHref, target],\n );\n\n return (\n <a {...rest} href={href} target={target} onClick={clickHandler}>\n {children}\n </a>\n );\n}\n"],"mappings":";AA6FI;AA5FJ,SAAS,WAAW;AAEpB,SAAS,uBAAuB,8BAA8B;AAC9D,SAAS,gBAAgB;AACzB,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAetB,MAAM,kBAAkB,MAAkC;AAC/D,QAAM,EAAE,UAAU,IAAI,IAAI,iBAAiB;AAC3C,SAAO,CAAC,SAAiB,aAAa,WAAW,IAAI;AACvD;AAQA,MAAM,uBAAuB,oBAAI,IAAI,CAAC,QAAQ,SAAS,SAAS,CAAC;AAoC1D,SAAS,aAAa,EAAE,MAAM,UAAU,SAAS,SAAS,QAAQ,GAAG,KAAK,GAAsB;AACrG,QAAM,EAAE,UAAU,IAAI,IAAI,iBAAiB;AAC3C,QAAM,OAAO,aAAa,WAAW,IAAI;AAEzC,QAAM,eAAe;AAAA,IACnB;AAAA,IACA,CAAC,UAAU;AAET,UAAI,sBAAsB,KAAK,EAAG;AAOlC,UAAI,CAAC,qBAAqB,IAAI,MAAM,EAAG;AAEvC,UAAI,CAAC,UAAW;AAChB,YAAM,eAAe;AACrB,eAAS,IAAI;AAAA,IACf;AAAA,IACA,CAAC,MAAM,WAAW,MAAM;AAAA,EAC1B;AAEA,SACE,oBAAC,OAAG,GAAG,MAAM,MAAY,QAAgB,SAAS,cAC/C,UACH;AAEJ;","names":[]}
|
package/dist/protocolSchemes.cjs
CHANGED
|
@@ -34,6 +34,7 @@ const SCHEMES = {
|
|
|
34
34
|
[import_protocol.PROTOCOL_IPC]: schemeOf(import_protocol.PROTOCOL_IPC),
|
|
35
35
|
[import_protocol.PROTOCOL_LAUNCH]: schemeOf(import_protocol.PROTOCOL_LAUNCH),
|
|
36
36
|
[import_protocol.PROTOCOL_LLM]: schemeOf(import_protocol.PROTOCOL_LLM),
|
|
37
|
+
[import_protocol.PROTOCOL_OPENREPO]: schemeOf(import_protocol.PROTOCOL_OPENREPO),
|
|
37
38
|
[import_protocol.PROTOCOL_RECENTS]: schemeOf(import_protocol.PROTOCOL_RECENTS),
|
|
38
39
|
[import_protocol.PROTOCOL_SECRETS]: schemeOf(import_protocol.PROTOCOL_SECRETS),
|
|
39
40
|
[import_protocol.PROTOCOL_SETTINGS]: schemeOf(import_protocol.PROTOCOL_SETTINGS),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,
|
|
1
|
+
{"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_OPENREPO,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,sBAkBO;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kCAAkB,GAAG,SAAS,kCAAkB;AAAA,EACjD,CAAC,mCAAmB,GAAG,SAAS,mCAAmB;AAAA,EACnD,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,iCAAiB,GAAG,SAAS,iCAAiB;AAAA,EAC/C,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,iCAAiB,GAAG,SAAS,iCAAiB;AAAA,EAC/C,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AACvC;","names":[]}
|
|
@@ -9,6 +9,7 @@ declare const SCHEMES: {
|
|
|
9
9
|
readonly "protocol-ipc": "ipc";
|
|
10
10
|
readonly "protocol-launch": "launch";
|
|
11
11
|
readonly "protocol-llm": "llm";
|
|
12
|
+
readonly "protocol-openrepo": "openrepo";
|
|
12
13
|
readonly "protocol-recents": "recents";
|
|
13
14
|
readonly "protocol-secrets": "secrets";
|
|
14
15
|
readonly "protocol-settings": "settings";
|
|
@@ -9,6 +9,7 @@ declare const SCHEMES: {
|
|
|
9
9
|
readonly "protocol-ipc": "ipc";
|
|
10
10
|
readonly "protocol-launch": "launch";
|
|
11
11
|
readonly "protocol-llm": "llm";
|
|
12
|
+
readonly "protocol-openrepo": "openrepo";
|
|
12
13
|
readonly "protocol-recents": "recents";
|
|
13
14
|
readonly "protocol-secrets": "secrets";
|
|
14
15
|
readonly "protocol-settings": "settings";
|
package/dist/protocolSchemes.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
PROTOCOL_IPC,
|
|
10
10
|
PROTOCOL_LAUNCH,
|
|
11
11
|
PROTOCOL_LLM,
|
|
12
|
+
PROTOCOL_OPENREPO,
|
|
12
13
|
PROTOCOL_RECENTS,
|
|
13
14
|
PROTOCOL_SECRETS,
|
|
14
15
|
PROTOCOL_SETTINGS,
|
|
@@ -29,6 +30,7 @@ const SCHEMES = {
|
|
|
29
30
|
[PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),
|
|
30
31
|
[PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),
|
|
31
32
|
[PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),
|
|
33
|
+
[PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),
|
|
32
34
|
[PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),
|
|
33
35
|
[PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),
|
|
34
36
|
[PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kBAAkB,GAAG,SAAS,kBAAkB;AAAA,EACjD,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_ANALYTICS,\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FEED,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_OPENREPO,\n PROTOCOL_RECENTS,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_ANALYTICS]: schemeOf(PROTOCOL_ANALYTICS),\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FEED]: schemeOf(PROTOCOL_FEED),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_OPENREPO]: schemeOf(PROTOCOL_OPENREPO),\n [PROTOCOL_RECENTS]: schemeOf(PROTOCOL_RECENTS),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,kBAAkB,GAAG,SAAS,kBAAkB;AAAA,EACjD,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
|
package/dist/recents.cjs
CHANGED
|
@@ -25,7 +25,15 @@ module.exports = __toCommonJS(recents_exports);
|
|
|
25
25
|
var import_sandboxUtils = require("./sandboxUtils");
|
|
26
26
|
var import_protocol = require("./generated/protocol");
|
|
27
27
|
var import_protocolSchemes = require("./protocolSchemes");
|
|
28
|
-
const recentsRequest = (params) =>
|
|
28
|
+
const recentsRequest = async (params) => {
|
|
29
|
+
const res = await (0, import_sandboxUtils.protocolRequest)(import_protocolSchemes.SCHEMES[import_protocol.PROTOCOL_RECENTS], "list", [params]);
|
|
30
|
+
if (!res || res.ok !== true) {
|
|
31
|
+
const err = new Error(res?.message ?? "recents request failed");
|
|
32
|
+
err.code = res?.code ?? "unknown";
|
|
33
|
+
throw err;
|
|
34
|
+
}
|
|
35
|
+
return res.data;
|
|
36
|
+
};
|
|
29
37
|
async function listRecentProjects() {
|
|
30
38
|
const res = await recentsRequest({});
|
|
31
39
|
return res.projects ?? null;
|
package/dist/recents.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recents.ts"],"sourcesContent":["// The recent-projects record — app-facing surface (R3-485, OSO §4.3).\n//\n// `listRecentProjects()` reads the HOST-OWNED record through the one elevated,\n// app-scoped `recents:read` capability. Two gates stand between an app and the\n// record, and both must pass: the §8.4 capability grant (declared as\n// `requests: { 'recents:read': {} }` and consented per (app, principal)), and the\n// handler's structural binding — the read is granted only to the app bound at\n// `page.home`. For every other app the call resolves to a refusal, whatever it\n// declared.\n//\n// A project entry is COORDINATES (provider/namespace/repository/ref + when) — a\n// location the user already navigated to under their own authority, never an\n// in-repo path. Opening one runs the ordinary load path with the ordinary\n// consent: the record confers nothing (R-OSO-22).\nimport { protocolRequest } from './sandboxUtils';\nimport { PROTOCOL_RECENTS } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** One recently opened project — repository coordinates, never an in-repo path. */\nexport interface RecentProject {\n provider: string;\n namespace: string;\n repository: string;\n /** The ref the user opened (`main`, a sha). */\n ref: string;\n /** When the user last opened it (epoch ms). */\n ts: number;\n}\n\ninterface RecentsReply {\n /** Newest-first; `null` when the record is empty or the user cleared it. */\n projects: RecentProject[] | null;\n}\n\n/**\n * The one call shape the SDK speaks under `protocol-recents` — read, or clear\n * (same gated surface). A single typed call site keeps the wire contract exact:\n * the snapshot gate reads THIS shape, and the host handler accepts the same.\n */\nconst recentsRequest = (params: { clear?: boolean }): Promise<RecentsReply>
|
|
1
|
+
{"version":3,"sources":["../src/recents.ts"],"sourcesContent":["// The recent-projects record — app-facing surface (R3-485, OSO §4.3).\n//\n// `listRecentProjects()` reads the HOST-OWNED record through the one elevated,\n// app-scoped `recents:read` capability. Two gates stand between an app and the\n// record, and both must pass: the §8.4 capability grant (declared as\n// `requests: { 'recents:read': {} }` and consented per (app, principal)), and the\n// handler's structural binding — the read is granted only to the app bound at\n// `page.home`. For every other app the call resolves to a refusal, whatever it\n// declared.\n//\n// A project entry is COORDINATES (provider/namespace/repository/ref + when) — a\n// location the user already navigated to under their own authority, never an\n// in-repo path. Opening one runs the ordinary load path with the ordinary\n// consent: the record confers nothing (R-OSO-22).\nimport { protocolRequest } from './sandboxUtils';\nimport { PROTOCOL_RECENTS } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** One recently opened project — repository coordinates, never an in-repo path. */\nexport interface RecentProject {\n provider: string;\n namespace: string;\n repository: string;\n /** The ref the user opened (`main`, a sha). */\n ref: string;\n /** When the user last opened it (epoch ms). */\n ts: number;\n}\n\ninterface RecentsReply {\n /** Newest-first; `null` when the record is empty or the user cleared it. */\n projects: RecentProject[] | null;\n}\n\n/**\n * The host wraps every action result as `{ ok: true, data }` and every refusal\n * as `{ ok: false, code, message }` INSIDE the reply's `result` — refusals\n * resolve rather than reject, because the sandbox's protocolRequest rejects\n * with a bare `Error` and would drop the `code` (requestDispatcher's reply\n * contract). Unwrap here, like every other gated module, so callers see the\n * coded refusal and never a silent `null`.\n */\ntype RecentsResult = { ok: true; data: RecentsReply } | { ok: false; code: string; message?: string };\n\n/**\n * The one call shape the SDK speaks under `protocol-recents` — read, or clear\n * (same gated surface). A single typed call site keeps the wire contract exact:\n * the snapshot gate reads THIS shape, and the host handler accepts the same.\n */\nconst recentsRequest = async (params: { clear?: boolean }): Promise<RecentsReply> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_RECENTS], 'list', [params])) as RecentsResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'recents request failed') as Error & { code: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/**\n * The user's recently opened projects, newest-first, or `null` when the record is\n * absent (R-OSO-22: cleared is absent, never an empty list). Refuses for any app\n * that is not the `page.home` binding.\n *\n * @throws a coded error — `forbidden` without the grant or the binding, or the\n * host's own code; never a silent `null` for a refusal.\n */\nexport async function listRecentProjects(): Promise<RecentProject[] | null> {\n const res = await recentsRequest({});\n return res.projects ?? null;\n}\n\n/**\n * Clear the user's recent-projects record (the surface afterwards is absent, not\n * empty). Rides the same page.home binding gate as the read.\n *\n * @throws a coded error when the clear is refused — the caller must not treat\n * the record as cleared.\n */\nexport async function clearRecentProjects(): Promise<void> {\n await recentsRequest({ clear: true });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,0BAAgC;AAChC,sBAAiC;AACjC,6BAAwB;AAiCxB,MAAM,iBAAiB,OAAO,WAAuD;AACnF,QAAM,MAAO,UAAM,qCAAgB,+BAAQ,gCAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC;AAC9E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,wBAAwB;AAC9D,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAUA,eAAsB,qBAAsD;AAC1E,QAAM,MAAM,MAAM,eAAe,CAAC,CAAC;AACnC,SAAO,IAAI,YAAY;AACzB;AASA,eAAsB,sBAAqC;AACzD,QAAM,eAAe,EAAE,OAAO,KAAK,CAAC;AACtC;","names":[]}
|
package/dist/recents.d.cts
CHANGED
|
@@ -12,11 +12,17 @@ interface RecentProject {
|
|
|
12
12
|
* The user's recently opened projects, newest-first, or `null` when the record is
|
|
13
13
|
* absent (R-OSO-22: cleared is absent, never an empty list). Refuses for any app
|
|
14
14
|
* that is not the `page.home` binding.
|
|
15
|
+
*
|
|
16
|
+
* @throws a coded error — `forbidden` without the grant or the binding, or the
|
|
17
|
+
* host's own code; never a silent `null` for a refusal.
|
|
15
18
|
*/
|
|
16
19
|
declare function listRecentProjects(): Promise<RecentProject[] | null>;
|
|
17
20
|
/**
|
|
18
21
|
* Clear the user's recent-projects record (the surface afterwards is absent, not
|
|
19
22
|
* empty). Rides the same page.home binding gate as the read.
|
|
23
|
+
*
|
|
24
|
+
* @throws a coded error when the clear is refused — the caller must not treat
|
|
25
|
+
* the record as cleared.
|
|
20
26
|
*/
|
|
21
27
|
declare function clearRecentProjects(): Promise<void>;
|
|
22
28
|
|
package/dist/recents.d.ts
CHANGED
|
@@ -12,11 +12,17 @@ interface RecentProject {
|
|
|
12
12
|
* The user's recently opened projects, newest-first, or `null` when the record is
|
|
13
13
|
* absent (R-OSO-22: cleared is absent, never an empty list). Refuses for any app
|
|
14
14
|
* that is not the `page.home` binding.
|
|
15
|
+
*
|
|
16
|
+
* @throws a coded error — `forbidden` without the grant or the binding, or the
|
|
17
|
+
* host's own code; never a silent `null` for a refusal.
|
|
15
18
|
*/
|
|
16
19
|
declare function listRecentProjects(): Promise<RecentProject[] | null>;
|
|
17
20
|
/**
|
|
18
21
|
* Clear the user's recent-projects record (the surface afterwards is absent, not
|
|
19
22
|
* empty). Rides the same page.home binding gate as the read.
|
|
23
|
+
*
|
|
24
|
+
* @throws a coded error when the clear is refused — the caller must not treat
|
|
25
|
+
* the record as cleared.
|
|
20
26
|
*/
|
|
21
27
|
declare function clearRecentProjects(): Promise<void>;
|
|
22
28
|
|
package/dist/recents.js
CHANGED
|
@@ -2,7 +2,15 @@ import "./chunk-VHAA22YE.js";
|
|
|
2
2
|
import { protocolRequest } from "./sandboxUtils";
|
|
3
3
|
import { PROTOCOL_RECENTS } from "./generated/protocol";
|
|
4
4
|
import { SCHEMES } from "./protocolSchemes";
|
|
5
|
-
const recentsRequest = (params) =>
|
|
5
|
+
const recentsRequest = async (params) => {
|
|
6
|
+
const res = await protocolRequest(SCHEMES[PROTOCOL_RECENTS], "list", [params]);
|
|
7
|
+
if (!res || res.ok !== true) {
|
|
8
|
+
const err = new Error(res?.message ?? "recents request failed");
|
|
9
|
+
err.code = res?.code ?? "unknown";
|
|
10
|
+
throw err;
|
|
11
|
+
}
|
|
12
|
+
return res.data;
|
|
13
|
+
};
|
|
6
14
|
async function listRecentProjects() {
|
|
7
15
|
const res = await recentsRequest({});
|
|
8
16
|
return res.projects ?? null;
|
package/dist/recents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recents.ts"],"sourcesContent":["// The recent-projects record — app-facing surface (R3-485, OSO §4.3).\n//\n// `listRecentProjects()` reads the HOST-OWNED record through the one elevated,\n// app-scoped `recents:read` capability. Two gates stand between an app and the\n// record, and both must pass: the §8.4 capability grant (declared as\n// `requests: { 'recents:read': {} }` and consented per (app, principal)), and the\n// handler's structural binding — the read is granted only to the app bound at\n// `page.home`. For every other app the call resolves to a refusal, whatever it\n// declared.\n//\n// A project entry is COORDINATES (provider/namespace/repository/ref + when) — a\n// location the user already navigated to under their own authority, never an\n// in-repo path. Opening one runs the ordinary load path with the ordinary\n// consent: the record confers nothing (R-OSO-22).\nimport { protocolRequest } from './sandboxUtils';\nimport { PROTOCOL_RECENTS } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** One recently opened project — repository coordinates, never an in-repo path. */\nexport interface RecentProject {\n provider: string;\n namespace: string;\n repository: string;\n /** The ref the user opened (`main`, a sha). */\n ref: string;\n /** When the user last opened it (epoch ms). */\n ts: number;\n}\n\ninterface RecentsReply {\n /** Newest-first; `null` when the record is empty or the user cleared it. */\n projects: RecentProject[] | null;\n}\n\n/**\n * The one call shape the SDK speaks under `protocol-recents` — read, or clear\n * (same gated surface). A single typed call site keeps the wire contract exact:\n * the snapshot gate reads THIS shape, and the host handler accepts the same.\n */\nconst recentsRequest = (params: { clear?: boolean }): Promise<RecentsReply>
|
|
1
|
+
{"version":3,"sources":["../src/recents.ts"],"sourcesContent":["// The recent-projects record — app-facing surface (R3-485, OSO §4.3).\n//\n// `listRecentProjects()` reads the HOST-OWNED record through the one elevated,\n// app-scoped `recents:read` capability. Two gates stand between an app and the\n// record, and both must pass: the §8.4 capability grant (declared as\n// `requests: { 'recents:read': {} }` and consented per (app, principal)), and the\n// handler's structural binding — the read is granted only to the app bound at\n// `page.home`. For every other app the call resolves to a refusal, whatever it\n// declared.\n//\n// A project entry is COORDINATES (provider/namespace/repository/ref + when) — a\n// location the user already navigated to under their own authority, never an\n// in-repo path. Opening one runs the ordinary load path with the ordinary\n// consent: the record confers nothing (R-OSO-22).\nimport { protocolRequest } from './sandboxUtils';\nimport { PROTOCOL_RECENTS } from './generated/protocol';\nimport { SCHEMES } from './protocolSchemes';\n\n/** One recently opened project — repository coordinates, never an in-repo path. */\nexport interface RecentProject {\n provider: string;\n namespace: string;\n repository: string;\n /** The ref the user opened (`main`, a sha). */\n ref: string;\n /** When the user last opened it (epoch ms). */\n ts: number;\n}\n\ninterface RecentsReply {\n /** Newest-first; `null` when the record is empty or the user cleared it. */\n projects: RecentProject[] | null;\n}\n\n/**\n * The host wraps every action result as `{ ok: true, data }` and every refusal\n * as `{ ok: false, code, message }` INSIDE the reply's `result` — refusals\n * resolve rather than reject, because the sandbox's protocolRequest rejects\n * with a bare `Error` and would drop the `code` (requestDispatcher's reply\n * contract). Unwrap here, like every other gated module, so callers see the\n * coded refusal and never a silent `null`.\n */\ntype RecentsResult = { ok: true; data: RecentsReply } | { ok: false; code: string; message?: string };\n\n/**\n * The one call shape the SDK speaks under `protocol-recents` — read, or clear\n * (same gated surface). A single typed call site keeps the wire contract exact:\n * the snapshot gate reads THIS shape, and the host handler accepts the same.\n */\nconst recentsRequest = async (params: { clear?: boolean }): Promise<RecentsReply> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_RECENTS], 'list', [params])) as RecentsResult;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'recents request failed') as Error & { code: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/**\n * The user's recently opened projects, newest-first, or `null` when the record is\n * absent (R-OSO-22: cleared is absent, never an empty list). Refuses for any app\n * that is not the `page.home` binding.\n *\n * @throws a coded error — `forbidden` without the grant or the binding, or the\n * host's own code; never a silent `null` for a refusal.\n */\nexport async function listRecentProjects(): Promise<RecentProject[] | null> {\n const res = await recentsRequest({});\n return res.projects ?? null;\n}\n\n/**\n * Clear the user's recent-projects record (the surface afterwards is absent, not\n * empty). Rides the same page.home binding gate as the read.\n *\n * @throws a coded error when the clear is refused — the caller must not treat\n * the record as cleared.\n */\nexport async function clearRecentProjects(): Promise<void> {\n await recentsRequest({ clear: true });\n}\n"],"mappings":";AAcA,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,eAAe;AAiCxB,MAAM,iBAAiB,OAAO,WAAuD;AACnF,QAAM,MAAO,MAAM,gBAAgB,QAAQ,gBAAgB,GAAG,QAAQ,CAAC,MAAM,CAAC;AAC9E,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,wBAAwB;AAC9D,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAUA,eAAsB,qBAAsD;AAC1E,QAAM,MAAM,MAAM,eAAe,CAAC,CAAC;AACnC,SAAO,IAAI,YAAY;AACzB;AASA,eAAsB,sBAAqC;AACzD,QAAM,eAAe,EAAE,OAAO,KAAK,CAAC;AACtC;","names":[]}
|
package/dist/region.cjs
CHANGED
|
@@ -19,11 +19,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var region_exports = {};
|
|
20
20
|
__export(region_exports, {
|
|
21
21
|
getRegion: () => getRegion,
|
|
22
|
-
|
|
22
|
+
isRegionHidden: () => isRegionHidden,
|
|
23
|
+
onRegionVisibilityChange: () => onRegionVisibilityChange,
|
|
24
|
+
useRegion: () => useRegion,
|
|
25
|
+
useRegionHidden: () => useRegionHidden
|
|
23
26
|
});
|
|
24
27
|
module.exports = __toCommonJS(region_exports);
|
|
25
28
|
var import_react = require("react");
|
|
26
29
|
var import_hostRuntime = require("./hostRuntime");
|
|
30
|
+
var import_pushChannel = require("./pushChannel");
|
|
31
|
+
var import_protocol = require("./generated/protocol");
|
|
27
32
|
const getRegion = () => (0, import_hostRuntime.getHostRuntime)()?.region ?? null;
|
|
28
33
|
const useRegion = () => {
|
|
29
34
|
const [region, setRegion] = (0, import_react.useState)(getRegion);
|
|
@@ -39,9 +44,24 @@ const useRegion = () => {
|
|
|
39
44
|
}, [region]);
|
|
40
45
|
return region;
|
|
41
46
|
};
|
|
47
|
+
const visibility = (0, import_pushChannel.createPushChannel)({
|
|
48
|
+
pushType: import_protocol.REGION_VISIBILITY,
|
|
49
|
+
requestType: import_protocol.REQUEST_REGION_VISIBILITY,
|
|
50
|
+
initial: false,
|
|
51
|
+
// Tolerant parse: `undefined` means "ignore this message", so a malformed push from
|
|
52
|
+
// some future/older host leaves the last good value standing rather than flipping
|
|
53
|
+
// the app to a value nobody sent.
|
|
54
|
+
parse: (msg) => typeof msg.hidden === "boolean" ? msg.hidden : void 0
|
|
55
|
+
});
|
|
56
|
+
const isRegionHidden = () => visibility.get();
|
|
57
|
+
const onRegionVisibilityChange = (listener) => visibility.onChange(listener);
|
|
58
|
+
const useRegionHidden = () => visibility.use();
|
|
42
59
|
// Annotate the CommonJS export names for ESM import in node:
|
|
43
60
|
0 && (module.exports = {
|
|
44
61
|
getRegion,
|
|
45
|
-
|
|
62
|
+
isRegionHidden,
|
|
63
|
+
onRegionVisibilityChange,
|
|
64
|
+
useRegion,
|
|
65
|
+
useRegionHidden
|
|
46
66
|
});
|
|
47
67
|
//# sourceMappingURL=region.cjs.map
|
package/dist/region.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/region.ts"],"sourcesContent":["// Region-awareness (UI_AS_APPS_SPEC §4.1). The host can mount the SAME app in more\n// than one chrome region — e.g. the agents activity puts one app in BOTH the panel\n// slot (`panel.agent`, the conversation list) and the stage slot\n// (`stage.conversation`, the selected conversation). `getRegion()` lets that one app\n// tell the slots apart and render the right view.\n//\n// This is descriptive only: the region id is a non-secret string the host already\n// knows. It grants nothing and gates nothing — it just names where the app is\n// mounted. The host reports it on the §4 discovery global beside `appMountPath`.\n\nimport { useEffect, useState } from 'react';\nimport { getHostRuntime } from './hostRuntime';\n\n/**\n * The chrome region this app instance is mounted in (e.g. `\"panel.agent\"`,\n * `\"stage.conversation\"`), or `null` when unknown — a standalone app, local\n * `vite dev`, or an older host that doesn't report it.\n */\nexport const getRegion = (): string | null => getHostRuntime()?.region ?? null;\n\n/**\n * React hook form of {@link getRegion}. The region is fixed for an app instance's\n * lifetime, but the discovery global can arrive just after first paint, so this\n * re-reads once the host runtime's `ready` promise resolves.\n */\nexport const useRegion = (): string | null => {\n const [region, setRegion] = useState<string | null>(getRegion);\n useEffect(() => {\n if (region !== null) return;\n let live = true;\n void getHostRuntime()?.ready?.then(() => {\n if (live) setRegion(getRegion());\n });\n return () => {\n live = false;\n };\n }, [region]);\n return region;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,mBAAoC;AACpC,yBAA+B;
|
|
1
|
+
{"version":3,"sources":["../src/region.ts"],"sourcesContent":["// Region-awareness (UI_AS_APPS_SPEC §4.1). The host can mount the SAME app in more\n// than one chrome region — e.g. the agents activity puts one app in BOTH the panel\n// slot (`panel.agent`, the conversation list) and the stage slot\n// (`stage.conversation`, the selected conversation). `getRegion()` lets that one app\n// tell the slots apart and render the right view.\n//\n// This is descriptive only: the region id is a non-secret string the host already\n// knows. It grants nothing and gates nothing — it just names where the app is\n// mounted. The host reports it on the §4 discovery global beside `appMountPath`.\n\nimport { useEffect, useState } from 'react';\nimport { getHostRuntime } from './hostRuntime';\nimport { createPushChannel } from './pushChannel';\nimport { REGION_VISIBILITY, REQUEST_REGION_VISIBILITY } from './generated/protocol';\n\n/**\n * The chrome region this app instance is mounted in (e.g. `\"panel.agent\"`,\n * `\"stage.conversation\"`), or `null` when unknown — a standalone app, local\n * `vite dev`, or an older host that doesn't report it.\n */\nexport const getRegion = (): string | null => getHostRuntime()?.region ?? null;\n\n/**\n * React hook form of {@link getRegion}. The region is fixed for an app instance's\n * lifetime, but the discovery global can arrive just after first paint, so this\n * re-reads once the host runtime's `ready` promise resolves.\n */\nexport const useRegion = (): string | null => {\n const [region, setRegion] = useState<string | null>(getRegion);\n useEffect(() => {\n if (region !== null) return;\n let live = true;\n void getHostRuntime()?.ready?.then(() => {\n if (live) setRegion(getRegion());\n });\n return () => {\n live = false;\n };\n }, [region]);\n return region;\n};\n\n// --- region visibility (R3-562) ---------------------------------------------\n//\n// The host can keep a region MOUNTED and merely hide it, so switching the workbench\n// to another activity does not reboot this app's iframe and destroy whatever it was\n// doing (`AGENT_RUN_DURABILITY_SPEC` §7 R-ARD-20). Hiding stops the frame PAINTING,\n// not executing — which is the problem this read exists to solve.\n//\n// An app that is doing something the user is meant to WATCH must stop while hidden.\n// The case that forced it is an agent loop: `LLM_AND_AGENTS_SPEC` §3.3's loop\n// observability contract requires a streaming transcript, an ordered tool-call log\n// and a reachable stop button, and none of those survives a `display:none` + `inert`\n// subtree. So the loop pauses at its next turn boundary and continues on reveal —\n// nothing is torn down, nothing is lost, and nothing runs where nobody can stop it\n// (R-ARD-20a).\n//\n// Like `getRegion()` above, this is DESCRIPTIVE ONLY: it grants nothing, gates\n// nothing, and names no resource. It is one boolean about the host's own chrome —\n// which is why the host answers it for every frame regardless of capabilities.\n//\n// An app that ignores it behaves exactly as it did before, and so does an app running\n// anywhere the host never pushes it (a standalone tab, `vite dev`, an older host):\n// `initial` is `false` — VISIBLE — because the alternative is an app that pauses\n// forever wherever nobody is telling it anything.\n\nconst visibility = createPushChannel<boolean>({\n pushType: REGION_VISIBILITY,\n requestType: REQUEST_REGION_VISIBILITY,\n initial: false,\n // Tolerant parse: `undefined` means \"ignore this message\", so a malformed push from\n // some future/older host leaves the last good value standing rather than flipping\n // the app to a value nobody sent.\n parse: (msg) => (typeof msg.hidden === 'boolean' ? msg.hidden : undefined),\n});\n\n/**\n * Whether the host has hidden this app's region — it is still mounted and running,\n * but off screen and `inert`, so the user can neither see it nor interact with it.\n *\n * `false` when the host says nothing (a standalone app, `vite dev`, an older host).\n */\nexport const isRegionHidden = (): boolean => visibility.get();\n\n/**\n * Subscribe to this region's visibility. The listener is invoked immediately with the\n * current value, then on every change. Returns an unsubscribe fn.\n *\n * Use it to STOP doing what the user is supposed to be watching, at your own safe\n * boundary — never mid-operation. An agent loop pauses between turns, so every\n * `tool_use` still has its `tool_result`; a poller stops polling; an animation stops\n * animating. Do not use it to hide UI: the host has already done that.\n */\nexport const onRegionVisibilityChange = (listener: (hidden: boolean) => void): (() => void) =>\n visibility.onChange(listener);\n\n/** React hook form of {@link isRegionHidden}, re-rendering on change. */\nexport const useRegionHidden = (): boolean => visibility.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,mBAAoC;AACpC,yBAA+B;AAC/B,yBAAkC;AAClC,sBAA6D;AAOtD,MAAM,YAAY,UAAqB,mCAAe,GAAG,UAAU;AAOnE,MAAM,YAAY,MAAqB;AAC5C,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAwB,SAAS;AAC7D,8BAAU,MAAM;AACd,QAAI,WAAW,KAAM;AACrB,QAAI,OAAO;AACX,aAAK,mCAAe,GAAG,OAAO,KAAK,MAAM;AACvC,UAAI,KAAM,WAAU,UAAU,CAAC;AAAA,IACjC,CAAC;AACD,WAAO,MAAM;AACX,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AACX,SAAO;AACT;AA0BA,MAAM,iBAAa,sCAA2B;AAAA,EAC5C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA;AAAA;AAAA;AAAA,EAIT,OAAO,CAAC,QAAS,OAAO,IAAI,WAAW,YAAY,IAAI,SAAS;AAClE,CAAC;AAQM,MAAM,iBAAiB,MAAe,WAAW,IAAI;AAWrD,MAAM,2BAA2B,CAAC,aACvC,WAAW,SAAS,QAAQ;AAGvB,MAAM,kBAAkB,MAAe,WAAW,IAAI;","names":[]}
|
package/dist/region.d.cts
CHANGED
|
@@ -10,5 +10,24 @@ declare const getRegion: () => string | null;
|
|
|
10
10
|
* re-reads once the host runtime's `ready` promise resolves.
|
|
11
11
|
*/
|
|
12
12
|
declare const useRegion: () => string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the host has hidden this app's region — it is still mounted and running,
|
|
15
|
+
* but off screen and `inert`, so the user can neither see it nor interact with it.
|
|
16
|
+
*
|
|
17
|
+
* `false` when the host says nothing (a standalone app, `vite dev`, an older host).
|
|
18
|
+
*/
|
|
19
|
+
declare const isRegionHidden: () => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to this region's visibility. The listener is invoked immediately with the
|
|
22
|
+
* current value, then on every change. Returns an unsubscribe fn.
|
|
23
|
+
*
|
|
24
|
+
* Use it to STOP doing what the user is supposed to be watching, at your own safe
|
|
25
|
+
* boundary — never mid-operation. An agent loop pauses between turns, so every
|
|
26
|
+
* `tool_use` still has its `tool_result`; a poller stops polling; an animation stops
|
|
27
|
+
* animating. Do not use it to hide UI: the host has already done that.
|
|
28
|
+
*/
|
|
29
|
+
declare const onRegionVisibilityChange: (listener: (hidden: boolean) => void) => (() => void);
|
|
30
|
+
/** React hook form of {@link isRegionHidden}, re-rendering on change. */
|
|
31
|
+
declare const useRegionHidden: () => boolean;
|
|
13
32
|
|
|
14
|
-
export { getRegion, useRegion };
|
|
33
|
+
export { getRegion, isRegionHidden, onRegionVisibilityChange, useRegion, useRegionHidden };
|
package/dist/region.d.ts
CHANGED
|
@@ -10,5 +10,24 @@ declare const getRegion: () => string | null;
|
|
|
10
10
|
* re-reads once the host runtime's `ready` promise resolves.
|
|
11
11
|
*/
|
|
12
12
|
declare const useRegion: () => string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Whether the host has hidden this app's region — it is still mounted and running,
|
|
15
|
+
* but off screen and `inert`, so the user can neither see it nor interact with it.
|
|
16
|
+
*
|
|
17
|
+
* `false` when the host says nothing (a standalone app, `vite dev`, an older host).
|
|
18
|
+
*/
|
|
19
|
+
declare const isRegionHidden: () => boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Subscribe to this region's visibility. The listener is invoked immediately with the
|
|
22
|
+
* current value, then on every change. Returns an unsubscribe fn.
|
|
23
|
+
*
|
|
24
|
+
* Use it to STOP doing what the user is supposed to be watching, at your own safe
|
|
25
|
+
* boundary — never mid-operation. An agent loop pauses between turns, so every
|
|
26
|
+
* `tool_use` still has its `tool_result`; a poller stops polling; an animation stops
|
|
27
|
+
* animating. Do not use it to hide UI: the host has already done that.
|
|
28
|
+
*/
|
|
29
|
+
declare const onRegionVisibilityChange: (listener: (hidden: boolean) => void) => (() => void);
|
|
30
|
+
/** React hook form of {@link isRegionHidden}, re-rendering on change. */
|
|
31
|
+
declare const useRegionHidden: () => boolean;
|
|
13
32
|
|
|
14
|
-
export { getRegion, useRegion };
|
|
33
|
+
export { getRegion, isRegionHidden, onRegionVisibilityChange, useRegion, useRegionHidden };
|