@immediately-run/sdk 0.44.0 → 0.45.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.
Files changed (139) hide show
  1. package/dist/ambient.d.ts +47 -0
  2. package/dist/auth.cjs +3 -2
  3. package/dist/auth.cjs.map +1 -1
  4. package/dist/auth.js +3 -2
  5. package/dist/auth.js.map +1 -1
  6. package/dist/boot.cjs +3 -2
  7. package/dist/boot.cjs.map +1 -1
  8. package/dist/boot.js +3 -2
  9. package/dist/boot.js.map +1 -1
  10. package/dist/catalog.cjs +3 -2
  11. package/dist/catalog.cjs.map +1 -1
  12. package/dist/catalog.js +3 -2
  13. package/dist/catalog.js.map +1 -1
  14. package/dist/contribute.cjs +2 -1
  15. package/dist/contribute.cjs.map +1 -1
  16. package/dist/contribute.js +2 -1
  17. package/dist/contribute.js.map +1 -1
  18. package/dist/debug.cjs +6 -5
  19. package/dist/debug.cjs.map +1 -1
  20. package/dist/debug.js +12 -5
  21. package/dist/debug.js.map +1 -1
  22. package/dist/diagnostics.cjs +3 -2
  23. package/dist/diagnostics.cjs.map +1 -1
  24. package/dist/diagnostics.js +3 -2
  25. package/dist/diagnostics.js.map +1 -1
  26. package/dist/dnd.cjs +5 -3
  27. package/dist/dnd.cjs.map +1 -1
  28. package/dist/dnd.js +5 -3
  29. package/dist/dnd.js.map +1 -1
  30. package/dist/editor.cjs +3 -1
  31. package/dist/editor.cjs.map +1 -1
  32. package/dist/editor.js +3 -1
  33. package/dist/editor.js.map +1 -1
  34. package/dist/editorContext.cjs +3 -2
  35. package/dist/editorContext.cjs.map +1 -1
  36. package/dist/editorContext.js +3 -2
  37. package/dist/editorContext.js.map +1 -1
  38. package/dist/formFactor.cjs +3 -2
  39. package/dist/formFactor.cjs.map +1 -1
  40. package/dist/formFactor.js +3 -2
  41. package/dist/formFactor.js.map +1 -1
  42. package/dist/generated/protocol.cjs +23 -0
  43. package/dist/generated/protocol.cjs.map +1 -0
  44. package/dist/generated/protocol.d.cts +1 -0
  45. package/dist/generated/protocol.d.ts +1 -0
  46. package/dist/generated/protocol.js +2 -0
  47. package/dist/generated/protocol.js.map +1 -0
  48. package/dist/hooks.cjs +22 -14
  49. package/dist/hooks.cjs.map +1 -1
  50. package/dist/hooks.d.cts +26 -4
  51. package/dist/hooks.d.ts +26 -4
  52. package/dist/hooks.js +23 -15
  53. package/dist/hooks.js.map +1 -1
  54. package/dist/index.cjs +2 -0
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.d.cts +2 -1
  57. package/dist/index.d.ts +2 -1
  58. package/dist/index.js +1 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/ipc.cjs +5 -3
  61. package/dist/ipc.cjs.map +1 -1
  62. package/dist/ipc.js +5 -3
  63. package/dist/ipc.js.map +1 -1
  64. package/dist/launch.cjs +5 -3
  65. package/dist/launch.cjs.map +1 -1
  66. package/dist/launch.js +5 -3
  67. package/dist/launch.js.map +1 -1
  68. package/dist/llm.cjs +3 -2
  69. package/dist/llm.cjs.map +1 -1
  70. package/dist/llm.js +3 -2
  71. package/dist/llm.js.map +1 -1
  72. package/dist/metadataSource.cjs +53 -0
  73. package/dist/metadataSource.cjs.map +1 -0
  74. package/dist/metadataSource.d.cts +51 -0
  75. package/dist/metadataSource.d.ts +51 -0
  76. package/dist/metadataSource.js +29 -0
  77. package/dist/metadataSource.js.map +1 -0
  78. package/dist/moduleCache.cjs +2 -1
  79. package/dist/moduleCache.cjs.map +1 -1
  80. package/dist/moduleCache.js +2 -1
  81. package/dist/moduleCache.js.map +1 -1
  82. package/dist/mounts.cjs +13 -10
  83. package/dist/mounts.cjs.map +1 -1
  84. package/dist/mounts.js +23 -10
  85. package/dist/mounts.js.map +1 -1
  86. package/dist/netFetch.cjs +4 -2
  87. package/dist/netFetch.cjs.map +1 -1
  88. package/dist/netFetch.js +4 -2
  89. package/dist/netFetch.js.map +1 -1
  90. package/dist/onFsChange.cjs +2 -1
  91. package/dist/onFsChange.cjs.map +1 -1
  92. package/dist/onFsChange.js +2 -1
  93. package/dist/onFsChange.js.map +1 -1
  94. package/dist/protocolSchemes.cjs +46 -0
  95. package/dist/protocolSchemes.cjs.map +1 -0
  96. package/dist/protocolSchemes.d.cts +18 -0
  97. package/dist/protocolSchemes.d.ts +18 -0
  98. package/dist/protocolSchemes.js +37 -0
  99. package/dist/protocolSchemes.js.map +1 -0
  100. package/dist/routing.cjs +2 -1
  101. package/dist/routing.cjs.map +1 -1
  102. package/dist/routing.js +2 -1
  103. package/dist/routing.js.map +1 -1
  104. package/dist/runtime.cjs +4 -2
  105. package/dist/runtime.cjs.map +1 -1
  106. package/dist/runtime.js +4 -2
  107. package/dist/runtime.js.map +1 -1
  108. package/dist/sandboxTypes.cjs.map +1 -1
  109. package/dist/sandboxTypes.d.cts +40 -6
  110. package/dist/sandboxTypes.d.ts +40 -6
  111. package/dist/secrets.cjs +5 -3
  112. package/dist/secrets.cjs.map +1 -1
  113. package/dist/secrets.js +5 -3
  114. package/dist/secrets.js.map +1 -1
  115. package/dist/tasks.cjs +6 -4
  116. package/dist/tasks.cjs.map +1 -1
  117. package/dist/tasks.js +6 -4
  118. package/dist/tasks.js.map +1 -1
  119. package/dist/theme.cjs +5 -3
  120. package/dist/theme.cjs.map +1 -1
  121. package/dist/theme.js +5 -3
  122. package/dist/theme.js.map +1 -1
  123. package/dist/urlUtils.cjs +3 -4
  124. package/dist/urlUtils.cjs.map +1 -1
  125. package/dist/urlUtils.d.cts +3 -10
  126. package/dist/urlUtils.d.ts +3 -10
  127. package/dist/urlUtils.js +1 -2
  128. package/dist/urlUtils.js.map +1 -1
  129. package/dist/vcs.cjs +5 -3
  130. package/dist/vcs.cjs.map +1 -1
  131. package/dist/vcs.js +5 -3
  132. package/dist/vcs.js.map +1 -1
  133. package/dist/version.cjs +1 -1
  134. package/dist/version.cjs.map +1 -1
  135. package/dist/version.d.cts +1 -1
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/version.js.map +1 -1
  139. package/package.json +11 -4
