@immediately-run/sdk 0.44.0 → 0.45.1

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 +20 -11
  124. package/dist/urlUtils.cjs.map +1 -1
  125. package/dist/urlUtils.d.cts +4 -10
  126. package/dist/urlUtils.d.ts +4 -10
  127. package/dist/urlUtils.js +18 -9
  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
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\nimport { protocolStream } from './protocolStream';\n\n/** Request options for {@link hostFetch}: method, headers, and a string body. */\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\n/** The serialized response from {@link hostFetch} (no live stream crosses the boundary). */\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest('fetch', 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/** One streamed slice of the response body (a chunk as it arrives from the\n * host). Concatenate `.chunk` across the stream to rebuild the body. */\nexport interface HostFetchStreamEvent {\n chunk: string;\n}\n\n/** The terminal value of a {@link hostFetchStream} run — the response metadata,\n * delivered once after the last body chunk. There is no `body` field: the body\n * arrived as the stream of {@link HostFetchStreamEvent}s. */\nexport interface HostFetchStreamResult {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n /** True if the stream hit the host's cumulative byte cap and was truncated. */\n truncated: boolean;\n /** Total decoded body bytes delivered. */\n bytes: number;\n}\n\n/**\n * Stream a response through the host's parent-fetch proxy (§5.11 streaming;\n * `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},\n * for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),\n * same credential-less rule and per-hop SSRF re-check; the response body is\n * pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single\n * buffered reply.\n *\n * ```ts\n * let body = '';\n * for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {\n * body += chunk; // e.g. parse SSE / token deltas as they arrive\n * }\n * ```\n *\n * The generator **returns** a {@link HostFetchStreamResult} (status + headers +\n * `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —\n * or one of the host's stream bounds — **throws** a `StreamError` (from\n * `./protocolStream`) carrying a\n * machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or\n * `network`. The stream is bounded by the host's idle/total timeouts and a\n * cumulative byte cap, so it cannot be used as an unbounded transfer channel.\n *\n * Requires the host to implement the `protocol-fetch` streaming emitter; until\n * that lands a streaming request fails with `not-streamable` and callers should\n * fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).\n */\nexport function hostFetchStream(\n url: string,\n init: HostFetchInit = {},\n): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void> {\n return protocolStream<HostFetchStreamEvent, HostFetchStreamResult>(\n 'protocol-fetch',\n 'fetchStream',\n [{ url, method: init.method, headers: init.headers, body: init.body }],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,0BAAgC;AAChC,4BAA+B;AA+BxB,MAAM,YAAY,OACvB,KACA,OAAsB,CAAC,MACQ;AAC/B,QAAM,MAAO,UAAM,qCAAgB,SAAS,SAAS;AAAA,IACnD,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EACrE,CAAC;AAID,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,kBAAkB;AACxD,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAkDO,SAAS,gBACd,KACA,OAAsB,CAAC,GAC4C;AACnE,aAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\nimport { protocolStream } from './protocolStream';\nimport { SCHEMES } from './protocolSchemes';\nimport { PROTOCOL_FETCH } from './generated/protocol';\n\n/** Request options for {@link hostFetch}: method, headers, and a string body. */\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\n/** The serialized response from {@link hostFetch} (no live stream crosses the boundary). */\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_FETCH], 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/** One streamed slice of the response body (a chunk as it arrives from the\n * host). Concatenate `.chunk` across the stream to rebuild the body. */\nexport interface HostFetchStreamEvent {\n chunk: string;\n}\n\n/** The terminal value of a {@link hostFetchStream} run — the response metadata,\n * delivered once after the last body chunk. There is no `body` field: the body\n * arrived as the stream of {@link HostFetchStreamEvent}s. */\nexport interface HostFetchStreamResult {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n /** True if the stream hit the host's cumulative byte cap and was truncated. */\n truncated: boolean;\n /** Total decoded body bytes delivered. */\n bytes: number;\n}\n\n/**\n * Stream a response through the host's parent-fetch proxy (§5.11 streaming;\n * `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},\n * for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),\n * same credential-less rule and per-hop SSRF re-check; the response body is\n * pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single\n * buffered reply.\n *\n * ```ts\n * let body = '';\n * for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {\n * body += chunk; // e.g. parse SSE / token deltas as they arrive\n * }\n * ```\n *\n * The generator **returns** a {@link HostFetchStreamResult} (status + headers +\n * `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —\n * or one of the host's stream bounds — **throws** a `StreamError` (from\n * `./protocolStream`) carrying a\n * machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or\n * `network`. The stream is bounded by the host's idle/total timeouts and a\n * cumulative byte cap, so it cannot be used as an unbounded transfer channel.\n *\n * Requires the host to implement the `protocol-fetch` streaming emitter; until\n * that lands a streaming request fails with `not-streamable` and callers should\n * fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).\n */\nexport function hostFetchStream(\n url: string,\n init: HostFetchInit = {},\n): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void> {\n return protocolStream<HostFetchStreamEvent, HostFetchStreamResult>(\n PROTOCOL_FETCH,\n 'fetchStream',\n [{ url, method: init.method, headers: init.headers, body: init.body }],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,0BAAgC;AAChC,4BAA+B;AAC/B,6BAAwB;AACxB,sBAA+B;AA+BxB,MAAM,YAAY,OACvB,KACA,OAAsB,CAAC,MACQ;AAC/B,QAAM,MAAO,UAAM,qCAAgB,+BAAQ,8BAAc,GAAG,SAAS;AAAA,IACnE,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EACrE,CAAC;AAID,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,kBAAkB;AACxD,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAkDO,SAAS,gBACd,KACA,OAAsB,CAAC,GAC4C;AACnE,aAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE;AACF;","names":[]}
package/dist/netFetch.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { protocolRequest } from "./sandboxUtils";
3
3
  import { protocolStream } from "./protocolStream";
4
+ import { SCHEMES } from "./protocolSchemes";
5
+ import { PROTOCOL_FETCH } from "./generated/protocol";
4
6
  const hostFetch = async (url, init = {}) => {
5
- const res = await protocolRequest("fetch", "fetch", [
7
+ const res = await protocolRequest(SCHEMES[PROTOCOL_FETCH], "fetch", [
6
8
  { url, method: init.method, headers: init.headers, body: init.body }
7
9
  ]);
8
10
  if (!res || res.ok !== true) {
@@ -14,7 +16,7 @@ const hostFetch = async (url, init = {}) => {
14
16
  };
15
17
  function hostFetchStream(url, init = {}) {
16
18
  return protocolStream(
17
- "protocol-fetch",
19
+ PROTOCOL_FETCH,
18
20
  "fetchStream",
19
21
  [{ url, method: init.method, headers: init.headers, body: init.body }]
20
22
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\nimport { protocolStream } from './protocolStream';\n\n/** Request options for {@link hostFetch}: method, headers, and a string body. */\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\n/** The serialized response from {@link hostFetch} (no live stream crosses the boundary). */\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest('fetch', 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/** One streamed slice of the response body (a chunk as it arrives from the\n * host). Concatenate `.chunk` across the stream to rebuild the body. */\nexport interface HostFetchStreamEvent {\n chunk: string;\n}\n\n/** The terminal value of a {@link hostFetchStream} run — the response metadata,\n * delivered once after the last body chunk. There is no `body` field: the body\n * arrived as the stream of {@link HostFetchStreamEvent}s. */\nexport interface HostFetchStreamResult {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n /** True if the stream hit the host's cumulative byte cap and was truncated. */\n truncated: boolean;\n /** Total decoded body bytes delivered. */\n bytes: number;\n}\n\n/**\n * Stream a response through the host's parent-fetch proxy (§5.11 streaming;\n * `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},\n * for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),\n * same credential-less rule and per-hop SSRF re-check; the response body is\n * pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single\n * buffered reply.\n *\n * ```ts\n * let body = '';\n * for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {\n * body += chunk; // e.g. parse SSE / token deltas as they arrive\n * }\n * ```\n *\n * The generator **returns** a {@link HostFetchStreamResult} (status + headers +\n * `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —\n * or one of the host's stream bounds — **throws** a `StreamError` (from\n * `./protocolStream`) carrying a\n * machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or\n * `network`. The stream is bounded by the host's idle/total timeouts and a\n * cumulative byte cap, so it cannot be used as an unbounded transfer channel.\n *\n * Requires the host to implement the `protocol-fetch` streaming emitter; until\n * that lands a streaming request fails with `not-streamable` and callers should\n * fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).\n */\nexport function hostFetchStream(\n url: string,\n init: HostFetchInit = {},\n): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void> {\n return protocolStream<HostFetchStreamEvent, HostFetchStreamResult>(\n 'protocol-fetch',\n 'fetchStream',\n [{ url, method: init.method, headers: init.headers, body: init.body }],\n );\n}\n"],"mappings":";AAQA,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AA+BxB,MAAM,YAAY,OACvB,KACA,OAAsB,CAAC,MACQ;AAC/B,QAAM,MAAO,MAAM,gBAAgB,SAAS,SAAS;AAAA,IACnD,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EACrE,CAAC;AAID,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,kBAAkB;AACxD,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAkDO,SAAS,gBACd,KACA,OAAsB,CAAC,GAC4C;AACnE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/netFetch.ts"],"sourcesContent":["// hostFetch — the app-facing side of the §5.11 parent-fetch proxy. The app calls\n// `hostFetch(url, init)`; the HOST performs the fetch with its real origin, but\n// only after validating `url` against your manifest's\n// `requests.\"net:fetch\".hosts` ∩ the user's consented hosts, blocking SSRF\n// targets, omitting immediately.run credentials, refusing redirects, and bounding\n// the response size. No raw network handle ever crosses the boundary (§8.10) —\n// only the serialized response.\n\nimport { protocolRequest } from './sandboxUtils';\nimport { protocolStream } from './protocolStream';\nimport { SCHEMES } from './protocolSchemes';\nimport { PROTOCOL_FETCH } from './generated/protocol';\n\n/** Request options for {@link hostFetch}: method, headers, and a string body. */\nexport interface HostFetchInit {\n method?: string;\n headers?: Record<string, string>;\n /** Request body for non-GET/HEAD methods (string). */\n body?: string;\n}\n\n/** The serialized response from {@link hostFetch} (no live stream crosses the boundary). */\nexport interface HostFetchResponse {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n /** True if the body hit the host's size cap and was truncated. */\n truncated: boolean;\n}\n\n/**\n * Fetch through the host's parent-fetch proxy (§5.11). Requires the `net:fetch`\n * capability with `url`'s origin in your effective allowlist (manifest ∩ the\n * user's consent) — both are arranged at load via the consent screen.\n *\n * A reachable server's reply (including a non-2xx status) RESOLVES — inspect\n * `.status`. A gate/SSRF/transport failure REJECTS with an {@link Error} carrying\n * a machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `too-large`, or `network`.\n */\nexport const hostFetch = async (\n url: string,\n init: HostFetchInit = {},\n): Promise<HostFetchResponse> => {\n const res = (await protocolRequest(SCHEMES[PROTOCOL_FETCH], 'fetch', [\n { url, method: init.method, headers: init.headers, body: init.body },\n ])) as\n | { ok: true; data: HostFetchResponse }\n | { ok: false; code?: string; message?: string }\n | undefined;\n if (!res || res.ok !== true) {\n const err = new Error(res?.message ?? 'hostFetch failed') as Error & { code?: string };\n err.code = (res && 'code' in res ? res.code : undefined) ?? 'unknown';\n throw err;\n }\n return res.data;\n};\n\n/** One streamed slice of the response body (a chunk as it arrives from the\n * host). Concatenate `.chunk` across the stream to rebuild the body. */\nexport interface HostFetchStreamEvent {\n chunk: string;\n}\n\n/** The terminal value of a {@link hostFetchStream} run — the response metadata,\n * delivered once after the last body chunk. There is no `body` field: the body\n * arrived as the stream of {@link HostFetchStreamEvent}s. */\nexport interface HostFetchStreamResult {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n /** True if the stream hit the host's cumulative byte cap and was truncated. */\n truncated: boolean;\n /** Total decoded body bytes delivered. */\n bytes: number;\n}\n\n/**\n * Stream a response through the host's parent-fetch proxy (§5.11 streaming;\n * `LLM_AND_AGENTS_SPEC §2.2`) — the streaming counterpart of {@link hostFetch},\n * for SSE / LLM token streaming. Same `net:fetch` gate (manifest ∩ consent),\n * same credential-less rule and per-hop SSRF re-check; the response body is\n * pumped as a sequence of {@link HostFetchStreamEvent} chunks instead of a single\n * buffered reply.\n *\n * ```ts\n * let body = '';\n * for await (const { chunk } of hostFetchStream(url, { method: 'POST', body })) {\n * body += chunk; // e.g. parse SSE / token deltas as they arrive\n * }\n * ```\n *\n * The generator **returns** a {@link HostFetchStreamResult} (status + headers +\n * `truncated`/`bytes`) when the body completes. A gate/SSRF/transport failure —\n * or one of the host's stream bounds — **throws** a `StreamError` (from\n * `./protocolStream`) carrying a\n * machine `.code`: `forbidden` (outside the allowlist), `blocked` (SSRF target),\n * `invalid` (bad url/scheme), `redirect` (the host refuses to follow redirects),\n * `idle-timeout` / `total-timeout` (the host's stream bounds), `byte-cap`, or\n * `network`. The stream is bounded by the host's idle/total timeouts and a\n * cumulative byte cap, so it cannot be used as an unbounded transfer channel.\n *\n * Requires the host to implement the `protocol-fetch` streaming emitter; until\n * that lands a streaming request fails with `not-streamable` and callers should\n * fall back to {@link hostFetch} (`LLM_AND_AGENTS_SPEC §2.2`, roadmap P3-71).\n */\nexport function hostFetchStream(\n url: string,\n init: HostFetchInit = {},\n): AsyncGenerator<HostFetchStreamEvent, HostFetchStreamResult, void> {\n return protocolStream<HostFetchStreamEvent, HostFetchStreamResult>(\n PROTOCOL_FETCH,\n 'fetchStream',\n [{ url, method: init.method, headers: init.headers, body: init.body }],\n );\n}\n"],"mappings":";AAQA,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,eAAe;AACxB,SAAS,sBAAsB;AA+BxB,MAAM,YAAY,OACvB,KACA,OAAsB,CAAC,MACQ;AAC/B,QAAM,MAAO,MAAM,gBAAgB,QAAQ,cAAc,GAAG,SAAS;AAAA,IACnE,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EACrE,CAAC;AAID,MAAI,CAAC,OAAO,IAAI,OAAO,MAAM;AAC3B,UAAM,MAAM,IAAI,MAAM,KAAK,WAAW,kBAAkB;AACxD,QAAI,QAAQ,OAAO,UAAU,MAAM,IAAI,OAAO,WAAc;AAC5D,UAAM;AAAA,EACR;AACA,SAAO,IAAI;AACb;AAkDO,SAAS,gBACd,KACA,OAAsB,CAAC,GAC4C;AACnE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,CAAC,EAAE,KAAK,QAAQ,KAAK,QAAQ,SAAS,KAAK,SAAS,MAAM,KAAK,KAAK,CAAC;AAAA,EACvE;AACF;","names":[]}
@@ -24,9 +24,10 @@ __export(onFsChange_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(onFsChange_exports);
26
26
  var import_pushChannel = require("./pushChannel");
27
+ var import_protocol = require("./generated/protocol");
27
28
  const isStringArray = (v) => Array.isArray(v) && v.every((p) => typeof p === "string");
28
29
  const channel = (0, import_pushChannel.createPushChannel)({
29
- pushType: "fs-change",
30
+ pushType: import_protocol.FS_CHANGE,
30
31
  initial: { paths: [], epoch: 0 },
31
32
  parse: (msg) => isStringArray(msg.paths) && typeof msg.epoch === "number" ? { paths: msg.paths, epoch: msg.epoch } : void 0
32
33
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/onFsChange.ts"],"sourcesContent":["// Working-tree change stream (EDITOR_AS_APP_SPEC §4.2). The host pushes the\n// repo-relative paths that just changed in the working tree — from ANY writer: an\n// agent's port write, a host `editor:write` action (create/delete/rename), or the\n// preview's own copy-on-write. A working-tree observer (the editor app, the file\n// explorer) reacts by re-reading the affected files instead of polling.\n//\n// Elevated `editor:read` — a previewed app holds no `editor:read`, so it never sees\n// the stream (the host channel ACL withholds it). Push-only: there is no past-event\n// state worth polling, so the empty initial stands until the first write.\n//\n// Origin-exclusion is the CONSUMER's responsibility: the editor must ignore the\n// echo of its OWN write (a debounced write lags the buffer, so re-reading it as\n// \"external\" would surface a false conflict). Compare the changed file's bytes to\n// what you last wrote; if they match, it is your echo, not an external change.\nimport { createPushChannel } from './pushChannel';\n\n/** One working-tree change batch the host pushes: the changed paths plus an epoch. */\nexport interface FsChange {\n /** Repo-relative paths (leading slash, e.g. `/src/App.tsx`) that just changed. */\n paths: string[];\n /**\n * Monotonic batch id — bumps on every change even if the path set repeats, so a\n * subscriber re-fires for a second edit to the same file (the value is never\n * deduplicated away). `0` is the pre-first-event initial.\n */\n epoch: number;\n}\n\nconst isStringArray = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((p) => typeof p === 'string');\n\nconst channel = createPushChannel<FsChange>({\n pushType: 'fs-change',\n initial: { paths: [], epoch: 0 },\n parse: (msg) =>\n isStringArray(msg.paths) && typeof msg.epoch === 'number'\n ? { paths: msg.paths, epoch: msg.epoch }\n : undefined,\n});\n\n/** The most recent working-tree change batch (the empty initial until the first). */\nexport const getFsChange = (): FsChange => channel.get();\n\n/**\n * Subscribe to working-tree changes. The listener fires immediately with the\n * current batch, then on every host push. Returns an unsubscribe fn. The common\n * use: re-read an open file when its path appears in `change.paths`.\n */\nexport const onFsChange = (listener: (change: FsChange) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current working-tree change batch, re-rendering on every push. */\nexport const useFsChange = (): FsChange => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,yBAAkC;AAclC,MAAM,gBAAgB,CAAC,MACrB,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAE1D,MAAM,cAAU,sCAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AAAA,EAC/B,OAAO,CAAC,QACN,cAAc,IAAI,KAAK,KAAK,OAAO,IAAI,UAAU,WAC7C,EAAE,OAAO,IAAI,OAAO,OAAO,IAAI,MAAM,IACrC;AACR,CAAC;AAGM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAOhD,MAAM,aAAa,CAAC,aACzB,QAAQ,SAAS,QAAQ;AAGpB,MAAM,cAAc,MAAgB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/onFsChange.ts"],"sourcesContent":["// Working-tree change stream (EDITOR_AS_APP_SPEC §4.2). The host pushes the\n// repo-relative paths that just changed in the working tree — from ANY writer: an\n// agent's port write, a host `editor:write` action (create/delete/rename), or the\n// preview's own copy-on-write. A working-tree observer (the editor app, the file\n// explorer) reacts by re-reading the affected files instead of polling.\n//\n// Elevated `editor:read` — a previewed app holds no `editor:read`, so it never sees\n// the stream (the host channel ACL withholds it). Push-only: there is no past-event\n// state worth polling, so the empty initial stands until the first write.\n//\n// Origin-exclusion is the CONSUMER's responsibility: the editor must ignore the\n// echo of its OWN write (a debounced write lags the buffer, so re-reading it as\n// \"external\" would surface a false conflict). Compare the changed file's bytes to\n// what you last wrote; if they match, it is your echo, not an external change.\nimport { createPushChannel } from './pushChannel';\nimport { FS_CHANGE } from './generated/protocol';\n\n/** One working-tree change batch the host pushes: the changed paths plus an epoch. */\nexport interface FsChange {\n /** Repo-relative paths (leading slash, e.g. `/src/App.tsx`) that just changed. */\n paths: string[];\n /**\n * Monotonic batch id — bumps on every change even if the path set repeats, so a\n * subscriber re-fires for a second edit to the same file (the value is never\n * deduplicated away). `0` is the pre-first-event initial.\n */\n epoch: number;\n}\n\nconst isStringArray = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((p) => typeof p === 'string');\n\nconst channel = createPushChannel<FsChange>({\n pushType: FS_CHANGE,\n initial: { paths: [], epoch: 0 },\n parse: (msg) =>\n isStringArray(msg.paths) && typeof msg.epoch === 'number'\n ? { paths: msg.paths, epoch: msg.epoch }\n : undefined,\n});\n\n/** The most recent working-tree change batch (the empty initial until the first). */\nexport const getFsChange = (): FsChange => channel.get();\n\n/**\n * Subscribe to working-tree changes. The listener fires immediately with the\n * current batch, then on every host push. Returns an unsubscribe fn. The common\n * use: re-read an open file when its path appears in `change.paths`.\n */\nexport const onFsChange = (listener: (change: FsChange) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current working-tree change batch, re-rendering on every push. */\nexport const useFsChange = (): FsChange => channel.use();\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,yBAAkC;AAClC,sBAA0B;AAc1B,MAAM,gBAAgB,CAAC,MACrB,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAE1D,MAAM,cAAU,sCAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AAAA,EAC/B,OAAO,CAAC,QACN,cAAc,IAAI,KAAK,KAAK,OAAO,IAAI,UAAU,WAC7C,EAAE,OAAO,IAAI,OAAO,OAAO,IAAI,MAAM,IACrC;AACR,CAAC;AAGM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAOhD,MAAM,aAAa,CAAC,aACzB,QAAQ,SAAS,QAAQ;AAGpB,MAAM,cAAc,MAAgB,QAAQ,IAAI;","names":[]}
@@ -1,8 +1,9 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { createPushChannel } from "./pushChannel";
3
+ import { FS_CHANGE } from "./generated/protocol";
3
4
  const isStringArray = (v) => Array.isArray(v) && v.every((p) => typeof p === "string");
4
5
  const channel = createPushChannel({
5
- pushType: "fs-change",
6
+ pushType: FS_CHANGE,
6
7
  initial: { paths: [], epoch: 0 },
7
8
  parse: (msg) => isStringArray(msg.paths) && typeof msg.epoch === "number" ? { paths: msg.paths, epoch: msg.epoch } : void 0
8
9
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/onFsChange.ts"],"sourcesContent":["// Working-tree change stream (EDITOR_AS_APP_SPEC §4.2). The host pushes the\n// repo-relative paths that just changed in the working tree — from ANY writer: an\n// agent's port write, a host `editor:write` action (create/delete/rename), or the\n// preview's own copy-on-write. A working-tree observer (the editor app, the file\n// explorer) reacts by re-reading the affected files instead of polling.\n//\n// Elevated `editor:read` — a previewed app holds no `editor:read`, so it never sees\n// the stream (the host channel ACL withholds it). Push-only: there is no past-event\n// state worth polling, so the empty initial stands until the first write.\n//\n// Origin-exclusion is the CONSUMER's responsibility: the editor must ignore the\n// echo of its OWN write (a debounced write lags the buffer, so re-reading it as\n// \"external\" would surface a false conflict). Compare the changed file's bytes to\n// what you last wrote; if they match, it is your echo, not an external change.\nimport { createPushChannel } from './pushChannel';\n\n/** One working-tree change batch the host pushes: the changed paths plus an epoch. */\nexport interface FsChange {\n /** Repo-relative paths (leading slash, e.g. `/src/App.tsx`) that just changed. */\n paths: string[];\n /**\n * Monotonic batch id — bumps on every change even if the path set repeats, so a\n * subscriber re-fires for a second edit to the same file (the value is never\n * deduplicated away). `0` is the pre-first-event initial.\n */\n epoch: number;\n}\n\nconst isStringArray = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((p) => typeof p === 'string');\n\nconst channel = createPushChannel<FsChange>({\n pushType: 'fs-change',\n initial: { paths: [], epoch: 0 },\n parse: (msg) =>\n isStringArray(msg.paths) && typeof msg.epoch === 'number'\n ? { paths: msg.paths, epoch: msg.epoch }\n : undefined,\n});\n\n/** The most recent working-tree change batch (the empty initial until the first). */\nexport const getFsChange = (): FsChange => channel.get();\n\n/**\n * Subscribe to working-tree changes. The listener fires immediately with the\n * current batch, then on every host push. Returns an unsubscribe fn. The common\n * use: re-read an open file when its path appears in `change.paths`.\n */\nexport const onFsChange = (listener: (change: FsChange) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current working-tree change batch, re-rendering on every push. */\nexport const useFsChange = (): FsChange => channel.use();\n"],"mappings":";AAcA,SAAS,yBAAyB;AAclC,MAAM,gBAAgB,CAAC,MACrB,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAE1D,MAAM,UAAU,kBAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AAAA,EAC/B,OAAO,CAAC,QACN,cAAc,IAAI,KAAK,KAAK,OAAO,IAAI,UAAU,WAC7C,EAAE,OAAO,IAAI,OAAO,OAAO,IAAI,MAAM,IACrC;AACR,CAAC;AAGM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAOhD,MAAM,aAAa,CAAC,aACzB,QAAQ,SAAS,QAAQ;AAGpB,MAAM,cAAc,MAAgB,QAAQ,IAAI;","names":[]}
1
+ {"version":3,"sources":["../src/onFsChange.ts"],"sourcesContent":["// Working-tree change stream (EDITOR_AS_APP_SPEC §4.2). The host pushes the\n// repo-relative paths that just changed in the working tree — from ANY writer: an\n// agent's port write, a host `editor:write` action (create/delete/rename), or the\n// preview's own copy-on-write. A working-tree observer (the editor app, the file\n// explorer) reacts by re-reading the affected files instead of polling.\n//\n// Elevated `editor:read` — a previewed app holds no `editor:read`, so it never sees\n// the stream (the host channel ACL withholds it). Push-only: there is no past-event\n// state worth polling, so the empty initial stands until the first write.\n//\n// Origin-exclusion is the CONSUMER's responsibility: the editor must ignore the\n// echo of its OWN write (a debounced write lags the buffer, so re-reading it as\n// \"external\" would surface a false conflict). Compare the changed file's bytes to\n// what you last wrote; if they match, it is your echo, not an external change.\nimport { createPushChannel } from './pushChannel';\nimport { FS_CHANGE } from './generated/protocol';\n\n/** One working-tree change batch the host pushes: the changed paths plus an epoch. */\nexport interface FsChange {\n /** Repo-relative paths (leading slash, e.g. `/src/App.tsx`) that just changed. */\n paths: string[];\n /**\n * Monotonic batch id — bumps on every change even if the path set repeats, so a\n * subscriber re-fires for a second edit to the same file (the value is never\n * deduplicated away). `0` is the pre-first-event initial.\n */\n epoch: number;\n}\n\nconst isStringArray = (v: unknown): v is string[] =>\n Array.isArray(v) && v.every((p) => typeof p === 'string');\n\nconst channel = createPushChannel<FsChange>({\n pushType: FS_CHANGE,\n initial: { paths: [], epoch: 0 },\n parse: (msg) =>\n isStringArray(msg.paths) && typeof msg.epoch === 'number'\n ? { paths: msg.paths, epoch: msg.epoch }\n : undefined,\n});\n\n/** The most recent working-tree change batch (the empty initial until the first). */\nexport const getFsChange = (): FsChange => channel.get();\n\n/**\n * Subscribe to working-tree changes. The listener fires immediately with the\n * current batch, then on every host push. Returns an unsubscribe fn. The common\n * use: re-read an open file when its path appears in `change.paths`.\n */\nexport const onFsChange = (listener: (change: FsChange) => void): (() => void) =>\n channel.onChange(listener);\n\n/** React hook: the current working-tree change batch, re-rendering on every push. */\nexport const useFsChange = (): FsChange => channel.use();\n"],"mappings":";AAcA,SAAS,yBAAyB;AAClC,SAAS,iBAAiB;AAc1B,MAAM,gBAAgB,CAAC,MACrB,MAAM,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAE1D,MAAM,UAAU,kBAA4B;AAAA,EAC1C,UAAU;AAAA,EACV,SAAS,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE;AAAA,EAC/B,OAAO,CAAC,QACN,cAAc,IAAI,KAAK,KAAK,OAAO,IAAI,UAAU,WAC7C,EAAE,OAAO,IAAI,OAAO,OAAO,IAAI,MAAM,IACrC;AACR,CAAC;AAGM,MAAM,cAAc,MAAgB,QAAQ,IAAI;AAOhD,MAAM,aAAa,CAAC,aACzB,QAAQ,SAAS,QAAQ;AAGpB,MAAM,cAAc,MAAgB,QAAQ,IAAI;","names":[]}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var protocolSchemes_exports = {};
20
+ __export(protocolSchemes_exports, {
21
+ SCHEMES: () => SCHEMES
22
+ });
23
+ module.exports = __toCommonJS(protocolSchemes_exports);
24
+ var import_protocol = require("./generated/protocol");
25
+ const PREFIX = "protocol-";
26
+ const schemeOf = (name) => name.slice(PREFIX.length);
27
+ const SCHEMES = {
28
+ [import_protocol.PROTOCOL_CONTRIBUTE]: schemeOf(import_protocol.PROTOCOL_CONTRIBUTE),
29
+ [import_protocol.PROTOCOL_DND]: schemeOf(import_protocol.PROTOCOL_DND),
30
+ [import_protocol.PROTOCOL_EDITOR]: schemeOf(import_protocol.PROTOCOL_EDITOR),
31
+ [import_protocol.PROTOCOL_FETCH]: schemeOf(import_protocol.PROTOCOL_FETCH),
32
+ [import_protocol.PROTOCOL_IPC]: schemeOf(import_protocol.PROTOCOL_IPC),
33
+ [import_protocol.PROTOCOL_LAUNCH]: schemeOf(import_protocol.PROTOCOL_LAUNCH),
34
+ [import_protocol.PROTOCOL_LLM]: schemeOf(import_protocol.PROTOCOL_LLM),
35
+ [import_protocol.PROTOCOL_SECRETS]: schemeOf(import_protocol.PROTOCOL_SECRETS),
36
+ [import_protocol.PROTOCOL_SETTINGS]: schemeOf(import_protocol.PROTOCOL_SETTINGS),
37
+ [import_protocol.PROTOCOL_SPACES]: schemeOf(import_protocol.PROTOCOL_SPACES),
38
+ [import_protocol.PROTOCOL_TASK]: schemeOf(import_protocol.PROTOCOL_TASK),
39
+ [import_protocol.PROTOCOL_THEME]: schemeOf(import_protocol.PROTOCOL_THEME),
40
+ [import_protocol.PROTOCOL_VCS]: schemeOf(import_protocol.PROTOCOL_VCS)
41
+ };
42
+ // Annotate the CommonJS export names for ESM import in node:
43
+ 0 && (module.exports = {
44
+ SCHEMES
45
+ });
46
+ //# sourceMappingURL=protocolSchemes.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AA2BA,sBAcO;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,mCAAmB,GAAG,SAAS,mCAAmB;AAAA,EACnD,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,4BAAY,GAAG,SAAS,4BAAY;AAAA,EACrC,CAAC,gCAAgB,GAAG,SAAS,gCAAgB;AAAA,EAC7C,CAAC,iCAAiB,GAAG,SAAS,iCAAiB;AAAA,EAC/C,CAAC,+BAAe,GAAG,SAAS,+BAAe;AAAA,EAC3C,CAAC,6BAAa,GAAG,SAAS,6BAAa;AAAA,EACvC,CAAC,8BAAc,GAAG,SAAS,8BAAc;AAAA,EACzC,CAAC,4BAAY,GAAG,SAAS,4BAAY;AACvC;","names":[]}
@@ -0,0 +1,18 @@
1
+ /** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */
2
+ declare const SCHEMES: {
3
+ readonly "protocol-contribute": "contribute";
4
+ readonly "protocol-dnd": "dnd";
5
+ readonly "protocol-editor": "editor";
6
+ readonly "protocol-fetch": "fetch";
7
+ readonly "protocol-ipc": "ipc";
8
+ readonly "protocol-launch": "launch";
9
+ readonly "protocol-llm": "llm";
10
+ readonly "protocol-secrets": "secrets";
11
+ readonly "protocol-settings": "settings";
12
+ readonly "protocol-spaces": "spaces";
13
+ readonly "protocol-task": "task";
14
+ readonly "protocol-theme": "theme";
15
+ readonly "protocol-vcs": "vcs";
16
+ };
17
+
18
+ export { SCHEMES };
@@ -0,0 +1,18 @@
1
+ /** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */
2
+ declare const SCHEMES: {
3
+ readonly "protocol-contribute": "contribute";
4
+ readonly "protocol-dnd": "dnd";
5
+ readonly "protocol-editor": "editor";
6
+ readonly "protocol-fetch": "fetch";
7
+ readonly "protocol-ipc": "ipc";
8
+ readonly "protocol-launch": "launch";
9
+ readonly "protocol-llm": "llm";
10
+ readonly "protocol-secrets": "secrets";
11
+ readonly "protocol-settings": "settings";
12
+ readonly "protocol-spaces": "spaces";
13
+ readonly "protocol-task": "task";
14
+ readonly "protocol-theme": "theme";
15
+ readonly "protocol-vcs": "vcs";
16
+ };
17
+
18
+ export { SCHEMES };
@@ -0,0 +1,37 @@
1
+ import "./chunk-VHAA22YE.js";
2
+ import {
3
+ PROTOCOL_CONTRIBUTE,
4
+ PROTOCOL_DND,
5
+ PROTOCOL_EDITOR,
6
+ PROTOCOL_FETCH,
7
+ PROTOCOL_IPC,
8
+ PROTOCOL_LAUNCH,
9
+ PROTOCOL_LLM,
10
+ PROTOCOL_SECRETS,
11
+ PROTOCOL_SETTINGS,
12
+ PROTOCOL_SPACES,
13
+ PROTOCOL_TASK,
14
+ PROTOCOL_THEME,
15
+ PROTOCOL_VCS
16
+ } from "./generated/protocol";
17
+ const PREFIX = "protocol-";
18
+ const schemeOf = (name) => name.slice(PREFIX.length);
19
+ const SCHEMES = {
20
+ [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),
21
+ [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),
22
+ [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),
23
+ [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),
24
+ [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),
25
+ [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),
26
+ [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),
27
+ [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),
28
+ [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),
29
+ [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),
30
+ [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),
31
+ [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),
32
+ [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS)
33
+ };
34
+ export {
35
+ SCHEMES
36
+ };
37
+ //# sourceMappingURL=protocolSchemes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocolSchemes.ts"],"sourcesContent":["// The `protocol-<scheme>` SCHEMES, derived from the wire names.\n//\n// `protocolRequest(scheme, method, params)` takes the scheme — `'theme'` — while the\n// wire name the frame dispatches on is `'protocol-theme'`; the frame adds the prefix.\n// So the typed service wrappers cannot pass the published `PROTOCOL_*` constant\n// directly, and spelling the scheme inline would put a second, unguarded copy of the\n// name back in the tree — exactly what R3-274c removes.\n//\n// Instead the schemes are *derived* from the wire names, keyed BY the wire name:\n//\n// protocolRequest(SCHEMES[PROTOCOL_THEME], 'set', [{ theme }])\n//\n// Keying by the constant is what makes the derivation unfalsifiable — there is no\n// second place to name the family, so there is no pair to get wrong. `schemeOf`\n// returns a template-literal conditional, so each value has a literal type (`'theme'`),\n// which buys two more things:\n//\n// - a wire name that stops matching `protocol-*` stops compiling here, rather than\n// silently producing an empty scheme at runtime;\n// - `check-protocol-snapshot.mjs` resolves `SCHEMES[PROTOCOL_THEME]` through the type\n// checker exactly like a plain literal, so the call sites stay visible to the gate.\n//\n// One export, deliberately: `./*` is a public subpath, so every name added here is\n// public API forever (ways_of_working §6, additive-only).\n//\n// This module is NOT generated — the derivation is the content — so it lives outside\n// `src/generated/`.\nimport {\n PROTOCOL_CONTRIBUTE,\n PROTOCOL_DND,\n PROTOCOL_EDITOR,\n PROTOCOL_FETCH,\n PROTOCOL_IPC,\n PROTOCOL_LAUNCH,\n PROTOCOL_LLM,\n PROTOCOL_SECRETS,\n PROTOCOL_SETTINGS,\n PROTOCOL_SPACES,\n PROTOCOL_TASK,\n PROTOCOL_THEME,\n PROTOCOL_VCS,\n} from './generated/protocol';\n\nconst PREFIX = 'protocol-';\n\n/** The scheme half of a `protocol-<scheme>` wire name. */\ntype SchemeOf<N extends string> = N extends `${typeof PREFIX}${infer S}` ? S : never;\n\n/**\n * `'protocol-theme'` → `'theme'`, as a literal type.\n *\n * The cast is the only place the derivation is asserted rather than computed; the\n * `N extends \\`protocol-${string}\\`` bound is what makes it sound — a wire name that is\n * not scheme-shaped is a compile error at the call, not a `never` at runtime.\n */\nconst schemeOf = <N extends `${typeof PREFIX}${string}`>(name: N): SchemeOf<N> =>\n name.slice(PREFIX.length) as SchemeOf<N>;\n\n/** Every `protocol-*` scheme the SDK speaks, keyed by its wire name. */\nexport const SCHEMES = {\n [PROTOCOL_CONTRIBUTE]: schemeOf(PROTOCOL_CONTRIBUTE),\n [PROTOCOL_DND]: schemeOf(PROTOCOL_DND),\n [PROTOCOL_EDITOR]: schemeOf(PROTOCOL_EDITOR),\n [PROTOCOL_FETCH]: schemeOf(PROTOCOL_FETCH),\n [PROTOCOL_IPC]: schemeOf(PROTOCOL_IPC),\n [PROTOCOL_LAUNCH]: schemeOf(PROTOCOL_LAUNCH),\n [PROTOCOL_LLM]: schemeOf(PROTOCOL_LLM),\n [PROTOCOL_SECRETS]: schemeOf(PROTOCOL_SECRETS),\n [PROTOCOL_SETTINGS]: schemeOf(PROTOCOL_SETTINGS),\n [PROTOCOL_SPACES]: schemeOf(PROTOCOL_SPACES),\n [PROTOCOL_TASK]: schemeOf(PROTOCOL_TASK),\n [PROTOCOL_THEME]: schemeOf(PROTOCOL_THEME),\n [PROTOCOL_VCS]: schemeOf(PROTOCOL_VCS),\n} as const;\n"],"mappings":";AA2BA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,SAAS;AAYf,MAAM,WAAW,CAAwC,SACvD,KAAK,MAAM,OAAO,MAAM;AAGnB,MAAM,UAAU;AAAA,EACrB,CAAC,mBAAmB,GAAG,SAAS,mBAAmB;AAAA,EACnD,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,YAAY,GAAG,SAAS,YAAY;AAAA,EACrC,CAAC,gBAAgB,GAAG,SAAS,gBAAgB;AAAA,EAC7C,CAAC,iBAAiB,GAAG,SAAS,iBAAiB;AAAA,EAC/C,CAAC,eAAe,GAAG,SAAS,eAAe;AAAA,EAC3C,CAAC,aAAa,GAAG,SAAS,aAAa;AAAA,EACvC,CAAC,cAAc,GAAG,SAAS,cAAc;AAAA,EACzC,CAAC,YAAY,GAAG,SAAS,YAAY;AACvC;","names":[]}
package/dist/routing.cjs CHANGED
@@ -35,6 +35,7 @@ var import_TinkerableContext = require("./TinkerableContext");
35
35
  var import_routeMatch = require("./routeMatch");
36
36
  var import_urlUtils = require("./urlUtils");
37
37
  var import_pathUtils = require("./pathUtils");
38
+ var import_protocol = require("./generated/protocol");
38
39
  const useTinkerableLink = (newSandboxLocation) => {
39
40
  const { outerHref, navigationState: navigation } = (0, import_react.use)(import_TinkerableContext.TinkerableContext);
40
41
  let newNavigationState = (0, import_urlUtils.parseTarget)(newSandboxLocation, navigation);
@@ -101,7 +102,7 @@ const navigate = (target, opts) => {
101
102
  } catch {
102
103
  }
103
104
  }
104
- (0, import_sandboxUtils.sendMessage)("urlchange", {
105
+ (0, import_sandboxUtils.sendMessage)(import_protocol.URLCHANGE, {
105
106
  url: target,
106
107
  back: false,
107
108
  forward: false,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule,\n pathParameters?: Record<string, string>;\n}\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation)\n } else {\n newNavigationState.sandboxPath = newSandboxLocation\n }\n return constructUrl(outerHref, newNavigationState);\n}\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (routingSpec:RoutingSpec, navigationState: NavigationState): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n}\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {navigationState: {routingRule, pathParameters}} = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (\n target: string,\n opts?: { viewedDocument?: string | null },\n) => {\n console.log(`[Sandbox] Navigating to ${target}`)\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n sendMessage('urlchange', {\n url: target,\n back: false,\n forward: false,\n ...declared,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CW;AA7CX,mBAAgC;AAEhC,0BAA4B;AAC5B,+BAAmD;AAEnD,wBAA2B;AAC3B,sBAA0D;AAC1D,uBAA0B;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,QAAI,kBAAI,0CAAiB;AACxE,MAAI,yBAAqB,6BAAY,oBAAoB,UAAU;AACnE,MAAI,KAAC,gCAAe,kBAAkB,GAAG;AACvC,uBAAmB,kBAAc,4BAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,aAAO,8BAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAAC,aAAyB,oBAAqE;AAC7H,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,qBAAiB,8BAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,4CAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,cAAU,yBAAW,0CAAiB;AAC5C,QAAM,EAAC,iBAAiB,EAAC,aAAa,eAAc,EAAC,IAAI;AACzD,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,UAC3B,kBAAI,0CAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,QAAI,kBAAI,0CAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA4BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CACtB,QACA,SACG;AACH,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,uCAAY,aAAa;AAAA,IACvB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\nimport { URLCHANGE } from './generated/protocol';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule,\n pathParameters?: Record<string, string>;\n}\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation)\n } else {\n newNavigationState.sandboxPath = newSandboxLocation\n }\n return constructUrl(outerHref, newNavigationState);\n}\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (routingSpec:RoutingSpec, navigationState: NavigationState): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n}\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {navigationState: {routingRule, pathParameters}} = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (\n target: string,\n opts?: { viewedDocument?: string | null },\n) => {\n console.log(`[Sandbox] Navigating to ${target}`)\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n sendMessage(URLCHANGE, {\n url: target,\n back: false,\n forward: false,\n ...declared,\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA+CW;AA9CX,mBAAgC;AAEhC,0BAA4B;AAC5B,+BAAmD;AAEnD,wBAA2B;AAC3B,sBAA0D;AAC1D,uBAA0B;AAC1B,sBAA0B;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,QAAI,kBAAI,0CAAiB;AACxE,MAAI,yBAAqB,6BAAY,oBAAoB,UAAU;AACnE,MAAI,KAAC,gCAAe,kBAAkB,GAAG;AACvC,uBAAmB,kBAAc,4BAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,aAAO,8BAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAAC,aAAyB,oBAAqE;AAC7H,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,qBAAiB,8BAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,4CAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,cAAU,yBAAW,0CAAiB;AAC5C,QAAM,EAAC,iBAAiB,EAAC,aAAa,eAAc,EAAC,IAAI;AACzD,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,UAC3B,kBAAI,0CAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,QAAI,kBAAI,0CAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA4BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CACtB,QACA,SACG;AACH,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,uCAAY,2BAAW;AAAA,IACrB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
package/dist/routing.js CHANGED
@@ -6,6 +6,7 @@ import { TinkerableContext } from "./TinkerableContext";
6
6
  import { matchRoute } from "./routeMatch";
7
7
  import { constructUrl, isAbsolutePath, parseTarget } from "./urlUtils";
8
8
  import { joinPaths } from "./pathUtils";
9
+ import { URLCHANGE } from "./generated/protocol";
9
10
  const useTinkerableLink = (newSandboxLocation) => {
10
11
  const { outerHref, navigationState: navigation } = use(TinkerableContext);
11
12
  let newNavigationState = parseTarget(newSandboxLocation, navigation);
@@ -72,7 +73,7 @@ const navigate = (target, opts) => {
72
73
  } catch {
73
74
  }
74
75
  }
75
- sendMessage("urlchange", {
76
+ sendMessage(URLCHANGE, {
76
77
  url: target,
77
78
  back: false,
78
79
  forward: false,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule,\n pathParameters?: Record<string, string>;\n}\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation)\n } else {\n newNavigationState.sandboxPath = newSandboxLocation\n }\n return constructUrl(outerHref, newNavigationState);\n}\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (routingSpec:RoutingSpec, navigationState: NavigationState): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n}\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {navigationState: {routingRule, pathParameters}} = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (\n target: string,\n opts?: { viewedDocument?: string | null },\n) => {\n console.log(`[Sandbox] Navigating to ${target}`)\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n sendMessage('urlchange', {\n url: target,\n back: false,\n forward: false,\n ...declared,\n });\n};\n"],"mappings":";AA8CW;AA7CX,SAAS,KAAK,kBAAkB;AAEhC,SAAS,mBAAmB;AAC5B,SAA0B,yBAAyB;AAEnD,SAAS,kBAAkB;AAC3B,SAAS,cAAc,gBAAgB,mBAAmB;AAC1D,SAAS,iBAAiB;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,IAAI,IAAI,iBAAiB;AACxE,MAAI,qBAAqB,YAAY,oBAAoB,UAAU;AACnE,MAAI,CAAC,eAAe,kBAAkB,GAAG;AACvC,uBAAmB,cAAc,UAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,SAAO,aAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAAC,aAAyB,oBAAqE;AAC7H,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,iBAAiB,WAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,oBAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,UAAU,WAAW,iBAAiB;AAC5C,QAAM,EAAC,iBAAiB,EAAC,aAAa,eAAc,EAAC,IAAI;AACzD,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,MAC3B,IAAI,iBAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,IAAI,IAAI,iBAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA4BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CACtB,QACA,SACG;AACH,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,cAAY,aAAa;AAAA,IACvB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../src/routing.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { use, useContext } from 'react';\n\nimport { sendMessage } from './sandboxUtils';\nimport { NavigationState, TinkerableContext } from './TinkerableContext';\nimport { RouteParams, RoutingRule, RoutingSpec } from './RoutingSpec';\nimport { matchRoute } from './routeMatch';\nimport { constructUrl, isAbsolutePath, parseTarget } from './urlUtils';\nimport { joinPaths } from './pathUtils';\nimport { URLCHANGE } from './generated/protocol';\n\n/** The result of matching a path: the winning {@link RoutingRule} plus its captured params. */\nexport type AppliedRoutingRule = {\n routingRule: RoutingRule,\n pathParameters?: Record<string, string>;\n}\n\n/** Build the full outer href for an in-app target (absolute `sandboxPath` or a\n * path relative to the current route), e.g. for an `href` attribute. */\nexport const useTinkerableLink = (newSandboxLocation: string) => {\n const { outerHref, navigationState: navigation } = use(TinkerableContext);\n let newNavigationState = parseTarget(newSandboxLocation, navigation);\n if (!isAbsolutePath(newSandboxLocation)) {\n newNavigationState.sandboxPath = joinPaths(navigation.sandboxPath, newSandboxLocation)\n } else {\n newNavigationState.sandboxPath = newSandboxLocation\n }\n return constructUrl(outerHref, newNavigationState);\n}\n\n/** Find the first rule in `routingSpec` whose pattern matches the current\n * `sandboxPath`, returning it with the captured params (or `undefined`). */\nexport const applyRoutingRule = (routingSpec:RoutingSpec, navigationState: NavigationState): AppliedRoutingRule | undefined => {\n const { sandboxPath } = navigationState;\n for (const routingRule of routingSpec.routes) {\n const pathParameters = matchRoute(routingRule.pattern, sandboxPath);\n if (pathParameters) {\n return { routingRule, pathParameters };\n }\n }\n return undefined;\n}\n\n/** Render a matched rule, passing params to a `component` and falling back to `element`/`reactNode`. */\nexport const renderRoute = (routingRule: RoutingRule, params: RouteParams): ReactNode => {\n if (routingRule.component) {\n const Component = routingRule.component;\n return <Component params={params} />;\n }\n return routingRule.element ?? routingRule.reactNode ?? null;\n};\n\n/** Render the route matched for the current location (set up by `boot`'s route table). */\nexport const Router = () => {\n const context = useContext(TinkerableContext);\n const {navigationState: {routingRule, pathParameters}} = context;\n if (!routingRule) {\n // TODO: better error\n throw new Error(`No route registered for path ${context.navigationState.sandboxPath}!`);\n }\n\n return renderRoute(routingRule, pathParameters ?? {});\n};\n\n/** Read the current route's matched params (`:name` segments and the `*` wildcard). */\nexport const useRouteParams = <T extends RouteParams = RouteParams>(): T =>\n (use(TinkerableContext).navigationState.pathParameters ?? {}) as T;\n\n/**\n * Read the current route: the matched rule's `name`, its `params`, the app-owned\n * `sandboxPath`, and the read-only platform prefix fields (`mode`, `provider`,\n * `namespace`, `repository`, `ref`) — e.g. to tell `/edit` from `/present`.\n */\nexport const useRoute = () => {\n const { navigationState } = use(TinkerableContext);\n const { routingRule, pathParameters, sandboxPath, mode, provider, namespace, repository, ref } = navigationState;\n return {\n name: routingRule?.name,\n params: (pathParameters ?? {}) as RouteParams,\n sandboxPath,\n mode,\n provider,\n namespace,\n repository,\n ref,\n };\n};\n\n\n/**\n * Navigate within the app. Messages the host to update the URL; the host then\n * pushes the new href back, which drives the actual route change.\n *\n * `opts.viewedDocument` (R3-268) optionally declares which WORKING-TREE file\n * this destination renders — a tri-state rider on the navigation event:\n * - omit the option entirely → the host derives the hint from the URL's\n * `files/` suffix convention (the zero-SDK default);\n * - `null` → this view shows no file (clears the highlight — tag pages,\n * search, home views);\n * - a repo-relative path (the CORPUS path under dispatch — only the viewer\n * can map its own key space) → the file explorer highlights it.\n * The hint is highlight-only by contract (it never scrolls, never moves focus\n * or panes, never switches the editor) and is validated host-side for\n * existence — a wrong path degrades to \"no highlight\", never an error. The\n * host remembers declarations per URL, so back/forward reproduces them\n * without re-announcement.\n */\n// R3-268: an app-registered rule mapping a navigation TARGET to its viewed\n// document, consulted by `navigate()` whenever the caller did not declare one\n// explicitly. Registered ONCE (e.g. at boot) so an app whose links all flow\n// through `<Link>`/`navigate` gets correct declarations everywhere without\n// threading an option through every call site. Return `undefined` for \"no\n// declaration\" (the host falls back to the URL convention), `null` for \"this\n// view shows no file\", or a working-tree repo-relative path.\nlet viewedDocumentResolver: ((targetHref: string) => string | null | undefined) | null = null;\n\n/** Register the app's route→viewed-document rule (R3-268); pass `null` to clear. */\nexport const setViewedDocumentResolver = (\n resolver: ((targetHref: string) => string | null | undefined) | null,\n): void => {\n viewedDocumentResolver = resolver;\n};\n\nexport const navigate = (\n target: string,\n opts?: { viewedDocument?: string | null },\n) => {\n console.log(`[Sandbox] Navigating to ${target}`)\n // Explicit option first; else the registered resolver; else nothing on the\n // wire (the host derives from the URL convention). A resolver throw is\n // swallowed to \"no declaration\" — a mapping bug must never break navigation.\n let declared: { viewedDocument: string | null } | Record<string, never> = {};\n if (opts && 'viewedDocument' in opts) {\n declared = { viewedDocument: opts.viewedDocument ?? null };\n } else if (viewedDocumentResolver) {\n try {\n const v = viewedDocumentResolver(target);\n if (v !== undefined) declared = { viewedDocument: v };\n } catch {\n /* no declaration */\n }\n }\n sendMessage(URLCHANGE, {\n url: target,\n back: false,\n forward: false,\n ...declared,\n });\n};\n"],"mappings":";AA+CW;AA9CX,SAAS,KAAK,kBAAkB;AAEhC,SAAS,mBAAmB;AAC5B,SAA0B,yBAAyB;AAEnD,SAAS,kBAAkB;AAC3B,SAAS,cAAc,gBAAgB,mBAAmB;AAC1D,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;AAUnB,MAAM,oBAAoB,CAAC,uBAA+B;AAC/D,QAAM,EAAE,WAAW,iBAAiB,WAAW,IAAI,IAAI,iBAAiB;AACxE,MAAI,qBAAqB,YAAY,oBAAoB,UAAU;AACnE,MAAI,CAAC,eAAe,kBAAkB,GAAG;AACvC,uBAAmB,cAAc,UAAU,WAAW,aAAa,kBAAkB;AAAA,EACvF,OAAO;AACL,uBAAmB,cAAc;AAAA,EACnC;AACA,SAAO,aAAa,WAAW,kBAAkB;AACnD;AAIO,MAAM,mBAAmB,CAAC,aAAyB,oBAAqE;AAC7H,QAAM,EAAE,YAAY,IAAI;AACxB,aAAW,eAAe,YAAY,QAAQ;AAC5C,UAAM,iBAAiB,WAAW,YAAY,SAAS,WAAW;AAClE,QAAI,gBAAgB;AAClB,aAAO,EAAE,aAAa,eAAe;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,cAAc,CAAC,aAA0B,WAAmC;AACvF,MAAI,YAAY,WAAW;AACzB,UAAM,YAAY,YAAY;AAC9B,WAAO,oBAAC,aAAU,QAAgB;AAAA,EACpC;AACA,SAAO,YAAY,WAAW,YAAY,aAAa;AACzD;AAGO,MAAM,SAAS,MAAM;AAC1B,QAAM,UAAU,WAAW,iBAAiB;AAC5C,QAAM,EAAC,iBAAiB,EAAC,aAAa,eAAc,EAAC,IAAI;AACzD,MAAI,CAAC,aAAa;AAEhB,UAAM,IAAI,MAAM,gCAAgC,QAAQ,gBAAgB,WAAW,GAAG;AAAA,EACxF;AAEA,SAAO,YAAY,aAAa,kBAAkB,CAAC,CAAC;AACtD;AAGO,MAAM,iBAAiB,MAC3B,IAAI,iBAAiB,EAAE,gBAAgB,kBAAkB,CAAC;AAOtD,MAAM,WAAW,MAAM;AAC5B,QAAM,EAAE,gBAAgB,IAAI,IAAI,iBAAiB;AACjD,QAAM,EAAE,aAAa,gBAAgB,aAAa,MAAM,UAAU,WAAW,YAAY,IAAI,IAAI;AACjG,SAAO;AAAA,IACL,MAAM,aAAa;AAAA,IACnB,QAAS,kBAAkB,CAAC;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA4BA,IAAI,yBAAqF;AAGlF,MAAM,4BAA4B,CACvC,aACS;AACT,2BAAyB;AAC3B;AAEO,MAAM,WAAW,CACtB,QACA,SACG;AACH,UAAQ,IAAI,2BAA2B,MAAM,EAAE;AAI/C,MAAI,WAAsE,CAAC;AAC3E,MAAI,QAAQ,oBAAoB,MAAM;AACpC,eAAW,EAAE,gBAAgB,KAAK,kBAAkB,KAAK;AAAA,EAC3D,WAAW,wBAAwB;AACjC,QAAI;AACF,YAAM,IAAI,uBAAuB,MAAM;AACvC,UAAI,MAAM,OAAW,YAAW,EAAE,gBAAgB,EAAE;AAAA,IACtD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,cAAY,WAAW;AAAA,IACrB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,GAAG;AAAA,EACL,CAAC;AACH;","names":[]}
package/dist/runtime.cjs CHANGED
@@ -27,6 +27,7 @@ __export(runtime_exports, {
27
27
  module.exports = __toCommonJS(runtime_exports);
28
28
  var import_sandboxUtils = require("./sandboxUtils");
29
29
  var import_version = require("./version");
30
+ var import_protocol = require("./generated/protocol");
30
31
  var import_hostRuntime = require("./hostRuntime");
31
32
  const SDK_PROTOCOL_VERSION = "1.0.0";
32
33
  const sdkHandshake = () => ({
@@ -36,12 +37,13 @@ const sdkHandshake = () => ({
36
37
  function announceHandshake() {
37
38
  const send = () => {
38
39
  try {
39
- (0, import_sandboxUtils.sendMessage)("sdk-handshake", sdkHandshake());
40
+ const payload = sdkHandshake();
41
+ (0, import_sandboxUtils.sendMessage)(import_protocol.SDK_HANDSHAKE, payload);
40
42
  } catch {
41
43
  }
42
44
  };
43
45
  send();
44
- return (0, import_sandboxUtils.addListener)("request-handshake", send);
46
+ return (0, import_sandboxUtils.addListener)(import_protocol.REQUEST_HANDSHAKE, send);
45
47
  }
46
48
  // Annotate the CommonJS export names for ESM import in node:
47
49
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\n/** Build this SDK's handshake payload (version + protocol) for the host to record. */\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n sendMessage('sdk-handshake', sdkHandshake() as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener('request-handshake', send);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,0BAAyC;AACzC,qBAA4B;AAO5B,yBAA+B;AAKxB,MAAM,uBAAuB;AAc7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AACF,2CAAY,iBAAiB,aAAa,CAAuC;AAAA,IACnF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,aAAO,iCAAY,qBAAqB,IAAI;AAC9C;","names":[]}
1
+ {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\nimport { REQUEST_HANDSHAKE, SDK_HANDSHAKE } from './generated/protocol';\nimport type { SdkHandshakePayload } from './generated/protocol';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\n/** Build this SDK's handshake payload (version + protocol) for the host to record. */\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n // R3-274e: annotate with the WIRE type, not this side's factory type.\n // `sdk-handshake` has two legitimate producers — the frame announces the\n // versions IT owns (`sandboxProtocolVersion`), this SDK announces the ones it\n // owns (`sdkVersion`) — and they were declaring two different payloads under\n // one name, which is the `divergent-declared` entry the R3-274a audit found.\n // The resolution is the union with every field optional: one message shape,\n // each producer populating what it knows, exactly as the host already reads it\n // (`site-main/src/editor/SandboxListener.ts` treats each field as optional and\n // fails open). `SdkHandshake` below is deliberately NOT weakened — it is public\n // API describing what THIS side sends, and every field it names is still sent.\n const payload: SdkHandshakePayload = sdkHandshake();\n sendMessage(SDK_HANDSHAKE, payload as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener(REQUEST_HANDSHAKE, send);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAcA,0BAAyC;AACzC,qBAA4B;AAC5B,sBAAiD;AAQjD,yBAA+B;AAKxB,MAAM,uBAAuB;AAc7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AAWF,YAAM,UAA+B,aAAa;AAClD,2CAAY,+BAAe,OAA6C;AAAA,IAC1E,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,aAAO,iCAAY,mCAAmB,IAAI;AAC5C;","names":[]}
package/dist/runtime.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import "./chunk-VHAA22YE.js";
2
2
  import { sendMessage, addListener } from "./sandboxUtils";
3
3
  import { SDK_VERSION } from "./version";
4
+ import { REQUEST_HANDSHAKE, SDK_HANDSHAKE } from "./generated/protocol";
4
5
  import { getHostRuntime } from "./hostRuntime";
5
6
  const SDK_PROTOCOL_VERSION = "1.0.0";
6
7
  const sdkHandshake = () => ({
@@ -10,12 +11,13 @@ const sdkHandshake = () => ({
10
11
  function announceHandshake() {
11
12
  const send = () => {
12
13
  try {
13
- sendMessage("sdk-handshake", sdkHandshake());
14
+ const payload = sdkHandshake();
15
+ sendMessage(SDK_HANDSHAKE, payload);
14
16
  } catch {
15
17
  }
16
18
  };
17
19
  send();
18
- return addListener("request-handshake", send);
20
+ return addListener(REQUEST_HANDSHAKE, send);
19
21
  }
20
22
  export {
21
23
  SDK_PROTOCOL_VERSION,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\n/** Build this SDK's handshake payload (version + protocol) for the host to record. */\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n sendMessage('sdk-handshake', sdkHandshake() as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener('request-handshake', send);\n}\n"],"mappings":";AAcA,SAAS,aAAa,mBAAmB;AACzC,SAAS,mBAAmB;AAO5B,SAAS,sBAAsB;AAKxB,MAAM,uBAAuB;AAc7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AACF,kBAAY,iBAAiB,aAAa,CAAuC;AAAA,IACnF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,SAAO,YAAY,qBAAqB,IAAI;AAC9C;","names":[]}
1
+ {"version":3,"sources":["../src/runtime.ts"],"sourcesContent":["// Runtime discovery + version handshake (SDK_PACKAGING_SPEC §4/§6).\n//\n// Today the SDK reaches the host through the INJECTED sandbox services\n// (`module.evaluation.module.bundler.*`, see sandboxUtils). The packaging migration\n// makes the SDK an app-pinnable npm dependency that finds the runtime through a\n// stable, versioned global the sandbox publishes BEFORE evaluating app code:\n//\n// globalThis.__immediatelyRun__ = { runtimeVersion, protocolVersion, transport }\n//\n// Phase 1 (behind a flag, injection still active): the SDK can READ that global\n// when present (else fall back to injection), and ANNOUNCE its own version +\n// protocol so the host can record + version-check it (§6/T45). The transport itself\n// is unchanged here — this only wires the discovery + handshake fields so the check\n// exists when app-pinned versions become real.\nimport { sendMessage, addListener } from './sandboxUtils';\nimport { SDK_VERSION } from './version';\nimport { REQUEST_HANDSHAKE, SDK_HANDSHAKE } from './generated/protocol';\nimport type { SdkHandshakePayload } from './generated/protocol';\n\n// `getHostRuntime` + `ImmediatelyRunGlobal` live in the leaf `hostRuntime` module\n// (imports nothing) and are re-exported here for a stable public API. This breaks\n// the sandboxUtils↔runtime import cycle: sandboxUtils reads `getHostRuntime` from\n// the leaf, while runtime still imports sandboxUtils for the handshake — one\n// direction only, no cycle.\nexport { getHostRuntime } from './hostRuntime';\nexport type { ImmediatelyRunGlobal } from './hostRuntime';\n\n/** The wire protocol (postMessage envelope / channels / methods) THIS SDK speaks.\n * Additive-only (§9); bump only for a backwards-compatible extension. */\nexport const SDK_PROTOCOL_VERSION = '1.0.0';\n\n/** This SDK's package version, baked from package.json at build (SP2-6,\n * `scripts/gen-version.mjs`). Re-exported so the public surface is unchanged\n * (`@immediately-run/sdk` → `SDK_VERSION`); imported above for the handshake. */\nexport { SDK_VERSION };\n\n/** This SDK's handshake payload — the version + protocol the host records + checks\n * against `HOST_PROTOCOL_VERSION` (§6/T45). */\nexport interface SdkHandshake {\n sdkVersion: string;\n protocolVersion: string;\n}\n/** Build this SDK's handshake payload (version + protocol) for the host to record. */\nexport const sdkHandshake = (): SdkHandshake => ({\n sdkVersion: SDK_VERSION,\n protocolVersion: SDK_PROTOCOL_VERSION,\n});\n\n/**\n * Announce this SDK's version to the host (§6). Sends `sdk-handshake` eagerly\n * (best-effort — the host may already be listening) AND replies to a host\n * `request-handshake` (the robust path, mirroring the other `request-*` pulls).\n * Idempotent; safe to call more than once. Returns an unsubscribe fn.\n */\nexport function announceHandshake(): () => void {\n const send = () => {\n try {\n // R3-274e: annotate with the WIRE type, not this side's factory type.\n // `sdk-handshake` has two legitimate producers — the frame announces the\n // versions IT owns (`sandboxProtocolVersion`), this SDK announces the ones it\n // owns (`sdkVersion`) — and they were declaring two different payloads under\n // one name, which is the `divergent-declared` entry the R3-274a audit found.\n // The resolution is the union with every field optional: one message shape,\n // each producer populating what it knows, exactly as the host already reads it\n // (`site-main/src/editor/SandboxListener.ts` treats each field as optional and\n // fails open). `SdkHandshake` below is deliberately NOT weakened — it is public\n // API describing what THIS side sends, and every field it names is still sent.\n const payload: SdkHandshakePayload = sdkHandshake();\n sendMessage(SDK_HANDSHAKE, payload as unknown as Record<string, unknown>);\n } catch {\n /* transport not ready yet — the request-handshake reply covers it */\n }\n };\n send();\n return addListener(REQUEST_HANDSHAKE, send);\n}\n"],"mappings":";AAcA,SAAS,aAAa,mBAAmB;AACzC,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB,qBAAqB;AAQjD,SAAS,sBAAsB;AAKxB,MAAM,uBAAuB;AAc7B,MAAM,eAAe,OAAqB;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AACnB;AAQO,SAAS,oBAAgC;AAC9C,QAAM,OAAO,MAAM;AACjB,QAAI;AAWF,YAAM,UAA+B,aAAa;AAClD,kBAAY,eAAe,OAA6C;AAAA,IAC1E,QAAQ;AAAA,IAER;AAAA,EACF;AACA,OAAK;AACL,SAAO,YAAY,mBAAmB,IAAI;AAC5C;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sandboxTypes.ts"],"sourcesContent":["/** The exports object of an evaluated sandbox module (untyped — shape depends on the module). */\nexport type ModuleExports = any\n\n/** The sandbox runtime's per-module evaluation context: a module's exports plus the\n * helpers to dynamically import, resolve, and re-evaluate other modules.\n * (The real type is `EvaluationContext` from `src/bundler/module/Evaluation.ts`.) */\nexport type EvaluationContext = {\n exports: ModuleExports;\n dynamicImport: (moduleToImport: string, symbolToImport:string) => Promise<ModuleExports>;\n getModuleEvaluationContext: (moduleName: string) => Promise<EvaluationContext>;\n resolve: (moduleName: string) => Promise<string>;\n evaluation: {\n module: {\n source: string;\n filepath: string;\n }\n }\n}\n\n/**\n * The parsed frontmatter of a single file. Apps can supply their own shape as the\n * `T` type parameter on the metadata hooks for typed field access; it defaults to\n * an open record.\n */\nexport type Metadata = Record<string, any>;\n\n/** The whole metadata store: a map from repo-relative file path to its frontmatter. */\nexport type FilesMetadata<T = Metadata> = Record<string, T>;\n\n/** The paths a {@link MetadataQueryFunction} selected. */\nexport type FileQueryResult = string[]\n\n/**\n * A query over the metadata store: receive every file's frontmatter keyed by path\n * and return the paths that match. Plain JS — `Object.entries(...).filter(...)` —\n * no query language to learn.\n */\nexport type MetadataQueryFunction<T = Metadata> = (filesMetadata: FilesMetadata<T>) => FileQueryResult;\n\n/** One match from {@link MetadataQueryFunction}: the file path paired with its frontmatter. */\nexport type MetadataQueryEntry<T = Metadata> = { path: string; meta: T };\n\n/**\n * The result of running a metadata query: the matched entries (path + frontmatter),\n * or the error a throwing query produced.\n */\nexport type MetadataQueryResult<T = Metadata> = MetadataQueryEntry<T>[] | { error: unknown }\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../src/sandboxTypes.ts"],"sourcesContent":["/** The exports object of an evaluated sandbox module (untyped — shape depends on the module). */\nexport type ModuleExports = any\n\n/** The sandbox runtime's per-module evaluation context: a module's exports plus the\n * helpers to dynamically import, resolve, and re-evaluate other modules.\n * (The real type is `EvaluationContext` from `src/bundler/module/Evaluation.ts`.) */\nexport type EvaluationContext = {\n exports: ModuleExports;\n dynamicImport: (moduleToImport: string, symbolToImport:string) => Promise<ModuleExports>;\n getModuleEvaluationContext: (moduleName: string) => Promise<EvaluationContext>;\n resolve: (moduleName: string) => Promise<string>;\n evaluation: {\n module: {\n source: string;\n filepath: string;\n }\n }\n}\n\n/**\n * The parsed frontmatter of a single file. Apps can supply their own shape as the\n * `T` type parameter on the metadata hooks for typed field access; it defaults to\n * an open record.\n *\n * The ENVELOPE this widens is `Frontmatter` from\n * `@immediately-run/platform-constants` (R3-275): string keys, JSON-serializable\n * values, the emitter's object-identity semantics, and the empty-frontmatter drop —\n * one statement of the contract, shared with the CLI that writes the sidecar and the\n * sandbox that reads it. This alias stays `any`-valued deliberately: app code\n * indexes frontmatter fields directly (`meta.title.length`), and tightening it to\n * `JsonValue` would turn every such access into a type error for a guarantee the\n * *values* never made (they are open by the spec's §6 decision).\n */\nexport type Metadata = Record<string, any>;\n\n/** The whole metadata store: a map from repo-relative file path to its frontmatter. */\nexport type FilesMetadata<T = Metadata> = Record<string, T>;\n\n/**\n * A record a query may return INSTEAD of a bare path (R3-276): the path plus\n * whatever the query computed on the way to selecting it.\n *\n * The motivating case is a query that derives something per match — a sort key, a\n * section, a formatted date — and would otherwise have to recompute it downstream\n * from `meta`, or smuggle it through a closure. Returning it here keeps the\n * derivation next to the selection that needed it.\n */\nexport type MetadataQueryRecord = { path: string } & Record<string, unknown>;\n\n/** What a {@link MetadataQueryFunction} selected: paths, or {@link MetadataQueryRecord}s.\n * The two forms may not be mixed in one result — a query returns one or the other. */\nexport type FileQueryResult = string[] | MetadataQueryRecord[]\n\n/**\n * A query over the metadata store: receive every file's frontmatter keyed by path\n * and return the paths that match. Plain JS — `Object.entries(...).filter(...)` —\n * no query language to learn.\n *\n * Return bare paths, or records carrying extra fields alongside `path` (R3-276);\n * either way the hook resolves each path to its frontmatter.\n */\nexport type MetadataQueryFunction<T = Metadata> = (filesMetadata: FilesMetadata<T>) => FileQueryResult;\n\n/**\n * One match from {@link MetadataQueryFunction}: the file path paired with its\n * frontmatter, plus any extra fields the query returned as a\n * {@link MetadataQueryRecord}.\n *\n * `E` defaults to `{}`, so every existing `MetadataQueryEntry<T>` keeps meaning\n * exactly what it meant — the extra-fields form is opt-in at the type level.\n * `path` and `meta` are applied AFTER the record's own fields, so a query cannot\n * shadow them with something else.\n */\nexport type MetadataQueryEntry<T = Metadata, E extends object = {}> = E & {\n path: string;\n meta: T;\n};\n\n/**\n * The result of running a metadata query: the matched entries (path + frontmatter),\n * or the error a throwing query produced.\n */\nexport type MetadataQueryResult<T = Metadata, E extends object = {}> =\n | MetadataQueryEntry<T, E>[]\n | { error: unknown }\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
@@ -19,20 +19,54 @@ type EvaluationContext = {
19
19
  * The parsed frontmatter of a single file. Apps can supply their own shape as the
20
20
  * `T` type parameter on the metadata hooks for typed field access; it defaults to
21
21
  * an open record.
22
+ *
23
+ * The ENVELOPE this widens is `Frontmatter` from
24
+ * `@immediately-run/platform-constants` (R3-275): string keys, JSON-serializable
25
+ * values, the emitter's object-identity semantics, and the empty-frontmatter drop —
26
+ * one statement of the contract, shared with the CLI that writes the sidecar and the
27
+ * sandbox that reads it. This alias stays `any`-valued deliberately: app code
28
+ * indexes frontmatter fields directly (`meta.title.length`), and tightening it to
29
+ * `JsonValue` would turn every such access into a type error for a guarantee the
30
+ * *values* never made (they are open by the spec's §6 decision).
22
31
  */
23
32
  type Metadata = Record<string, any>;
24
33
  /** The whole metadata store: a map from repo-relative file path to its frontmatter. */
25
34
  type FilesMetadata<T = Metadata> = Record<string, T>;
26
- /** The paths a {@link MetadataQueryFunction} selected. */
27
- type FileQueryResult = string[];
35
+ /**
36
+ * A record a query may return INSTEAD of a bare path (R3-276): the path plus
37
+ * whatever the query computed on the way to selecting it.
38
+ *
39
+ * The motivating case is a query that derives something per match — a sort key, a
40
+ * section, a formatted date — and would otherwise have to recompute it downstream
41
+ * from `meta`, or smuggle it through a closure. Returning it here keeps the
42
+ * derivation next to the selection that needed it.
43
+ */
44
+ type MetadataQueryRecord = {
45
+ path: string;
46
+ } & Record<string, unknown>;
47
+ /** What a {@link MetadataQueryFunction} selected: paths, or {@link MetadataQueryRecord}s.
48
+ * The two forms may not be mixed in one result — a query returns one or the other. */
49
+ type FileQueryResult = string[] | MetadataQueryRecord[];
28
50
  /**
29
51
  * A query over the metadata store: receive every file's frontmatter keyed by path
30
52
  * and return the paths that match. Plain JS — `Object.entries(...).filter(...)` —
31
53
  * no query language to learn.
54
+ *
55
+ * Return bare paths, or records carrying extra fields alongside `path` (R3-276);
56
+ * either way the hook resolves each path to its frontmatter.
32
57
  */
33
58
  type MetadataQueryFunction<T = Metadata> = (filesMetadata: FilesMetadata<T>) => FileQueryResult;
34
- /** One match from {@link MetadataQueryFunction}: the file path paired with its frontmatter. */
35
- type MetadataQueryEntry<T = Metadata> = {
59
+ /**
60
+ * One match from {@link MetadataQueryFunction}: the file path paired with its
61
+ * frontmatter, plus any extra fields the query returned as a
62
+ * {@link MetadataQueryRecord}.
63
+ *
64
+ * `E` defaults to `{}`, so every existing `MetadataQueryEntry<T>` keeps meaning
65
+ * exactly what it meant — the extra-fields form is opt-in at the type level.
66
+ * `path` and `meta` are applied AFTER the record's own fields, so a query cannot
67
+ * shadow them with something else.
68
+ */
69
+ type MetadataQueryEntry<T = Metadata, E extends object = {}> = E & {
36
70
  path: string;
37
71
  meta: T;
38
72
  };
@@ -40,8 +74,8 @@ type MetadataQueryEntry<T = Metadata> = {
40
74
  * The result of running a metadata query: the matched entries (path + frontmatter),
41
75
  * or the error a throwing query produced.
42
76
  */
43
- type MetadataQueryResult<T = Metadata> = MetadataQueryEntry<T>[] | {
77
+ type MetadataQueryResult<T = Metadata, E extends object = {}> = MetadataQueryEntry<T, E>[] | {
44
78
  error: unknown;
45
79
  };
46
80
 
47
- export type { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryResult, ModuleExports };
81
+ export type { EvaluationContext, FileQueryResult, FilesMetadata, Metadata, MetadataQueryEntry, MetadataQueryFunction, MetadataQueryRecord, MetadataQueryResult, ModuleExports };