@@ -0,0 +1,47 @@
1
+ // Ambient declarations for the globals the immediately.run SANDBOX provides to app
2
+ // code (PLATFORM_LAYERING_SPEC §4 / S3, R3-276).
3
+ //
4
+ // Activate them in a consuming app with a one-line reference in any `.d.ts` your
5
+ // tsconfig includes:
6
+ //
7
+ // /// <reference types="@immediately-run/sdk/ambient" />
8
+ //
9
+ // This is types-only: nothing here is imported at runtime, so referencing it does
10
+ // not pull the SDK's sandbox-adapter tier into the app's bundle graph.
11
+ //
12
+ // ── What is NOT here: the `fs` module ────────────────────────────────────────
13
+ // The async-only `fs` surface the sandbox exposes is declared by
14
+ // `@immediately-run/dev-fs/fs` (`/// <reference types="@immediately-run/dev-fs/fs" />`),
15
+ // the package that also bridges it to real disk under `vite dev`. It is not
16
+ // re-declared here: a second copy of a type declaration is the drift this whole
17
+ // project is removing, and the copy would be the one apps hit first. Moving that
18
+ // declaration's OWNERSHIP to the SDK (with dev-fs re-referencing it) is the right
19
+ // long-run shape and needs a change in that repo — filed as R3-276b.
20
+ //
21
+ // ── Host obligation: mount before boot ───────────────────────────────────────
22
+ // The corpus a viewer reads must be MOUNTED before the app boots. The SDK offers no
23
+ // "wait for the mount" affordance for it, and none of the types below imply one:
24
+ // app code that reads `fs` at module scope is entitled to assume the filesystem is
25
+ // already there, because the host guarantees the mount precedes evaluation. A host
26
+ // that boots an app first and mounts second is breaking the contract, not exposing
27
+ // a race the app should defend against.
28
+
29
+ import type { EvaluationContext } from './sandboxTypes';
30
+
31
+ declare global {
32
+ /**
33
+ * The module currently being evaluated — the sandbox's evaluation context for
34
+ * THIS file, injected as a global by the runtime.
35
+ *
36
+ * Its use in app code is as `<Include baseModule={module} …>`: the anchor a
37
+ * relative include resolves against, so an included file's own relative imports
38
+ * resolve from where it lives rather than from the app root.
39
+ *
40
+ * TypeScript otherwise types a bare `module` from `@types/node` (a CommonJS
41
+ * `NodeModule`, which this is not) or fails to resolve it at all — the reason app
42
+ * repos ended up asserting `module as any` at every call site.
43
+ */
44
+ const module: EvaluationContext;
45
+ }
46
+
47
+ export {};
package/dist/auth.cjs CHANGED
@@ -24,13 +24,14 @@ __export(auth_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(auth_exports);
26
26
  var import_pushChannel = require("./pushChannel");
27
+ var import_protocol = require("./generated/protocol");
27
28
  const isAuthState = (v) => {
28
29
  const s = v;
29
30
  return !!s && (s.status === "unknown" || s.status === "signed-in" || s.status === "signed-out") && (s.user === null || typeof s.user === "object" && typeof s.user.login === "string");
30
31
  };
31
32
  const channel = (0, import_pushChannel.createPushChannel)({
32
- pushType: "auth-state",
33
- requestType: "request-auth-state",
33
+ pushType: import_protocol.AUTH_STATE,
34
+ requestType: import_protocol.REQUEST_AUTH_STATE,
34
35
  initial: { status: "unknown", user: null },
35
36
  parse: (msg) => isAuthState(msg.state) ? msg.state : void 0
36
37
  });
package/dist/auth.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\n\n/**\n * Login / account state of the immediately.run user, mirrored from the host\n * window into the sandbox.\n *\n * `status` is `'unknown'` until the host has reported a value (use it to\n * distinguish \"still loading\" from a confirmed signed-out session).\n */\nexport type AuthStatus = 'unknown' | 'signed-in' | 'signed-out';\n\n/** The signed-in immediately.run user, as seen by the sandbox (no token, ever). */\nexport interface SandboxUser {\n /** GitHub login (handle) of the signed-in user. */\n login: string;\n}\n\n/** The user's login / account state: a `status` plus the `user` when signed in. */\nexport interface AuthState {\n status: AuthStatus;\n user: SandboxUser | null;\n}\n\nconst isAuthState = (v: unknown): v is AuthState => {\n const s = v as Partial<AuthState> | null;\n return (\n !!s &&\n (s.status === 'unknown' || s.status === 'signed-in' || s.status === 'signed-out') &&\n (s.user === null || (typeof s.user === 'object' && typeof (s.user as SandboxUser).login === 'string'))\n );\n};\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `auth-state`\n// and answers `request-auth-state` (wire format: site-main channelBridge.ts).\nconst channel = createPushChannel<AuthState>({\n pushType: 'auth-state',\n requestType: 'request-auth-state',\n initial: { status: 'unknown', user: null },\n parse: (msg) => (isAuthState(msg.state) ? (msg.state as AuthState) : undefined),\n});\n\n/**\n * Returns the current login / account state. Poll this whenever you need a\n * one-off read; use {@link onAuthChange} or {@link useAuth} to react to changes.\n */\nexport const getAuthState = (): AuthState => channel.get();\n\n/**\n * Subscribe to login / logout changes. The listener is invoked immediately with\n * the current state, then again on every change. Returns an unsubscribe fn.\n */\nexport const onAuthChange = (listener: (state: AuthState) => void): (() => void) =>\n channel.onChange(listener);\n\n/**\n * React hook returning the current login / account state, re-rendering on\n * login / logout.\n */\nexport const useAuth = (): AuthState => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAkC;AAuBlC,MAAM,cAAc,CAAC,MAA+B;AAClD,QAAM,IAAI;AACV,SACE,CAAC,CAAC,MACD,EAAE,WAAW,aAAa,EAAE,WAAW,eAAe,EAAE,WAAW,kBACnE,EAAE,SAAS,QAAS,OAAO,EAAE,SAAS,YAAY,OAAQ,EAAE,KAAqB,UAAU;AAEhG;AAIA,MAAM,cAAU,sCAA6B;AAAA,EAC3C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,EAAE,QAAQ,WAAW,MAAM,KAAK;AAAA,EACzC,OAAO,CAAC,QAAS,YAAY,IAAI,KAAK,IAAK,IAAI,QAAsB;AACvE,CAAC;AAMM,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAMlD,MAAM,eAAe,CAAC,aAC3B,QAAQ,SAAS,QAAQ;AAMpB,MAAM,UAAU,MAAiB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\nimport { AUTH_STATE, REQUEST_AUTH_STATE } from './generated/protocol';\n\n/**\n * Login / account state of the immediately.run user, mirrored from the host\n * window into the sandbox.\n *\n * `status` is `'unknown'` until the host has reported a value (use it to\n * distinguish \"still loading\" from a confirmed signed-out session).\n */\nexport type AuthStatus = 'unknown' | 'signed-in' | 'signed-out';\n\n/** The signed-in immediately.run user, as seen by the sandbox (no token, ever). */\nexport interface SandboxUser {\n /** GitHub login (handle) of the signed-in user. */\n login: string;\n}\n\n/** The user's login / account state: a `status` plus the `user` when signed in. */\nexport interface AuthState {\n status: AuthStatus;\n user: SandboxUser | null;\n}\n\nconst isAuthState = (v: unknown): v is AuthState => {\n const s = v as Partial<AuthState> | null;\n return (\n !!s &&\n (s.status === 'unknown' || s.status === 'signed-in' || s.status === 'signed-out') &&\n (s.user === null || (typeof s.user === 'object' && typeof (s.user as SandboxUser).login === 'string'))\n );\n};\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `auth-state`\n// and answers `request-auth-state` (wire format: site-main channelBridge.ts).\nconst channel = createPushChannel<AuthState>({\n pushType: AUTH_STATE,\n requestType: REQUEST_AUTH_STATE,\n initial: { status: 'unknown', user: null },\n parse: (msg) => (isAuthState(msg.state) ? (msg.state as AuthState) : undefined),\n});\n\n/**\n * Returns the current login / account state. Poll this whenever you need a\n * one-off read; use {@link onAuthChange} or {@link useAuth} to react to changes.\n */\nexport const getAuthState = (): AuthState => channel.get();\n\n/**\n * Subscribe to login / logout changes. The listener is invoked immediately with\n * the current state, then again on every change. Returns an unsubscribe fn.\n */\nexport const onAuthChange = (listener: (state: AuthState) => void): (() => void) =>\n channel.onChange(listener);\n\n/**\n * React hook returning the current login / account state, re-rendering on\n * login / logout.\n */\nexport const useAuth = (): AuthState => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yBAAkC;AAClC,sBAA+C;AAuB/C,MAAM,cAAc,CAAC,MAA+B;AAClD,QAAM,IAAI;AACV,SACE,CAAC,CAAC,MACD,EAAE,WAAW,aAAa,EAAE,WAAW,eAAe,EAAE,WAAW,kBACnE,EAAE,SAAS,QAAS,OAAO,EAAE,SAAS,YAAY,OAAQ,EAAE,KAAqB,UAAU;AAEhG;AAIA,MAAM,cAAU,sCAA6B;AAAA,EAC3C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,EAAE,QAAQ,WAAW,MAAM,KAAK;AAAA,EACzC,OAAO,CAAC,QAAS,YAAY,IAAI,KAAK,IAAK,IAAI,QAAsB;AACvE,CAAC;AAMM,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAMlD,MAAM,eAAe,CAAC,aAC3B,QAAQ,SAAS,QAAQ;AAMpB,MAAM,UAAU,MAAiB,QAAQ,IAAI;","names":[]}
package/dist/auth.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { createPushChannel } from "./pushChannel";
3
+ import { AUTH_STATE, REQUEST_AUTH_STATE } from "./generated/protocol";
3
4
  const isAuthState = (v) => {
4
5
  const s = v;
5
6
  return !!s && (s.status === "unknown" || s.status === "signed-in" || s.status === "signed-out") && (s.user === null || typeof s.user === "object" && typeof s.user.login === "string");
6
7
  };
7
8
  const channel = createPushChannel({
8
- pushType: "auth-state",
9
- requestType: "request-auth-state",
9
+ pushType: AUTH_STATE,
10
+ requestType: REQUEST_AUTH_STATE,
10
11
  initial: { status: "unknown", user: null },
11
12
  parse: (msg) => isAuthState(msg.state) ? msg.state : void 0
12
13
  });
package/dist/auth.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\n\n/**\n * Login / account state of the immediately.run user, mirrored from the host\n * window into the sandbox.\n *\n * `status` is `'unknown'` until the host has reported a value (use it to\n * distinguish \"still loading\" from a confirmed signed-out session).\n */\nexport type AuthStatus = 'unknown' | 'signed-in' | 'signed-out';\n\n/** The signed-in immediately.run user, as seen by the sandbox (no token, ever). */\nexport interface SandboxUser {\n /** GitHub login (handle) of the signed-in user. */\n login: string;\n}\n\n/** The user's login / account state: a `status` plus the `user` when signed in. */\nexport interface AuthState {\n status: AuthStatus;\n user: SandboxUser | null;\n}\n\nconst isAuthState = (v: unknown): v is AuthState => {\n const s = v as Partial<AuthState> | null;\n return (\n !!s &&\n (s.status === 'unknown' || s.status === 'signed-in' || s.status === 'signed-out') &&\n (s.user === null || (typeof s.user === 'object' && typeof (s.user as SandboxUser).login === 'string'))\n );\n};\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `auth-state`\n// and answers `request-auth-state` (wire format: site-main channelBridge.ts).\nconst channel = createPushChannel<AuthState>({\n pushType: 'auth-state',\n requestType: 'request-auth-state',\n initial: { status: 'unknown', user: null },\n parse: (msg) => (isAuthState(msg.state) ? (msg.state as AuthState) : undefined),\n});\n\n/**\n * Returns the current login / account state. Poll this whenever you need a\n * one-off read; use {@link onAuthChange} or {@link useAuth} to react to changes.\n */\nexport const getAuthState = (): AuthState => channel.get();\n\n/**\n * Subscribe to login / logout changes. The listener is invoked immediately with\n * the current state, then again on every change. Returns an unsubscribe fn.\n */\nexport const onAuthChange = (listener: (state: AuthState) => void): (() => void) =>\n channel.onChange(listener);\n\n/**\n * React hook returning the current login / account state, re-rendering on\n * login / logout.\n */\nexport const useAuth = (): AuthState => channel.use();\n"],"mappings":";AAAA,SAAS,yBAAyB;AAuBlC,MAAM,cAAc,CAAC,MAA+B;AAClD,QAAM,IAAI;AACV,SACE,CAAC,CAAC,MACD,EAAE,WAAW,aAAa,EAAE,WAAW,eAAe,EAAE,WAAW,kBACnE,EAAE,SAAS,QAAS,OAAO,EAAE,SAAS,YAAY,OAAQ,EAAE,KAAqB,UAAU;AAEhG;AAIA,MAAM,UAAU,kBAA6B;AAAA,EAC3C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,EAAE,QAAQ,WAAW,MAAM,KAAK;AAAA,EACzC,OAAO,CAAC,QAAS,YAAY,IAAI,KAAK,IAAK,IAAI,QAAsB;AACvE,CAAC;AAMM,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAMlD,MAAM,eAAe,CAAC,aAC3B,QAAQ,SAAS,QAAQ;AAMpB,MAAM,UAAU,MAAiB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/auth.ts"],"sourcesContent":["import { createPushChannel } from './pushChannel';\nimport { AUTH_STATE, REQUEST_AUTH_STATE } from './generated/protocol';\n\n/**\n * Login / account state of the immediately.run user, mirrored from the host\n * window into the sandbox.\n *\n * `status` is `'unknown'` until the host has reported a value (use it to\n * distinguish \"still loading\" from a confirmed signed-out session).\n */\nexport type AuthStatus = 'unknown' | 'signed-in' | 'signed-out';\n\n/** The signed-in immediately.run user, as seen by the sandbox (no token, ever). */\nexport interface SandboxUser {\n /** GitHub login (handle) of the signed-in user. */\n login: string;\n}\n\n/** The user's login / account state: a `status` plus the `user` when signed in. */\nexport interface AuthState {\n status: AuthStatus;\n user: SandboxUser | null;\n}\n\nconst isAuthState = (v: unknown): v is AuthState => {\n const s = v as Partial<AuthState> | null;\n return (\n !!s &&\n (s.status === 'unknown' || s.status === 'signed-in' || s.status === 'signed-out') &&\n (s.user === null || (typeof s.user === 'object' && typeof (s.user as SandboxUser).login === 'string'))\n );\n};\n\n// Read over the transport (SDK_PACKAGING_SPEC §4): the host pushes `auth-state`\n// and answers `request-auth-state` (wire format: site-main channelBridge.ts).\nconst channel = createPushChannel<AuthState>({\n pushType: AUTH_STATE,\n requestType: REQUEST_AUTH_STATE,\n initial: { status: 'unknown', user: null },\n parse: (msg) => (isAuthState(msg.state) ? (msg.state as AuthState) : undefined),\n});\n\n/**\n * Returns the current login / account state. Poll this whenever you need a\n * one-off read; use {@link onAuthChange} or {@link useAuth} to react to changes.\n */\nexport const getAuthState = (): AuthState => channel.get();\n\n/**\n * Subscribe to login / logout changes. The listener is invoked immediately with\n * the current state, then again on every change. Returns an unsubscribe fn.\n */\nexport const onAuthChange = (listener: (state: AuthState) => void): (() => void) =>\n channel.onChange(listener);\n\n/**\n * React hook returning the current login / account state, re-rendering on\n * login / logout.\n */\nexport const useAuth = (): AuthState => channel.use();\n"],"mappings":";AAAA,SAAS,yBAAyB;AAClC,SAAS,YAAY,0BAA0B;AAuB/C,MAAM,cAAc,CAAC,MAA+B;AAClD,QAAM,IAAI;AACV,SACE,CAAC,CAAC,MACD,EAAE,WAAW,aAAa,EAAE,WAAW,eAAe,EAAE,WAAW,kBACnE,EAAE,SAAS,QAAS,OAAO,EAAE,SAAS,YAAY,OAAQ,EAAE,KAAqB,UAAU;AAEhG;AAIA,MAAM,UAAU,kBAA6B;AAAA,EAC3C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,EAAE,QAAQ,WAAW,MAAM,KAAK;AAAA,EACzC,OAAO,CAAC,QAAS,YAAY,IAAI,KAAK,IAAK,IAAI,QAAsB;AACvE,CAAC;AAMM,MAAM,eAAe,MAAiB,QAAQ,IAAI;AAMlD,MAAM,eAAe,CAAC,aAC3B,QAAQ,SAAS,QAAQ;AAMpB,MAAM,UAAU,MAAiB,QAAQ,IAAI;","names":[]}
package/dist/boot.cjs CHANGED
@@ -42,6 +42,7 @@ var import_routing = require("./routing");
42
42
  var import_sandboxUtils = require("./sandboxUtils");
43
43
  var import_TinkerableContext = require("./TinkerableContext");
44
44
  var import_urlUtils = require("./urlUtils");
45
+ var import_protocol = require("./generated/protocol");
45
46
  const resolveMdxComponents = (mdxComponents) => mdxComponents === void 0 ? import_MDXComponents.DEFAULT_MDX_COMPONENTS : typeof mdxComponents === "function" ? mdxComponents(import_MDXComponents.DEFAULT_MDX_COMPONENTS) : { ...import_MDXComponents.DEFAULT_MDX_COMPONENTS, ...mdxComponents };
46
47
  const updateAlreadyApplied = (filesMetadata, update) => {
47
48
  for (let [key, value] of Object.entries(update)) {
@@ -57,7 +58,7 @@ const TinkerableApp = ({
57
58
  }) => {
58
59
  const [context, setContext] = (0, import_react.useState)((0, import_contextUtils.getInitialContext)(routingSpec));
59
60
  (0, import_react.useEffect)(() => {
60
- const removeListener = (0, import_sandboxUtils.addListener)("urlchange", ({ url }) => {
61
+ const removeListener = (0, import_sandboxUtils.addListener)(import_protocol.URLCHANGE, ({ url }) => {
61
62
  setContext((context2) => {
62
63
  const updatedContext = (0, import_contextUtils.updateContext)(context2, url);
63
64
  if (updatedContext !== context2) {
@@ -73,7 +74,7 @@ const TinkerableApp = ({
73
74
  (0, import_react.useEffect)(() => {
74
75
  const source = (0, import_injectedBundler.resolveMetadataSource)((0, import_injectedBundler.getInjectedMetadataEmitter)());
75
76
  const dispose = (0, import_sandboxUtils.addListener)(
76
- "metadata-update",
77
+ import_protocol.METADATA_UPDATE,
77
78
  ({ update }) => {
78
79
  setContext(
79
80
  (prevContext) => updateAlreadyApplied(prevContext.filesMetadata, update) ? prevContext : {
package/dist/boot.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, ReactNode, StrictMode, useEffect, useLayoutEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { emitMarkerOnce } from './markers';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { ScrollAfterNavigation } from './components/ScrollAfterNavigation';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\n\n/** A map of MDX component overrides, or a function that receives the platform\n * {@link DEFAULT_MDX_COMPONENTS} and returns the full map to use. */\nexport type MdxComponents =\n | Record<string, FC>\n | ((defaults: Record<string, FC>) => Record<string, FC>);\n\n/**\n * Resolve the effective MDX component map from a {@link BootProps.mdxComponents}\n * value (MARKDOWN_SYNTAX_SPEC §11.3):\n * - `undefined` → the platform {@link DEFAULT_MDX_COMPONENTS} (same reference).\n * - a **function** → the full-replace escape hatch, handed the defaults.\n * - a **map** → merged *over* the defaults (`{ ...defaults, ...map }`), so\n * overriding one component keeps the rest — the phantom-defaults invariant\n * (§11.2) that stops the MDX missing-reference guard from firing.\n */\nexport const resolveMdxComponents = (mdxComponents?: MdxComponents): Record<string, FC> =>\n mdxComponents === undefined\n ? (DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : typeof mdxComponents === 'function'\n ? mdxComponents(DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : { ...(DEFAULT_MDX_COMPONENTS as Record<string, FC>), ...mdxComponents };\n\n/** Options for {@link boot}: MDX overrides, a route table, or an app root. */\nexport type BootProps = {\n /**\n * MDX component overrides. A **map** is *merged over* the platform defaults\n * ({@link DEFAULT_MDX_COMPONENTS}) — so overriding `WikiLink` alone keeps the\n * default `a` and `Admonition` (MARKDOWN_SYNTAX_SPEC §11.3). Pass a **function**\n * `(defaults) => map` as the full-replace escape hatch when you want complete\n * control over the set.\n */\n mdxComponents?: MdxComponents;\n routingSpec?: RoutingSpec;\n /**\n * App root rendered directly inside the providers (with full navigation\n * context), instead of dispatching through a `routingSpec`. Render\n * `<Routes>`/`<Route>` here for fully dynamic routing — no catch-all rule\n * boilerplate. Takes precedence over `routingSpec` for what is rendered.\n */\n children?: ReactNode;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\n/** The app shell {@link boot} renders: holds navigation state, subscribes to host\n * URL + metadata pushes, and renders `children` or the route `<Router />`. */\nexport const TinkerableApp = ({\n routingSpec,\n children,\n}: {\n routingSpec: RoutingSpec;\n children?: ReactNode;\n}) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener('urlchange', ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n 'metadata-update',\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n {/* Capability C: lands cross-page `#fragment` deep-links on their section\n after the destination tree mounts, uniform for every MDX app (§13.5). */}\n <ScrollAfterNavigation />\n {children ?? <Router />}\n </TinkerableContext>\n );\n};\n\n// Boot marker emitter (LOAD_PROFILING_SPEC §3, R3-46). Rendered at the top of the\n// app tree so its layout effect fires on the FIRST root-render commit: that instant\n// is `ir.fmp` (the content is in the DOM, about to paint) and the baseline for\n// `ir.interactive` (the host treats a forwarded `ir.interactive` as the root-commit\n// signal and resolves `max(commit, reportReady)` — LP2-3 — so this can only ever be\n// delayed by an app's `reportReady()`, never advanced). Emitted in canonical stream\n// order (fmp then interactive); idempotent per name (StrictMode-safe). Renders null.\nconst BootMarkers = (): null => {\n useLayoutEffect(() => {\n emitMarkerOnce('ir.fmp');\n emitMarkerOnce('ir.interactive');\n }, []);\n return null;\n};\n\n/** The default route table when `boot` is called with no `routingSpec`/`children`:\n * `/` → main content, `/files/<path>` → the file router, else not-found.\n * Re-expressed with path templates (SANDBOX_ROUTING_SPEC §7); the file path\n * surfaces under the `*` wildcard. The catch-all stays a raw RegExp — the\n * escape hatch — so it anchors `.+` (a non-empty path) exactly as before. */\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: '/', element: <MainContent /> },\n { name: 'FileRouter', pattern: `${FILES_PREFIX}/*`, element: <FileRouter /> },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, element: <ErrorNotFound /> },\n ],\n};\n\n/**\n * Matches any `sandboxPath` so navigation context can be built without a route\n * table. Used when {@link boot} is given `children` (the app owns dispatch via\n * `<Routes>`); the catch-all's `element` is never rendered (children are).\n */\nexport const CATCH_ALL_ROUTING_SPEC: RoutingSpec = {\n routes: [{ name: 'AppRoot', pattern: /^.*$/, element: null }],\n};\n\n/**\n * Mount an immediately.run app into the sandbox `#root`. The entry point every\n * app calls from `index.tsx`: wires the MDX, module-cache, and navigation\n * providers, then renders the route table (`routingSpec`) or your `children`.\n */\nexport const boot = ({\n mdxComponents,\n routingSpec,\n children,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const resolvedComponents = resolveMdxComponents(mdxComponents);\n // `children` own dispatch, so a catch-all keeps navigation working without a\n // table; otherwise fall back to the default file/main-content routes.\n const spec = routingSpec ?? (children ? CATCH_ALL_ROUTING_SPEC : DEFAULT_ROUTING_SPEC);\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={resolvedComponents}>\n <BootMarkers />\n <TinkerableApp routingSpec={spec}>{children}</TinkerableApp>\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":"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","names":["context"]}
1
+ {"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, ReactNode, StrictMode, useEffect, useLayoutEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { emitMarkerOnce } from './markers';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { ScrollAfterNavigation } from './components/ScrollAfterNavigation';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\nimport { METADATA_UPDATE, URLCHANGE } from './generated/protocol';\n\n/** A map of MDX component overrides, or a function that receives the platform\n * {@link DEFAULT_MDX_COMPONENTS} and returns the full map to use. */\nexport type MdxComponents =\n | Record<string, FC>\n | ((defaults: Record<string, FC>) => Record<string, FC>);\n\n/**\n * Resolve the effective MDX component map from a {@link BootProps.mdxComponents}\n * value (MARKDOWN_SYNTAX_SPEC §11.3):\n * - `undefined` → the platform {@link DEFAULT_MDX_COMPONENTS} (same reference).\n * - a **function** → the full-replace escape hatch, handed the defaults.\n * - a **map** → merged *over* the defaults (`{ ...defaults, ...map }`), so\n * overriding one component keeps the rest — the phantom-defaults invariant\n * (§11.2) that stops the MDX missing-reference guard from firing.\n */\nexport const resolveMdxComponents = (mdxComponents?: MdxComponents): Record<string, FC> =>\n mdxComponents === undefined\n ? (DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : typeof mdxComponents === 'function'\n ? mdxComponents(DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : { ...(DEFAULT_MDX_COMPONENTS as Record<string, FC>), ...mdxComponents };\n\n/** Options for {@link boot}: MDX overrides, a route table, or an app root. */\nexport type BootProps = {\n /**\n * MDX component overrides. A **map** is *merged over* the platform defaults\n * ({@link DEFAULT_MDX_COMPONENTS}) — so overriding `WikiLink` alone keeps the\n * default `a` and `Admonition` (MARKDOWN_SYNTAX_SPEC §11.3). Pass a **function**\n * `(defaults) => map` as the full-replace escape hatch when you want complete\n * control over the set.\n */\n mdxComponents?: MdxComponents;\n routingSpec?: RoutingSpec;\n /**\n * App root rendered directly inside the providers (with full navigation\n * context), instead of dispatching through a `routingSpec`. Render\n * `<Routes>`/`<Route>` here for fully dynamic routing — no catch-all rule\n * boilerplate. Takes precedence over `routingSpec` for what is rendered.\n */\n children?: ReactNode;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\n/** The app shell {@link boot} renders: holds navigation state, subscribes to host\n * URL + metadata pushes, and renders `children` or the route `<Router />`. */\nexport const TinkerableApp = ({\n routingSpec,\n children,\n}: {\n routingSpec: RoutingSpec;\n children?: ReactNode;\n}) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener(URLCHANGE, ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n METADATA_UPDATE,\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n {/* Capability C: lands cross-page `#fragment` deep-links on their section\n after the destination tree mounts, uniform for every MDX app (§13.5). */}\n <ScrollAfterNavigation />\n {children ?? <Router />}\n </TinkerableContext>\n );\n};\n\n// Boot marker emitter (LOAD_PROFILING_SPEC §3, R3-46). Rendered at the top of the\n// app tree so its layout effect fires on the FIRST root-render commit: that instant\n// is `ir.fmp` (the content is in the DOM, about to paint) and the baseline for\n// `ir.interactive` (the host treats a forwarded `ir.interactive` as the root-commit\n// signal and resolves `max(commit, reportReady)` — LP2-3 — so this can only ever be\n// delayed by an app's `reportReady()`, never advanced). Emitted in canonical stream\n// order (fmp then interactive); idempotent per name (StrictMode-safe). Renders null.\nconst BootMarkers = (): null => {\n useLayoutEffect(() => {\n emitMarkerOnce('ir.fmp');\n emitMarkerOnce('ir.interactive');\n }, []);\n return null;\n};\n\n/** The default route table when `boot` is called with no `routingSpec`/`children`:\n * `/` → main content, `/files/<path>` → the file router, else not-found.\n * Re-expressed with path templates (SANDBOX_ROUTING_SPEC §7); the file path\n * surfaces under the `*` wildcard. The catch-all stays a raw RegExp — the\n * escape hatch — so it anchors `.+` (a non-empty path) exactly as before. */\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: '/', element: <MainContent /> },\n { name: 'FileRouter', pattern: `${FILES_PREFIX}/*`, element: <FileRouter /> },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, element: <ErrorNotFound /> },\n ],\n};\n\n/**\n * Matches any `sandboxPath` so navigation context can be built without a route\n * table. Used when {@link boot} is given `children` (the app owns dispatch via\n * `<Routes>`); the catch-all's `element` is never rendered (children are).\n */\nexport const CATCH_ALL_ROUTING_SPEC: RoutingSpec = {\n routes: [{ name: 'AppRoot', pattern: /^.*$/, element: null }],\n};\n\n/**\n * Mount an immediately.run app into the sandbox `#root`. The entry point every\n * app calls from `index.tsx`: wires the MDX, module-cache, and navigation\n * providers, then renders the route table (`routingSpec`) or your `children`.\n */\nexport const boot = ({\n mdxComponents,\n routingSpec,\n children,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const resolvedComponents = resolveMdxComponents(mdxComponents);\n // `children` own dispatch, so a catch-all keeps navigation working without a\n // table; otherwise fall back to the default file/main-content routes.\n const spec = routingSpec ?? (children ? CATCH_ALL_ROUTING_SPEC : DEFAULT_ROUTING_SPEC);\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={resolvedComponents}>\n <BootMarkers />\n <TinkerableApp routingSpec={spec}>{children}</TinkerableApp>\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8HI;AA9HJ,mBAAgF;AAChF,oBAA2B;AAE3B,qBAA+B;AAE/B,oBAA8B;AAC9B,wBAA2B;AAC3B,yBAA4B;AAC5B,2BAAuC;AACvC,mCAAsC;AACtC,0BAAiD;AACjD,6BAAkE;AAClE,yBAA4B;AAC5B,yBAAwD;AACxD,qBAAuB;AAGvB,0BAA4B;AAC5B,+BAAmD;AACnD,sBAA6B;AAC7B,sBAA2C;AAiBpC,MAAM,uBAAuB,CAAC,kBACnC,kBAAkB,SACb,8CACD,OAAO,kBAAkB,aACvB,cAAc,2CAA4C,IAC1D,EAAE,GAAI,6CAA+C,GAAG,cAAc;AAsB9E,MAAM,uBAAuB,CAAC,eAA8B,WAA0B;AACpF,WAAS,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC/C,QAAI,cAAc,GAAG,MAAM,OAAO;AAChC,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAIO,MAAM,gBAAgB,CAAC;AAAA,EAC5B;AAAA,EACA;AACF,MAGM;AACJ,QAAM,CAAC,SAAS,UAAU,QAAI,2BAA0B,uCAAkB,WAAW,CAAC;AACtF,8BAAU,MAAM;AACd,UAAM,qBAAiB,iCAAY,2BAAW,CAAC,EAAE,IAAI,MAAM;AACzD,iBAAW,CAACA,aAAY;AACtB,cAAM,qBAAiB,mCAAcA,UAAS,GAAG;AACjD,YAAI,mBAAmBA,UAAS;AAC9B,kBAAQ;AAAA,YACN,gCAAgCA,SAAQ,gBAAgB,WAAW,OAAO,eAAe,gBAAgB,WAAW;AAAA,UACtH;AAAA,QACF;AACA,eAAO;AAAA,MACT,CAAC;AAAA,IACH,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AACf,8BAAU,MAAM;AAKd,UAAM,aAAS,kDAAsB,mDAA2B,CAAC;AACjE,UAAM,cAAU;AAAA,MACd;AAAA,MACA,CAAC,EAAE,OAAO,MAA2B;AACnC;AAAA,UAAW,CAAC,gBACV,qBAAqB,YAAY,eAAe,MAAM,IAClD,cACA;AAAA,YACE,GAAG;AAAA,YACH,eAAe;AAAA;AAAA,cAEb,GAAG,YAAY;AAAA,cACf,GAAG;AAAA,YACL;AAAA,UACF;AAAA,QACN;AAAA,MACF;AAAA,MACA,OAAO;AAAA,IACT;AACA,WAAO,OAAO;AACd,WAAO;AAAA,EACT,GAAG,CAAC,UAAU,CAAC;AAEf,SACE,6CAAC,8CAAkB,OAAO,SAGxB;AAAA,gDAAC,sDAAsB;AAAA,IACtB,YAAY,4CAAC,yBAAO;AAAA,KACvB;AAEJ;AASA,MAAM,cAAc,MAAY;AAC9B,oCAAgB,MAAM;AACpB,uCAAe,QAAQ;AACvB,uCAAe,gBAAgB;AAAA,EACjC,GAAG,CAAC,CAAC;AACL,SAAO;AACT;AAOO,MAAM,uBAAoC;AAAA,EAC/C,QAAQ;AAAA,IACN,EAAE,MAAM,eAAe,SAAS,KAAK,SAAS,4CAAC,kCAAY,EAAG;AAAA,IAC9D,EAAE,MAAM,cAAc,SAAS,GAAG,4BAAY,MAAM,SAAS,4CAAC,gCAAW,EAAG;AAAA,IAC5E,EAAE,MAAM,iBAAiB,SAAS,iBAAiB,SAAS,4CAAC,+BAAc,EAAG;AAAA,EAChF;AACF;AAOO,MAAM,yBAAsC;AAAA,EACjD,QAAQ,CAAC,EAAE,MAAM,WAAW,SAAS,QAAQ,SAAS,KAAK,CAAC;AAC9D;AAOO,MAAM,OAAO,CAAC;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AACF,IAAe,CAAC,MAAM;AACpB,QAAM,cAAc,SAAS,eAAe,MAAM;AAClD,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AACA,QAAM,qBAAqB,qBAAqB,aAAa;AAG7D,QAAM,OAAO,gBAAgB,WAAW,yBAAyB;AACjE,QAAM,cAAc,IAAI,+BAAY;AACpC,QAAM,WAAO,0BAAW,WAAW;AACnC,OAAK;AAAA,IACH,4CAAC,2BACC,sDAAC,iDAA2B,aAC1B,uDAAC,kCAAY,YAAY,oBACvB;AAAA,kDAAC,eAAY;AAAA,MACb,4CAAC,iBAAc,aAAa,MAAO,UAAS;AAAA,OAC9C,GACF,GACF;AAAA,EACF;AACF;","names":["context"]}
package/dist/boot.js CHANGED
@@ -16,6 +16,7 @@ import { Router } from "./routing";
16
16
  import { addListener } from "./sandboxUtils";
17
17
  import { TinkerableContext } from "./TinkerableContext";
18
18
  import { FILES_PREFIX } from "./urlUtils";
19
+ import { METADATA_UPDATE, URLCHANGE } from "./generated/protocol";
19
20
  const resolveMdxComponents = (mdxComponents) => mdxComponents === void 0 ? DEFAULT_MDX_COMPONENTS : typeof mdxComponents === "function" ? mdxComponents(DEFAULT_MDX_COMPONENTS) : { ...DEFAULT_MDX_COMPONENTS, ...mdxComponents };
20
21
  const updateAlreadyApplied = (filesMetadata, update) => {
21
22
  for (let [key, value] of Object.entries(update)) {
@@ -31,7 +32,7 @@ const TinkerableApp = ({
31
32
  }) => {
32
33
  const [context, setContext] = useState(getInitialContext(routingSpec));
33
34
  useEffect(() => {
34
- const removeListener = addListener("urlchange", ({ url }) => {
35
+ const removeListener = addListener(URLCHANGE, ({ url }) => {
35
36
  setContext((context2) => {
36
37
  const updatedContext = updateContext(context2, url);
37
38
  if (updatedContext !== context2) {
@@ -47,7 +48,7 @@ const TinkerableApp = ({
47
48
  useEffect(() => {
48
49
  const source = resolveMetadataSource(getInjectedMetadataEmitter());
49
50
  const dispose = addListener(
50
- "metadata-update",
51
+ METADATA_UPDATE,
51
52
  ({ update }) => {
52
53
  setContext(
53
54
  (prevContext) => updateAlreadyApplied(prevContext.filesMetadata, update) ? prevContext : {
package/dist/boot.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, ReactNode, StrictMode, useEffect, useLayoutEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { emitMarkerOnce } from './markers';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { ScrollAfterNavigation } from './components/ScrollAfterNavigation';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\n\n/** A map of MDX component overrides, or a function that receives the platform\n * {@link DEFAULT_MDX_COMPONENTS} and returns the full map to use. */\nexport type MdxComponents =\n | Record<string, FC>\n | ((defaults: Record<string, FC>) => Record<string, FC>);\n\n/**\n * Resolve the effective MDX component map from a {@link BootProps.mdxComponents}\n * value (MARKDOWN_SYNTAX_SPEC §11.3):\n * - `undefined` → the platform {@link DEFAULT_MDX_COMPONENTS} (same reference).\n * - a **function** → the full-replace escape hatch, handed the defaults.\n * - a **map** → merged *over* the defaults (`{ ...defaults, ...map }`), so\n * overriding one component keeps the rest — the phantom-defaults invariant\n * (§11.2) that stops the MDX missing-reference guard from firing.\n */\nexport const resolveMdxComponents = (mdxComponents?: MdxComponents): Record<string, FC> =>\n mdxComponents === undefined\n ? (DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : typeof mdxComponents === 'function'\n ? mdxComponents(DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : { ...(DEFAULT_MDX_COMPONENTS as Record<string, FC>), ...mdxComponents };\n\n/** Options for {@link boot}: MDX overrides, a route table, or an app root. */\nexport type BootProps = {\n /**\n * MDX component overrides. A **map** is *merged over* the platform defaults\n * ({@link DEFAULT_MDX_COMPONENTS}) — so overriding `WikiLink` alone keeps the\n * default `a` and `Admonition` (MARKDOWN_SYNTAX_SPEC §11.3). Pass a **function**\n * `(defaults) => map` as the full-replace escape hatch when you want complete\n * control over the set.\n */\n mdxComponents?: MdxComponents;\n routingSpec?: RoutingSpec;\n /**\n * App root rendered directly inside the providers (with full navigation\n * context), instead of dispatching through a `routingSpec`. Render\n * `<Routes>`/`<Route>` here for fully dynamic routing — no catch-all rule\n * boilerplate. Takes precedence over `routingSpec` for what is rendered.\n */\n children?: ReactNode;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\n/** The app shell {@link boot} renders: holds navigation state, subscribes to host\n * URL + metadata pushes, and renders `children` or the route `<Router />`. */\nexport const TinkerableApp = ({\n routingSpec,\n children,\n}: {\n routingSpec: RoutingSpec;\n children?: ReactNode;\n}) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener('urlchange', ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n 'metadata-update',\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n {/* Capability C: lands cross-page `#fragment` deep-links on their section\n after the destination tree mounts, uniform for every MDX app (§13.5). */}\n <ScrollAfterNavigation />\n {children ?? <Router />}\n </TinkerableContext>\n );\n};\n\n// Boot marker emitter (LOAD_PROFILING_SPEC §3, R3-46). Rendered at the top of the\n// app tree so its layout effect fires on the FIRST root-render commit: that instant\n// is `ir.fmp` (the content is in the DOM, about to paint) and the baseline for\n// `ir.interactive` (the host treats a forwarded `ir.interactive` as the root-commit\n// signal and resolves `max(commit, reportReady)` — LP2-3 — so this can only ever be\n// delayed by an app's `reportReady()`, never advanced). Emitted in canonical stream\n// order (fmp then interactive); idempotent per name (StrictMode-safe). Renders null.\nconst BootMarkers = (): null => {\n useLayoutEffect(() => {\n emitMarkerOnce('ir.fmp');\n emitMarkerOnce('ir.interactive');\n }, []);\n return null;\n};\n\n/** The default route table when `boot` is called with no `routingSpec`/`children`:\n * `/` → main content, `/files/<path>` → the file router, else not-found.\n * Re-expressed with path templates (SANDBOX_ROUTING_SPEC §7); the file path\n * surfaces under the `*` wildcard. The catch-all stays a raw RegExp — the\n * escape hatch — so it anchors `.+` (a non-empty path) exactly as before. */\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: '/', element: <MainContent /> },\n { name: 'FileRouter', pattern: `${FILES_PREFIX}/*`, element: <FileRouter /> },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, element: <ErrorNotFound /> },\n ],\n};\n\n/**\n * Matches any `sandboxPath` so navigation context can be built without a route\n * table. Used when {@link boot} is given `children` (the app owns dispatch via\n * `<Routes>`); the catch-all's `element` is never rendered (children are).\n */\nexport const CATCH_ALL_ROUTING_SPEC: RoutingSpec = {\n routes: [{ name: 'AppRoot', pattern: /^.*$/, element: null }],\n};\n\n/**\n * Mount an immediately.run app into the sandbox `#root`. The entry point every\n * app calls from `index.tsx`: wires the MDX, module-cache, and navigation\n * providers, then renders the route table (`routingSpec`) or your `children`.\n */\nexport const boot = ({\n mdxComponents,\n routingSpec,\n children,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const resolvedComponents = resolveMdxComponents(mdxComponents);\n // `children` own dispatch, so a catch-all keeps navigation working without a\n // table; otherwise fall back to the default file/main-content routes.\n const spec = routingSpec ?? (children ? CATCH_ALL_ROUTING_SPEC : DEFAULT_ROUTING_SPEC);\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={resolvedComponents}>\n <BootMarkers />\n <TinkerableApp routingSpec={spec}>{children}</TinkerableApp>\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":"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","names":["context"]}
1
+ {"version":3,"sources":["../src/boot.tsx"],"sourcesContent":["import { FC, ReactNode, StrictMode, useEffect, useLayoutEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\nimport { emitMarkerOnce } from './markers';\n\nimport { ErrorNotFound } from './components/errors';\nimport { FileRouter } from './components/FileRouter';\nimport { MainContent } from './components/MainContent';\nimport { DEFAULT_MDX_COMPONENTS } from './components/MDXComponents';\nimport { ScrollAfterNavigation } from './components/ScrollAfterNavigation';\nimport { getInitialContext, updateContext } from './contextUtils';\nimport { getInjectedMetadataEmitter, resolveMetadataSource } from './injectedBundler';\nimport { MDXProvider } from './MDXProvider';\nimport { ModuleCache, ModuleCacheContextProvider } from './moduleCache';\nimport { Router } from './routing';\nimport type { RoutingSpec } from './RoutingSpec';\nimport { FilesMetadata } from './sandboxTypes';\nimport { addListener } from './sandboxUtils';\nimport { TinkerableContext, TinkerableState } from './TinkerableContext';\nimport { FILES_PREFIX } from './urlUtils';\nimport { METADATA_UPDATE, URLCHANGE } from './generated/protocol';\n\n/** A map of MDX component overrides, or a function that receives the platform\n * {@link DEFAULT_MDX_COMPONENTS} and returns the full map to use. */\nexport type MdxComponents =\n | Record<string, FC>\n | ((defaults: Record<string, FC>) => Record<string, FC>);\n\n/**\n * Resolve the effective MDX component map from a {@link BootProps.mdxComponents}\n * value (MARKDOWN_SYNTAX_SPEC §11.3):\n * - `undefined` → the platform {@link DEFAULT_MDX_COMPONENTS} (same reference).\n * - a **function** → the full-replace escape hatch, handed the defaults.\n * - a **map** → merged *over* the defaults (`{ ...defaults, ...map }`), so\n * overriding one component keeps the rest — the phantom-defaults invariant\n * (§11.2) that stops the MDX missing-reference guard from firing.\n */\nexport const resolveMdxComponents = (mdxComponents?: MdxComponents): Record<string, FC> =>\n mdxComponents === undefined\n ? (DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : typeof mdxComponents === 'function'\n ? mdxComponents(DEFAULT_MDX_COMPONENTS as Record<string, FC>)\n : { ...(DEFAULT_MDX_COMPONENTS as Record<string, FC>), ...mdxComponents };\n\n/** Options for {@link boot}: MDX overrides, a route table, or an app root. */\nexport type BootProps = {\n /**\n * MDX component overrides. A **map** is *merged over* the platform defaults\n * ({@link DEFAULT_MDX_COMPONENTS}) — so overriding `WikiLink` alone keeps the\n * default `a` and `Admonition` (MARKDOWN_SYNTAX_SPEC §11.3). Pass a **function**\n * `(defaults) => map` as the full-replace escape hatch when you want complete\n * control over the set.\n */\n mdxComponents?: MdxComponents;\n routingSpec?: RoutingSpec;\n /**\n * App root rendered directly inside the providers (with full navigation\n * context), instead of dispatching through a `routingSpec`. Render\n * `<Routes>`/`<Route>` here for fully dynamic routing — no catch-all rule\n * boilerplate. Takes precedence over `routingSpec` for what is rendered.\n */\n children?: ReactNode;\n};\n\nconst updateAlreadyApplied = (filesMetadata: FilesMetadata, update: FilesMetadata) => {\n for (let [key, value] of Object.entries(update)) {\n if (filesMetadata[key] !== value) {\n return false;\n }\n }\n return true;\n};\n\n/** The app shell {@link boot} renders: holds navigation state, subscribes to host\n * URL + metadata pushes, and renders `children` or the route `<Router />`. */\nexport const TinkerableApp = ({\n routingSpec,\n children,\n}: {\n routingSpec: RoutingSpec;\n children?: ReactNode;\n}) => {\n const [context, setContext] = useState<TinkerableState>(getInitialContext(routingSpec));\n useEffect(() => {\n const removeListener = addListener(URLCHANGE, ({ url }) => {\n setContext((context) => {\n const updatedContext = updateContext(context, url);\n if (updatedContext !== context) {\n console.log(\n `[Sandbox] Updating path from ${context.navigationState.sandboxPath} to ${updatedContext.navigationState.sandboxPath}`\n );\n }\n return updatedContext;\n });\n });\n return removeListener;\n }, [setContext]);\n useEffect(() => {\n // Phase 5 dual-mode (SDK_PACKAGING_SPEC §4/§8): prefer the injected bundler's\n // metadata emitter (the live path, byte-identical); when the SDK is npm-fetched\n // with no injection, `event` is undefined so `addListener` receives\n // 'metadata-update' over the §4 transport instead, and `enable` is a no-op.\n const source = resolveMetadataSource(getInjectedMetadataEmitter());\n const dispose = addListener(\n METADATA_UPDATE,\n ({ update }: Record<string, any>) => {\n setContext((prevContext) =>\n updateAlreadyApplied(prevContext.filesMetadata, update)\n ? prevContext\n : {\n ...prevContext,\n filesMetadata: {\n // TODO: file deletion!\n ...prevContext.filesMetadata,\n ...update,\n },\n }\n );\n },\n source.event\n );\n source.enable();\n return dispose;\n }, [setContext]);\n\n return (\n <TinkerableContext value={context}>\n {/* Capability C: lands cross-page `#fragment` deep-links on their section\n after the destination tree mounts, uniform for every MDX app (§13.5). */}\n <ScrollAfterNavigation />\n {children ?? <Router />}\n </TinkerableContext>\n );\n};\n\n// Boot marker emitter (LOAD_PROFILING_SPEC §3, R3-46). Rendered at the top of the\n// app tree so its layout effect fires on the FIRST root-render commit: that instant\n// is `ir.fmp` (the content is in the DOM, about to paint) and the baseline for\n// `ir.interactive` (the host treats a forwarded `ir.interactive` as the root-commit\n// signal and resolves `max(commit, reportReady)` — LP2-3 — so this can only ever be\n// delayed by an app's `reportReady()`, never advanced). Emitted in canonical stream\n// order (fmp then interactive); idempotent per name (StrictMode-safe). Renders null.\nconst BootMarkers = (): null => {\n useLayoutEffect(() => {\n emitMarkerOnce('ir.fmp');\n emitMarkerOnce('ir.interactive');\n }, []);\n return null;\n};\n\n/** The default route table when `boot` is called with no `routingSpec`/`children`:\n * `/` → main content, `/files/<path>` → the file router, else not-found.\n * Re-expressed with path templates (SANDBOX_ROUTING_SPEC §7); the file path\n * surfaces under the `*` wildcard. The catch-all stays a raw RegExp — the\n * escape hatch — so it anchors `.+` (a non-empty path) exactly as before. */\nexport const DEFAULT_ROUTING_SPEC: RoutingSpec = {\n routes: [\n { name: 'MainContent', pattern: '/', element: <MainContent /> },\n { name: 'FileRouter', pattern: `${FILES_PREFIX}/*`, element: <FileRouter /> },\n { name: 'ErrorNotFound', pattern: /^(?<path>.+)$/, element: <ErrorNotFound /> },\n ],\n};\n\n/**\n * Matches any `sandboxPath` so navigation context can be built without a route\n * table. Used when {@link boot} is given `children` (the app owns dispatch via\n * `<Routes>`); the catch-all's `element` is never rendered (children are).\n */\nexport const CATCH_ALL_ROUTING_SPEC: RoutingSpec = {\n routes: [{ name: 'AppRoot', pattern: /^.*$/, element: null }],\n};\n\n/**\n * Mount an immediately.run app into the sandbox `#root`. The entry point every\n * app calls from `index.tsx`: wires the MDX, module-cache, and navigation\n * providers, then renders the route table (`routingSpec`) or your `children`.\n */\nexport const boot = ({\n mdxComponents,\n routingSpec,\n children,\n}: BootProps = {}) => {\n const rootElement = document.getElementById('root');\n if (!rootElement) {\n throw new Error('boot requires root HTML element to exist');\n }\n const resolvedComponents = resolveMdxComponents(mdxComponents);\n // `children` own dispatch, so a catch-all keeps navigation working without a\n // table; otherwise fall back to the default file/main-content routes.\n const spec = routingSpec ?? (children ? CATCH_ALL_ROUTING_SPEC : DEFAULT_ROUTING_SPEC);\n const moduleCache = new ModuleCache();\n const root = createRoot(rootElement);\n root.render(\n <StrictMode>\n <ModuleCacheContextProvider moduleCache={moduleCache}>\n <MDXProvider components={resolvedComponents}>\n <BootMarkers />\n <TinkerableApp routingSpec={spec}>{children}</TinkerableApp>\n </MDXProvider>\n </ModuleCacheContextProvider>\n </StrictMode>\n );\n};\n"],"mappings":"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","names":["context"]}
package/dist/catalog.cjs CHANGED
@@ -28,6 +28,7 @@ module.exports = __toCommonJS(catalog_exports);
28
28
  var import_sandboxUtils = require("./sandboxUtils");
29
29
  var import_protocolStream = require("./protocolStream");
30
30
  var import_pushChannel = require("./pushChannel");
31
+ var import_protocol = require("./generated/protocol");
31
32
  const split = (name) => {
32
33
  const i = name.indexOf(":");
33
34
  if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);
@@ -55,8 +56,8 @@ function invokeStream(name, params = {}, signal) {
55
56
  return (0, import_protocolStream.consumeStream)(streamTransport, `protocol-${scheme}`, method, [params], void 0, signal);
56
57
  }
57
58
  const channel = (0, import_pushChannel.createPushChannel)({
58
- pushType: "api-catalog",
59
- requestType: "request-api-catalog",
59
+ pushType: import_protocol.API_CATALOG,
60
+ requestType: import_protocol.REQUEST_API_CATALOG,
60
61
  initial: [],
61
62
  parse: (msg) => Array.isArray(msg.methods) ? msg.methods : void 0
62
63
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:invite`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:invite', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it\n // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to\n * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n signal?: AbortSignal,\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params], undefined, signal);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: 'api-catalog',\n requestType: 'request-api-catalog',\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,0BAA0D;AAE1D,4BAA8B;AAC9B,yBAAkC;AAqBlC,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,UAAM,qCAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,gBAChB,iCAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA;AAAA;AAAA,EAGrF,QAAQ,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAClF;AAIO,SAAS,aACd,MACA,SAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,aAAO,qCAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAW,MAAM;AACvG;AAKA,MAAM,cAAU,sCAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\nimport { API_CATALOG, REQUEST_API_CATALOG } from './generated/protocol';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:invite`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:invite', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it\n // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to\n * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n signal?: AbortSignal,\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params], undefined, signal);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: API_CATALOG,\n requestType: REQUEST_API_CATALOG,\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,0BAA0D;AAE1D,4BAA8B;AAC9B,yBAAkC;AAClC,sBAAiD;AAqBjD,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,UAAM,qCAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,gBAChB,iCAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA;AAAA;AAAA,EAGrF,QAAQ,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAClF;AAIO,SAAS,aACd,MACA,SAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,aAAO,qCAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAW,MAAM;AACvG;AAKA,MAAM,cAAU,sCAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
package/dist/catalog.js CHANGED
@@ -2,6 +2,7 @@ import "./chunk-VHAA22YE.js";
2
2
  import { protocolRequest, sendMessage, addListener } from "./sandboxUtils";
3
3
  import { consumeStream } from "./protocolStream";
4
4
  import { createPushChannel } from "./pushChannel";
5
+ import { API_CATALOG, REQUEST_API_CATALOG } from "./generated/protocol";
5
6
  const split = (name) => {
6
7
  const i = name.indexOf(":");
7
8
  if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);
@@ -29,8 +30,8 @@ function invokeStream(name, params = {}, signal) {
29
30
  return consumeStream(streamTransport, `protocol-${scheme}`, method, [params], void 0, signal);
30
31
  }
31
32
  const channel = createPushChannel({
32
- pushType: "api-catalog",
33
- requestType: "request-api-catalog",
33
+ pushType: API_CATALOG,
34
+ requestType: REQUEST_API_CATALOG,
34
35
  initial: [],
35
36
  parse: (msg) => Array.isArray(msg.methods) ? msg.methods : void 0
36
37
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:invite`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:invite', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it\n // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to\n * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n signal?: AbortSignal,\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params], undefined, signal);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: 'api-catalog',\n requestType: 'request-api-catalog',\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":";AAKA,SAAS,iBAAiB,aAAa,mBAAmB;AAE1D,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAqBlC,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,MAAM,gBAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA;AAAA;AAAA,EAGrF,QAAQ,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAClF;AAIO,SAAS,aACd,MACA,SAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,SAAO,cAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAW,MAAM;AACvG;AAKA,MAAM,UAAU,kBAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/catalog.ts"],"sourcesContent":["// The method catalog (UI_AS_APPS_SPEC §5.5) — the app's own grant-filtered RPC\n// surface, and a generic way to call it. The host advertises exactly the methods\n// this app may invoke (MCP-tool-shaped); `invoke()` calls one by its catalog name.\n// Handing the catalog to an embedded agent as its tool list confines the agent to\n// the app's authority (agent sandboxing falls out of the capability model, §5.9).\nimport { protocolRequest, sendMessage, addListener } from './sandboxUtils';\nimport type { StreamFrame, StreamTransport } from './protocolStream';\nimport { consumeStream } from './protocolStream';\nimport { createPushChannel } from './pushChannel';\nimport { API_CATALOG, REQUEST_API_CATALOG } from './generated/protocol';\n\n/** One advertised method, as the host generated it from its gate table. */\nexport interface ApiMethod {\n /** Catalog name, `protocol-` stripped — e.g. `spaces:invite`, `contribute:run`. */\n name: string;\n /** The capability this method requires (already held — it's in your catalog). */\n capability: string;\n /** True when the method STREAMS (use {@link invokeStream}) vs. single-reply. */\n stream?: boolean;\n /**\n * JSON Schema for the method's single object argument, when the host declares one.\n * Self-describes the call so a catalog-as-tools bridge (an embedded agent) can\n * advertise the real param shape — e.g. that `authoring:typecheck` takes a nested\n * `{ files: [{ path, content }] }` array — instead of a permissive \"any object\".\n * Absent for methods the host advertises without a schema.\n */\n paramsSchema?: Record<string, unknown>;\n}\n\n// `scheme:method` → ['scheme', 'method'] (the wire protocol is `protocol-scheme`).\nconst split = (name: string): [string, string] => {\n const i = name.indexOf(':');\n if (i <= 0) throw new Error(`invalid catalog method name: ${name}`);\n return [name.slice(0, i), name.slice(i + 1)];\n};\n\n/**\n * Call a catalog method by name — `invoke('spaces:invite', { spaceId, login, role })`.\n * A thin generic over the host protocol: the host validates params and gates the\n * call (an un-granted method → `forbidden`, even if you name it directly). For a\n * STREAMING method (`ApiMethod.stream`), use {@link invokeStream}.\n */\nexport const invoke = async <T = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n): Promise<T> => {\n const [scheme, method] = split(name);\n // The host replies with an `{ ok, data } | { ok:false, code }` envelope; unwrap\n // it and THROW on refusal (a `.code` like `forbidden` for an off-catalog call)\n // so callers — and any agent driving `invoke` — see the gate's verdict.\n const res = (await protocolRequest(scheme, method, [params])) as\n | { ok: true; data: unknown }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? `${name} failed`) as Error & { code?: string };\n err.code = res?.code ?? 'unknown';\n throw err;\n }\n return res.data as T;\n};\n\n// Stream transport over the resolver (SDK_PACKAGING_SPEC §4) — sendMessage /\n// addListener route through `transport()` (injected bundler messageBus or the §4\n// global), never `bundler.messageBus` directly.\nconst streamTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n // Early-cancel: route a `{type, msgId, cancel:true}` frame back to the host so it\n // aborts the in-flight generation (and, for `llm:chat`, stops billing) — §3.3.\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/** Call a STREAMING catalog method by name, yielding its events. Pass `signal` to\n * abort mid-stream: the host stops generating and (for `llm:chat`) stops billing. */\nexport function invokeStream<T = unknown, R = unknown>(\n name: string,\n params: Record<string, unknown> = {},\n signal?: AbortSignal,\n): AsyncGenerator<T, R, void> {\n const [scheme, method] = split(name);\n return consumeStream<T, R>(streamTransport, `protocol-${scheme}`, method, [params], undefined, signal);\n}\n\n// The catalog list is read over the transport (§4): the host pushes `api-catalog`\n// and answers `request-api-catalog` with this app's grant-filtered methods (wire\n// format: site-main channelBridge.ts).\nconst channel = createPushChannel<ApiMethod[]>({\n pushType: API_CATALOG,\n requestType: REQUEST_API_CATALOG,\n initial: [],\n parse: (msg) => (Array.isArray(msg.methods) ? (msg.methods as ApiMethod[]) : undefined),\n});\n\n/** The methods this app may call (grant-filtered, §5.5). Poll for a one-off read;\n * use {@link onCatalogChange} / {@link useCatalog} to react. */\nexport const getCatalog = (): ApiMethod[] => channel.get();\n\n/** Subscribe to catalog changes (e.g. a grant added/revoked). Invoked immediately\n * with the current catalog, then on every change. Returns an unsubscribe fn. */\nexport const onCatalogChange = (listener: (catalog: ApiMethod[]) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook returning this app's method catalog, re-rendering on change. Hand\n * it to an embedded agent as its tool list to confine the agent to the app's\n * authority (§5.9). */\nexport const useCatalog = (): ApiMethod[] => channel.use();\n"],"mappings":";AAKA,SAAS,iBAAiB,aAAa,mBAAmB;AAE1D,SAAS,qBAAqB;AAC9B,SAAS,yBAAyB;AAClC,SAAS,aAAa,2BAA2B;AAqBjD,MAAM,QAAQ,CAAC,SAAmC;AAChD,QAAM,IAAI,KAAK,QAAQ,GAAG;AAC1B,MAAI,KAAK,EAAG,OAAM,IAAI,MAAM,gCAAgC,IAAI,EAAE;AAClE,SAAO,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,KAAK,MAAM,IAAI,CAAC,CAAC;AAC7C;AAQO,MAAM,SAAS,OACpB,MACA,SAAkC,CAAC,MACpB;AACf,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AAInC,QAAM,MAAO,MAAM,gBAAgB,QAAQ,QAAQ,CAAC,MAAM,CAAC;AAI3D,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,GAAG,IAAI,SAAS;AACtD,QAAI,OAAO,KAAK,QAAQ;AACxB,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAKA,MAAM,kBAAmC;AAAA,EACvC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA;AAAA;AAAA,EAGrF,QAAQ,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAClF;AAIO,SAAS,aACd,MACA,SAAkC,CAAC,GACnC,QAC4B;AAC5B,QAAM,CAAC,QAAQ,MAAM,IAAI,MAAM,IAAI;AACnC,SAAO,cAAoB,iBAAiB,YAAY,MAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,QAAW,MAAM;AACvG;AAKA,MAAM,UAAU,kBAA+B;AAAA,EAC7C,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS,CAAC;AAAA,EACV,OAAO,CAAC,QAAS,MAAM,QAAQ,IAAI,OAAO,IAAK,IAAI,UAA0B;AAC/E,CAAC;AAIM,MAAM,aAAa,MAAmB,QAAQ,IAAI;AAIlD,MAAM,kBAAkB,CAAC,aAC9B,QAAQ,SAAS,QAAQ;AAKpB,MAAM,aAAa,MAAmB,QAAQ,IAAI;","names":[]}
@@ -22,8 +22,9 @@ __export(contribute_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(contribute_exports);
24
24
  var import_protocolStream = require("./protocolStream");
25
+ var import_protocol = require("./generated/protocol");
25
26
  function contribute(opts) {
26
- return (0, import_protocolStream.protocolStream)("protocol-contribute", "run", [opts]);
27
+ return (0, import_protocolStream.protocolStream)(import_protocol.PROTOCOL_CONTRIBUTE, "run", [opts]);
27
28
  }
28
29
  // Annotate the CommonJS export names for ESM import in node:
29
30
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contribute.ts"],"sourcesContent":["// Contribute — stream a save (PR or direct commit) from app code\n// (UI_AS_APPS_SPEC §5.1, CONTRIBUTE_SPEC). The host runs the *existing*\n// contribution orchestrator and streams its stages back; this is the typed\n// front door over `protocolStream`.\n//\n// The OAuth token never reaches here — it stays on the host. The app passes a\n// commit message + save mode and watches stages go by; the host owns the token,\n// the GitHub API calls, and the user-facing consent.\nimport { protocolStream } from './protocolStream';\n\n/** The save strategy. `direct` requires the first-party `contribute:direct`\n * capability and a scarier consent line — a `contribute:any` app asking for it\n * is REJECTED (`forbidden`), never silently downgraded to a PR (threat T11). */\nexport type ContributeMode = 'pr' | 'direct';\n\n/** A stage emitted as the contribution runs. Mirrors the host orchestrator's\n * event union; carries progress metadata only — never the token or file blobs. */\nexport type ContributionEvent =\n | { stage: 'auth-check' }\n | { stage: 'diff-compute' }\n | { stage: 'permission-check' }\n | { stage: 'install-required'; targetOwner: string; targetRepo: string; installUrl: string }\n | { stage: 'conflict-check' }\n | { stage: 'fork-prepare'; forkOwner: string; alreadyExists: boolean }\n | { stage: 'upload-blob'; path: string; index: number; total: number }\n | { stage: 'create-tree' }\n | { stage: 'create-commit' }\n | { stage: 'create-branch'; branchName: string }\n | { stage: 'create-pr' }\n | { stage: 'pr-updated'; prNumber: number; prUrl: string; commitSha: string }\n | { stage: 'commit-pushed'; ref: string; commitSha: string }\n | { stage: 'switch-branch'; provider: 'github'; pushOwner: string; repository: string; branchName: string }\n | { stage: 'done'; prUrl?: string; prNumber?: number; commitSha: string }\n | { stage: 'warning'; message: string; details?: unknown }\n | { stage: 'error'; message: string; recoverable: boolean };\n\n/** The settled outcome (the stream's return value). */\nexport interface ContributionResult {\n prUrl?: string;\n prNumber?: number;\n commitSha: string;\n treeSha: string;\n branchName: string;\n mode: 'direct-commit' | 'new-branch-pr' | 'extend-existing';\n}\n\n/** Options for a contribution: the commit message, save {@link ContributeMode},\n * and (PR mode) an optional branch name. */\nexport interface ContributeOptions {\n /** The commit message / PR title. */\n commitMessage: string;\n /** `'pr'` (default) opens a PR; `'direct'` commits to the branch and needs\n * the first-party `contribute:direct` capability. */\n mode?: ContributeMode;\n /** Override the generated branch name (PR mode). */\n branchName?: string;\n}\n\n/**\n * Save the current working tree, streaming each stage.\n *\n * ```ts\n * for await (const ev of contribute({ commitMessage: 'Edit post' })) {\n * if (ev.stage === 'done') console.log(ev.prUrl);\n * }\n * ```\n *\n * Yields {@link ContributionEvent}s and returns a {@link ContributionResult}.\n * Throws a `StreamError` (`.code`) if the host rejects the request — notably\n * `forbidden` when a `contribute:any` app asks for `mode: 'direct'` (T11).\n */\nexport function contribute(\n opts: ContributeOptions\n): AsyncGenerator<ContributionEvent, ContributionResult, void> {\n return protocolStream<ContributionEvent, ContributionResult>('protocol-contribute', 'run', [opts]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,4BAA+B;AA+DxB,SAAS,WACd,MAC6D;AAC7D,aAAO,sCAAsD,uBAAuB,OAAO,CAAC,IAAI,CAAC;AACnG;","names":[]}
1
+ {"version":3,"sources":["../src/contribute.ts"],"sourcesContent":["// Contribute — stream a save (PR or direct commit) from app code\n// (UI_AS_APPS_SPEC §5.1, CONTRIBUTE_SPEC). The host runs the *existing*\n// contribution orchestrator and streams its stages back; this is the typed\n// front door over `protocolStream`.\n//\n// The OAuth token never reaches here — it stays on the host. The app passes a\n// commit message + save mode and watches stages go by; the host owns the token,\n// the GitHub API calls, and the user-facing consent.\nimport { protocolStream } from './protocolStream';\nimport { PROTOCOL_CONTRIBUTE } from './generated/protocol';\n\n/** The save strategy. `direct` requires the first-party `contribute:direct`\n * capability and a scarier consent line — a `contribute:any` app asking for it\n * is REJECTED (`forbidden`), never silently downgraded to a PR (threat T11). */\nexport type ContributeMode = 'pr' | 'direct';\n\n/** A stage emitted as the contribution runs. Mirrors the host orchestrator's\n * event union; carries progress metadata only — never the token or file blobs. */\nexport type ContributionEvent =\n | { stage: 'auth-check' }\n | { stage: 'diff-compute' }\n | { stage: 'permission-check' }\n | { stage: 'install-required'; targetOwner: string; targetRepo: string; installUrl: string }\n | { stage: 'conflict-check' }\n | { stage: 'fork-prepare'; forkOwner: string; alreadyExists: boolean }\n | { stage: 'upload-blob'; path: string; index: number; total: number }\n | { stage: 'create-tree' }\n | { stage: 'create-commit' }\n | { stage: 'create-branch'; branchName: string }\n | { stage: 'create-pr' }\n | { stage: 'pr-updated'; prNumber: number; prUrl: string; commitSha: string }\n | { stage: 'commit-pushed'; ref: string; commitSha: string }\n | { stage: 'switch-branch'; provider: 'github'; pushOwner: string; repository: string; branchName: string }\n | { stage: 'done'; prUrl?: string; prNumber?: number; commitSha: string }\n | { stage: 'warning'; message: string; details?: unknown }\n | { stage: 'error'; message: string; recoverable: boolean };\n\n/** The settled outcome (the stream's return value). */\nexport interface ContributionResult {\n prUrl?: string;\n prNumber?: number;\n commitSha: string;\n treeSha: string;\n branchName: string;\n mode: 'direct-commit' | 'new-branch-pr' | 'extend-existing';\n}\n\n/** Options for a contribution: the commit message, save {@link ContributeMode},\n * and (PR mode) an optional branch name. */\nexport interface ContributeOptions {\n /** The commit message / PR title. */\n commitMessage: string;\n /** `'pr'` (default) opens a PR; `'direct'` commits to the branch and needs\n * the first-party `contribute:direct` capability. */\n mode?: ContributeMode;\n /** Override the generated branch name (PR mode). */\n branchName?: string;\n}\n\n/**\n * Save the current working tree, streaming each stage.\n *\n * ```ts\n * for await (const ev of contribute({ commitMessage: 'Edit post' })) {\n * if (ev.stage === 'done') console.log(ev.prUrl);\n * }\n * ```\n *\n * Yields {@link ContributionEvent}s and returns a {@link ContributionResult}.\n * Throws a `StreamError` (`.code`) if the host rejects the request — notably\n * `forbidden` when a `contribute:any` app asks for `mode: 'direct'` (T11).\n */\nexport function contribute(\n opts: ContributeOptions\n): AsyncGenerator<ContributionEvent, ContributionResult, void> {\n return protocolStream<ContributionEvent, ContributionResult>(PROTOCOL_CONTRIBUTE, 'run', [opts]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,4BAA+B;AAC/B,sBAAoC;AA+D7B,SAAS,WACd,MAC6D;AAC7D,aAAO,sCAAsD,qCAAqB,OAAO,CAAC,IAAI,CAAC;AACjG;","names":[]}
@@ -1,7 +1,8 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { protocolStream } from "./protocolStream";
3
+ import { PROTOCOL_CONTRIBUTE } from "./generated/protocol";
3
4
  function contribute(opts) {
4
- return protocolStream("protocol-contribute", "run", [opts]);
5
+ return protocolStream(PROTOCOL_CONTRIBUTE, "run", [opts]);
5
6
  }
6
7
  export {
7
8
  contribute
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/contribute.ts"],"sourcesContent":["// Contribute — stream a save (PR or direct commit) from app code\n// (UI_AS_APPS_SPEC §5.1, CONTRIBUTE_SPEC). The host runs the *existing*\n// contribution orchestrator and streams its stages back; this is the typed\n// front door over `protocolStream`.\n//\n// The OAuth token never reaches here — it stays on the host. The app passes a\n// commit message + save mode and watches stages go by; the host owns the token,\n// the GitHub API calls, and the user-facing consent.\nimport { protocolStream } from './protocolStream';\n\n/** The save strategy. `direct` requires the first-party `contribute:direct`\n * capability and a scarier consent line — a `contribute:any` app asking for it\n * is REJECTED (`forbidden`), never silently downgraded to a PR (threat T11). */\nexport type ContributeMode = 'pr' | 'direct';\n\n/** A stage emitted as the contribution runs. Mirrors the host orchestrator's\n * event union; carries progress metadata only — never the token or file blobs. */\nexport type ContributionEvent =\n | { stage: 'auth-check' }\n | { stage: 'diff-compute' }\n | { stage: 'permission-check' }\n | { stage: 'install-required'; targetOwner: string; targetRepo: string; installUrl: string }\n | { stage: 'conflict-check' }\n | { stage: 'fork-prepare'; forkOwner: string; alreadyExists: boolean }\n | { stage: 'upload-blob'; path: string; index: number; total: number }\n | { stage: 'create-tree' }\n | { stage: 'create-commit' }\n | { stage: 'create-branch'; branchName: string }\n | { stage: 'create-pr' }\n | { stage: 'pr-updated'; prNumber: number; prUrl: string; commitSha: string }\n | { stage: 'commit-pushed'; ref: string; commitSha: string }\n | { stage: 'switch-branch'; provider: 'github'; pushOwner: string; repository: string; branchName: string }\n | { stage: 'done'; prUrl?: string; prNumber?: number; commitSha: string }\n | { stage: 'warning'; message: string; details?: unknown }\n | { stage: 'error'; message: string; recoverable: boolean };\n\n/** The settled outcome (the stream's return value). */\nexport interface ContributionResult {\n prUrl?: string;\n prNumber?: number;\n commitSha: string;\n treeSha: string;\n branchName: string;\n mode: 'direct-commit' | 'new-branch-pr' | 'extend-existing';\n}\n\n/** Options for a contribution: the commit message, save {@link ContributeMode},\n * and (PR mode) an optional branch name. */\nexport interface ContributeOptions {\n /** The commit message / PR title. */\n commitMessage: string;\n /** `'pr'` (default) opens a PR; `'direct'` commits to the branch and needs\n * the first-party `contribute:direct` capability. */\n mode?: ContributeMode;\n /** Override the generated branch name (PR mode). */\n branchName?: string;\n}\n\n/**\n * Save the current working tree, streaming each stage.\n *\n * ```ts\n * for await (const ev of contribute({ commitMessage: 'Edit post' })) {\n * if (ev.stage === 'done') console.log(ev.prUrl);\n * }\n * ```\n *\n * Yields {@link ContributionEvent}s and returns a {@link ContributionResult}.\n * Throws a `StreamError` (`.code`) if the host rejects the request — notably\n * `forbidden` when a `contribute:any` app asks for `mode: 'direct'` (T11).\n */\nexport function contribute(\n opts: ContributeOptions\n): AsyncGenerator<ContributionEvent, ContributionResult, void> {\n return protocolStream<ContributionEvent, ContributionResult>('protocol-contribute', 'run', [opts]);\n}\n"],"mappings":";AAQA,SAAS,sBAAsB;AA+DxB,SAAS,WACd,MAC6D;AAC7D,SAAO,eAAsD,uBAAuB,OAAO,CAAC,IAAI,CAAC;AACnG;","names":[]}
1
+ {"version":3,"sources":["../src/contribute.ts"],"sourcesContent":["// Contribute — stream a save (PR or direct commit) from app code\n// (UI_AS_APPS_SPEC §5.1, CONTRIBUTE_SPEC). The host runs the *existing*\n// contribution orchestrator and streams its stages back; this is the typed\n// front door over `protocolStream`.\n//\n// The OAuth token never reaches here — it stays on the host. The app passes a\n// commit message + save mode and watches stages go by; the host owns the token,\n// the GitHub API calls, and the user-facing consent.\nimport { protocolStream } from './protocolStream';\nimport { PROTOCOL_CONTRIBUTE } from './generated/protocol';\n\n/** The save strategy. `direct` requires the first-party `contribute:direct`\n * capability and a scarier consent line — a `contribute:any` app asking for it\n * is REJECTED (`forbidden`), never silently downgraded to a PR (threat T11). */\nexport type ContributeMode = 'pr' | 'direct';\n\n/** A stage emitted as the contribution runs. Mirrors the host orchestrator's\n * event union; carries progress metadata only — never the token or file blobs. */\nexport type ContributionEvent =\n | { stage: 'auth-check' }\n | { stage: 'diff-compute' }\n | { stage: 'permission-check' }\n | { stage: 'install-required'; targetOwner: string; targetRepo: string; installUrl: string }\n | { stage: 'conflict-check' }\n | { stage: 'fork-prepare'; forkOwner: string; alreadyExists: boolean }\n | { stage: 'upload-blob'; path: string; index: number; total: number }\n | { stage: 'create-tree' }\n | { stage: 'create-commit' }\n | { stage: 'create-branch'; branchName: string }\n | { stage: 'create-pr' }\n | { stage: 'pr-updated'; prNumber: number; prUrl: string; commitSha: string }\n | { stage: 'commit-pushed'; ref: string; commitSha: string }\n | { stage: 'switch-branch'; provider: 'github'; pushOwner: string; repository: string; branchName: string }\n | { stage: 'done'; prUrl?: string; prNumber?: number; commitSha: string }\n | { stage: 'warning'; message: string; details?: unknown }\n | { stage: 'error'; message: string; recoverable: boolean };\n\n/** The settled outcome (the stream's return value). */\nexport interface ContributionResult {\n prUrl?: string;\n prNumber?: number;\n commitSha: string;\n treeSha: string;\n branchName: string;\n mode: 'direct-commit' | 'new-branch-pr' | 'extend-existing';\n}\n\n/** Options for a contribution: the commit message, save {@link ContributeMode},\n * and (PR mode) an optional branch name. */\nexport interface ContributeOptions {\n /** The commit message / PR title. */\n commitMessage: string;\n /** `'pr'` (default) opens a PR; `'direct'` commits to the branch and needs\n * the first-party `contribute:direct` capability. */\n mode?: ContributeMode;\n /** Override the generated branch name (PR mode). */\n branchName?: string;\n}\n\n/**\n * Save the current working tree, streaming each stage.\n *\n * ```ts\n * for await (const ev of contribute({ commitMessage: 'Edit post' })) {\n * if (ev.stage === 'done') console.log(ev.prUrl);\n * }\n * ```\n *\n * Yields {@link ContributionEvent}s and returns a {@link ContributionResult}.\n * Throws a `StreamError` (`.code`) if the host rejects the request — notably\n * `forbidden` when a `contribute:any` app asks for `mode: 'direct'` (T11).\n */\nexport function contribute(\n opts: ContributeOptions\n): AsyncGenerator<ContributionEvent, ContributionResult, void> {\n return protocolStream<ContributionEvent, ContributionResult>(PROTOCOL_CONTRIBUTE, 'run', [opts]);\n}\n"],"mappings":";AAQA,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B;AA+D7B,SAAS,WACd,MAC6D;AAC7D,SAAO,eAAsD,qBAAqB,OAAO,CAAC,IAAI,CAAC;AACjG;","names":[]}
package/dist/debug.cjs CHANGED
@@ -26,9 +26,10 @@ __export(debug_exports, {
26
26
  module.exports = __toCommonJS(debug_exports);
27
27
  var import_pushChannel = require("./pushChannel");
28
28
  var import_sandboxUtils = require("./sandboxUtils");
29
+ var import_protocol = require("./generated/protocol");
29
30
  const enabledChannel = (0, import_pushChannel.createPushChannel)({
30
- pushType: "debug-enabled",
31
- requestType: "request-debug-enabled",
31
+ pushType: import_protocol.DEBUG_ENABLED,
32
+ requestType: import_protocol.REQUEST_DEBUG_ENABLED,
32
33
  initial: false,
33
34
  parse: (msg) => typeof msg.enabled === "boolean" ? msg.enabled : void 0
34
35
  });
@@ -49,7 +50,7 @@ function safeData(data) {
49
50
  function log(level, message, data) {
50
51
  if (!enabledChannel.get()) return;
51
52
  try {
52
- (0, import_sandboxUtils.sendMessage)("debug-log", { level, message: String(message), data: safeData(data) });
53
+ (0, import_sandboxUtils.sendMessage)(import_protocol.DEBUG_LOG, { level, message: String(message), data: safeData(data) });
53
54
  } catch {
54
55
  }
55
56
  }
@@ -121,7 +122,7 @@ let responderStarted = false;
121
122
  function startResponder() {
122
123
  if (responderStarted || typeof window === "undefined") return;
123
124
  responderStarted = true;
124
- (0, import_sandboxUtils.addListener)("debug-query", (msg) => {
125
+ (0, import_sandboxUtils.addListener)(import_protocol.DEBUG_QUERY, (msg) => {
125
126
  if (!enabledChannel.get()) return;
126
127
  const id = msg.id;
127
128
  const method = msg.method;
@@ -149,7 +150,7 @@ function startResponder() {
149
150
  error = e instanceof Error ? e.message : String(e);
150
151
  }
151
152
  try {
152
- (0, import_sandboxUtils.sendMessage)("debug-query-result", { id, ok, result, error });
153
+ (0, import_sandboxUtils.sendMessage)(import_protocol.DEBUG_QUERY_RESULT, { id, ok, result, error });
153
154
  } catch {
154
155
  }
155
156
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/debug.ts"],"sourcesContent":["// System-app devtools — the app-facing surface (plan: docs/plans/system-app-devtools.md).\n//\n// Two opt-in, DEV-ONLY instruments for debugging a sandboxed UI-as-app region:\n// 1. `debug.log(...)` — an app→host one-way log surfaced in the host dev panel\n// / CLI `/debug` stream (instead of hand-fishing console output out of a\n// cross-origin iframe's devtools).\n// 2. a READ-ONLY DOM/layout responder the host can query from outside — the\n// thing a cross-origin screenshot can't reliably give you (a blank capture\n// is ambiguous between a real 0-height collapse and a paint artifact).\n//\n// SECURITY (the gating constraint — see the plan's §0):\n// - Both are inert unless the HOST signals dev mode via the `debug-enabled`\n// channel. The host only sets it for a dev/override session (the `ir-dev-*`\n// deep link) or an explicit operator developer-mode. A published app served\n// to a normal user gets `enabled:false` → `debug.log` is a no-op and the\n// responder never answers. Production isolation is therefore unchanged.\n// - The responder is READ-ONLY with a fixed vocabulary (snapshotDom /\n// computedStyle / rect). There is deliberately NO eval bridge — that would\n// turn a debug aid into remote code execution into the sandbox.\n// - The responder reads only its OWN `document` (it lives in its own opaque\n// iframe and cannot reach a sibling app), so there is no app↔app leak even\n// in dev.\n// - Output is bounded (node/depth/text caps) so a query can't exfiltrate an\n// unbounded payload or wedge the app.\n//\n// Apps that want the strongest guarantee can additionally guard their own usage\n// behind `import.meta.env.DEV` so the calls are tree-shaken from prod bundles;\n// the runtime gate here is the backstop that holds regardless.\n\nimport { createPushChannel } from './pushChannel';\nimport { sendMessage, addListener } from './sandboxUtils';\n\n/** Severity of a {@link debug.log} entry. */\nexport type DebugLevel = 'debug' | 'info' | 'warn' | 'error';\n\n// ── Dev gate ────────────────────────────────────────────────────────────────\n// The host pushes `debug-enabled:true` only for a dev/override session. Until\n// then (and always in production) it stays false and every instrument is inert.\nconst enabledChannel = createPushChannel<boolean>({\n pushType: 'debug-enabled',\n requestType: 'request-debug-enabled',\n initial: false,\n parse: (msg) => (typeof msg.enabled === 'boolean' ? msg.enabled : undefined),\n});\n\n/** Is the host dev-debug surface active for this session? `false` in production. */\nexport const isDebugEnabled = (): boolean => enabledChannel.get();\n\n/** React hook: whether the host dev-debug surface is active (re-renders on change).\n * Handy for showing a debug affordance only when it would do something. */\nexport const useDebugEnabled = (): boolean => enabledChannel.use();\n\n// ── 1. App→host debug log ─────────────────────────────────────────────────────\n// Best-effort: a value that can't be structured-cloned is replaced with a marker\n// rather than throwing — `debug.log` must never break the app.\nconst MAX_DATA_BYTES = 16 * 1024;\n\nfunction safeData(data: unknown): unknown {\n if (data === undefined) return undefined;\n try {\n const json = JSON.stringify(data);\n if (json === undefined) return '[unserializable]';\n if (json.length > MAX_DATA_BYTES) return `[truncated ${json.length}B]`;\n return JSON.parse(json);\n } catch {\n return '[unserializable]';\n }\n}\n\n/**\n * Emit a structured debug entry to the host dev surface. A NO-OP unless the host\n * has enabled the dev-debug session ({@link isDebugEnabled}); in production it\n * does nothing and sends nothing.\n *\n * debug.log('info', 'mounted', { activeFile });\n */\nexport function log(level: DebugLevel, message: string, data?: unknown): void {\n if (!enabledChannel.get()) return; // inert in prod / non-dev sessions\n try {\n sendMessage('debug-log', { level, message: String(message), data: safeData(data) });\n } catch {\n /* transport not ready — drop silently; logging must never throw */\n }\n}\n\n// ── 2. Read-only DOM / layout responder ───────────────────────────────────────\n// The host sends `debug-query` { id, method, params }; we reply with\n// `debug-query-result` { id, ok, result | error }. Only ever active while the dev\n// gate is enabled. Vocabulary is fixed and read-only.\n\ninterface DomNode {\n tag: string;\n id?: string;\n classes?: string[];\n attrs?: Record<string, string>;\n rect?: { x: number; y: number; w: number; h: number };\n text?: string;\n children?: DomNode[];\n truncated?: true;\n}\n\nconst ATTR_ALLOW = new Set(['role', 'aria-hidden', 'data-theme', 'data-active', 'href', 'type', 'hidden']);\nconst MAX_NODES = 2000;\nconst MAX_DEPTH = 25;\nconst MAX_TEXT = 200;\n\nfunction round(n: number): number {\n return Math.round(n);\n}\n\nfunction snapshotDom(params: { selector?: string; maxDepth?: number; maxNodes?: number }): DomNode | null {\n if (typeof document === 'undefined') return null;\n const root = params.selector ? document.querySelector(params.selector) : document.body;\n if (!root) return null;\n const maxDepth = Math.min(params.maxDepth ?? MAX_DEPTH, MAX_DEPTH);\n const maxNodes = Math.min(params.maxNodes ?? MAX_NODES, MAX_NODES);\n let budget = maxNodes;\n\n const walk = (el: Element, depth: number): DomNode => {\n budget--;\n const r = el.getBoundingClientRect();\n const classes = el.classList.length ? [...el.classList] : undefined;\n const attrs: Record<string, string> = {};\n for (const name of el.getAttributeNames()) {\n if (ATTR_ALLOW.has(name)) attrs[name] = el.getAttribute(name) ?? '';\n }\n // Direct text (not descendants') so a leaf's label is visible without dumping\n // the whole subtree's text.\n const ownText = [...el.childNodes]\n .filter((n) => n.nodeType === 3)\n .map((n) => (n.textContent ?? '').trim())\n .join(' ')\n .trim();\n const node: DomNode = {\n tag: el.tagName.toLowerCase(),\n ...(el.id ? { id: el.id } : {}),\n ...(classes ? { classes } : {}),\n ...(Object.keys(attrs).length ? { attrs } : {}),\n rect: { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) },\n ...(ownText ? { text: ownText.slice(0, MAX_TEXT) } : {}),\n };\n if (depth < maxDepth && el.children.length && budget > 0) {\n const children: DomNode[] = [];\n for (const child of el.children) {\n if (budget <= 0) {\n node.truncated = true;\n break;\n }\n children.push(walk(child, depth + 1));\n }\n if (children.length) node.children = children;\n } else if (el.children.length) {\n node.truncated = true;\n }\n return node;\n };\n\n return walk(root, 0);\n}\n\nfunction computedStyle(params: { selector: string; props: string[] }): Record<string, string> | null {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector(params.selector);\n if (!el) return null;\n const cs = getComputedStyle(el);\n const out: Record<string, string> = {};\n for (const p of params.props.slice(0, 50)) out[p] = cs.getPropertyValue(p) || cs[p as keyof CSSStyleDeclaration]?.toString?.() || '';\n return out;\n}\n\nfunction rects(params: { selector: string }): Array<{ x: number; y: number; w: number; h: number }> {\n if (typeof document === 'undefined') return [];\n return [...document.querySelectorAll(params.selector)].slice(0, 200).map((el) => {\n const r = el.getBoundingClientRect();\n return { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) };\n });\n}\n\nlet responderStarted = false;\n\n/** Wire the read-only DOM/layout responder. Idempotent; called lazily once the\n * dev gate turns on. No effect when `document` is absent (non-browser realm). */\nfunction startResponder(): void {\n if (responderStarted || typeof window === 'undefined') return;\n responderStarted = true;\n addListener('debug-query', (msg: { id?: unknown; method?: unknown; params?: unknown }) => {\n if (!enabledChannel.get()) return; // gate: ignore unless dev-enabled\n const id = msg.id;\n const method = msg.method;\n const params = (msg.params ?? {}) as Record<string, unknown>;\n let ok = true;\n let result: unknown = null;\n let error: string | undefined;\n try {\n switch (method) {\n case 'snapshotDom':\n result = snapshotDom(params as never);\n break;\n case 'computedStyle':\n result = computedStyle(params as never);\n break;\n case 'rect':\n result = rects(params as never);\n break;\n default:\n ok = false;\n error = `unknown debug method: ${String(method)}`;\n }\n } catch (e) {\n ok = false;\n error = e instanceof Error ? e.message : String(e);\n }\n try {\n sendMessage('debug-query-result', { id, ok, result, error });\n } catch {\n /* transport gone — nothing to do */\n }\n });\n}\n\n// Start the responder as soon as the gate flips on (and not before).\nenabledChannel.onChange((enabled) => {\n if (enabled) startResponder();\n});\n\n/** The dev-only debug surface. Inert unless the host enables it ({@link isDebugEnabled}). */\nexport const debug = { log, isEnabled: isDebugEnabled } as const;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/debug.ts"],"sourcesContent":["// System-app devtools — the app-facing surface (plan: docs/plans/system-app-devtools.md).\n//\n// Two opt-in, DEV-ONLY instruments for debugging a sandboxed UI-as-app region:\n// 1. `debug.log(...)` — an app→host one-way log surfaced in the host dev panel\n// / CLI `/debug` stream (instead of hand-fishing console output out of a\n// cross-origin iframe's devtools).\n// 2. a READ-ONLY DOM/layout responder the host can query from outside — the\n// thing a cross-origin screenshot can't reliably give you (a blank capture\n// is ambiguous between a real 0-height collapse and a paint artifact).\n//\n// SECURITY (the gating constraint — see the plan's §0):\n// - Both are inert unless the HOST signals dev mode via the `debug-enabled`\n// channel. The host only sets it for a dev/override session (the `ir-dev-*`\n// deep link) or an explicit operator developer-mode. A published app served\n// to a normal user gets `enabled:false` → `debug.log` is a no-op and the\n// responder never answers. Production isolation is therefore unchanged.\n// - The responder is READ-ONLY with a fixed vocabulary (snapshotDom /\n// computedStyle / rect). There is deliberately NO eval bridge — that would\n// turn a debug aid into remote code execution into the sandbox.\n// - The responder reads only its OWN `document` (it lives in its own opaque\n// iframe and cannot reach a sibling app), so there is no app↔app leak even\n// in dev.\n// - Output is bounded (node/depth/text caps) so a query can't exfiltrate an\n// unbounded payload or wedge the app.\n//\n// Apps that want the strongest guarantee can additionally guard their own usage\n// behind `import.meta.env.DEV` so the calls are tree-shaken from prod bundles;\n// the runtime gate here is the backstop that holds regardless.\n\nimport { createPushChannel } from './pushChannel';\nimport { sendMessage, addListener } from './sandboxUtils';\nimport {\n DEBUG_ENABLED,\n DEBUG_LOG,\n DEBUG_QUERY,\n DEBUG_QUERY_RESULT,\n REQUEST_DEBUG_ENABLED,\n} from './generated/protocol';\n\n/** Severity of a {@link debug.log} entry. */\nexport type DebugLevel = 'debug' | 'info' | 'warn' | 'error';\n\n// ── Dev gate ────────────────────────────────────────────────────────────────\n// The host pushes `debug-enabled:true` only for a dev/override session. Until\n// then (and always in production) it stays false and every instrument is inert.\nconst enabledChannel = createPushChannel<boolean>({\n pushType: DEBUG_ENABLED,\n requestType: REQUEST_DEBUG_ENABLED,\n initial: false,\n parse: (msg) => (typeof msg.enabled === 'boolean' ? msg.enabled : undefined),\n});\n\n/** Is the host dev-debug surface active for this session? `false` in production. */\nexport const isDebugEnabled = (): boolean => enabledChannel.get();\n\n/** React hook: whether the host dev-debug surface is active (re-renders on change).\n * Handy for showing a debug affordance only when it would do something. */\nexport const useDebugEnabled = (): boolean => enabledChannel.use();\n\n// ── 1. App→host debug log ─────────────────────────────────────────────────────\n// Best-effort: a value that can't be structured-cloned is replaced with a marker\n// rather than throwing — `debug.log` must never break the app.\nconst MAX_DATA_BYTES = 16 * 1024;\n\nfunction safeData(data: unknown): unknown {\n if (data === undefined) return undefined;\n try {\n const json = JSON.stringify(data);\n if (json === undefined) return '[unserializable]';\n if (json.length > MAX_DATA_BYTES) return `[truncated ${json.length}B]`;\n return JSON.parse(json);\n } catch {\n return '[unserializable]';\n }\n}\n\n/**\n * Emit a structured debug entry to the host dev surface. A NO-OP unless the host\n * has enabled the dev-debug session ({@link isDebugEnabled}); in production it\n * does nothing and sends nothing.\n *\n * debug.log('info', 'mounted', { activeFile });\n */\nexport function log(level: DebugLevel, message: string, data?: unknown): void {\n if (!enabledChannel.get()) return; // inert in prod / non-dev sessions\n try {\n sendMessage(DEBUG_LOG, { level, message: String(message), data: safeData(data) });\n } catch {\n /* transport not ready — drop silently; logging must never throw */\n }\n}\n\n// ── 2. Read-only DOM / layout responder ───────────────────────────────────────\n// The host sends `debug-query` { id, method, params }; we reply with\n// `debug-query-result` { id, ok, result | error }. Only ever active while the dev\n// gate is enabled. Vocabulary is fixed and read-only.\n\ninterface DomNode {\n tag: string;\n id?: string;\n classes?: string[];\n attrs?: Record<string, string>;\n rect?: { x: number; y: number; w: number; h: number };\n text?: string;\n children?: DomNode[];\n truncated?: true;\n}\n\nconst ATTR_ALLOW = new Set(['role', 'aria-hidden', 'data-theme', 'data-active', 'href', 'type', 'hidden']);\nconst MAX_NODES = 2000;\nconst MAX_DEPTH = 25;\nconst MAX_TEXT = 200;\n\nfunction round(n: number): number {\n return Math.round(n);\n}\n\nfunction snapshotDom(params: { selector?: string; maxDepth?: number; maxNodes?: number }): DomNode | null {\n if (typeof document === 'undefined') return null;\n const root = params.selector ? document.querySelector(params.selector) : document.body;\n if (!root) return null;\n const maxDepth = Math.min(params.maxDepth ?? MAX_DEPTH, MAX_DEPTH);\n const maxNodes = Math.min(params.maxNodes ?? MAX_NODES, MAX_NODES);\n let budget = maxNodes;\n\n const walk = (el: Element, depth: number): DomNode => {\n budget--;\n const r = el.getBoundingClientRect();\n const classes = el.classList.length ? [...el.classList] : undefined;\n const attrs: Record<string, string> = {};\n for (const name of el.getAttributeNames()) {\n if (ATTR_ALLOW.has(name)) attrs[name] = el.getAttribute(name) ?? '';\n }\n // Direct text (not descendants') so a leaf's label is visible without dumping\n // the whole subtree's text.\n const ownText = [...el.childNodes]\n .filter((n) => n.nodeType === 3)\n .map((n) => (n.textContent ?? '').trim())\n .join(' ')\n .trim();\n const node: DomNode = {\n tag: el.tagName.toLowerCase(),\n ...(el.id ? { id: el.id } : {}),\n ...(classes ? { classes } : {}),\n ...(Object.keys(attrs).length ? { attrs } : {}),\n rect: { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) },\n ...(ownText ? { text: ownText.slice(0, MAX_TEXT) } : {}),\n };\n if (depth < maxDepth && el.children.length && budget > 0) {\n const children: DomNode[] = [];\n for (const child of el.children) {\n if (budget <= 0) {\n node.truncated = true;\n break;\n }\n children.push(walk(child, depth + 1));\n }\n if (children.length) node.children = children;\n } else if (el.children.length) {\n node.truncated = true;\n }\n return node;\n };\n\n return walk(root, 0);\n}\n\nfunction computedStyle(params: { selector: string; props: string[] }): Record<string, string> | null {\n if (typeof document === 'undefined') return null;\n const el = document.querySelector(params.selector);\n if (!el) return null;\n const cs = getComputedStyle(el);\n const out: Record<string, string> = {};\n for (const p of params.props.slice(0, 50)) out[p] = cs.getPropertyValue(p) || cs[p as keyof CSSStyleDeclaration]?.toString?.() || '';\n return out;\n}\n\nfunction rects(params: { selector: string }): Array<{ x: number; y: number; w: number; h: number }> {\n if (typeof document === 'undefined') return [];\n return [...document.querySelectorAll(params.selector)].slice(0, 200).map((el) => {\n const r = el.getBoundingClientRect();\n return { x: round(r.x), y: round(r.y), w: round(r.width), h: round(r.height) };\n });\n}\n\nlet responderStarted = false;\n\n/** Wire the read-only DOM/layout responder. Idempotent; called lazily once the\n * dev gate turns on. No effect when `document` is absent (non-browser realm). */\nfunction startResponder(): void {\n if (responderStarted || typeof window === 'undefined') return;\n responderStarted = true;\n addListener(DEBUG_QUERY, (msg: { id?: unknown; method?: unknown; params?: unknown }) => {\n if (!enabledChannel.get()) return; // gate: ignore unless dev-enabled\n const id = msg.id;\n const method = msg.method;\n const params = (msg.params ?? {}) as Record<string, unknown>;\n let ok = true;\n let result: unknown = null;\n let error: string | undefined;\n try {\n switch (method) {\n case 'snapshotDom':\n result = snapshotDom(params as never);\n break;\n case 'computedStyle':\n result = computedStyle(params as never);\n break;\n case 'rect':\n result = rects(params as never);\n break;\n default:\n ok = false;\n error = `unknown debug method: ${String(method)}`;\n }\n } catch (e) {\n ok = false;\n error = e instanceof Error ? e.message : String(e);\n }\n try {\n sendMessage(DEBUG_QUERY_RESULT, { id, ok, result, error });\n } catch {\n /* transport gone — nothing to do */\n }\n });\n}\n\n// Start the responder as soon as the gate flips on (and not before).\nenabledChannel.onChange((enabled) => {\n if (enabled) startResponder();\n});\n\n/** The dev-only debug surface. Inert unless the host enables it ({@link isDebugEnabled}). */\nexport const debug = { log, isEnabled: isDebugEnabled } as const;\n"],"mappings":"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","names":[]}