@immediately-run/sdk 0.45.3 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/hostAttention.cjs +57 -0
  2. package/dist/hostAttention.cjs.map +1 -0
  3. package/dist/hostAttention.d.cts +39 -0
  4. package/dist/hostAttention.d.ts +39 -0
  5. package/dist/hostAttention.js +31 -0
  6. package/dist/hostAttention.js.map +1 -0
  7. package/dist/hostTransport.cjs +55 -0
  8. package/dist/hostTransport.cjs.map +1 -0
  9. package/dist/hostTransport.d.cts +12 -0
  10. package/dist/hostTransport.d.ts +12 -0
  11. package/dist/hostTransport.js +30 -0
  12. package/dist/hostTransport.js.map +1 -0
  13. package/dist/index.cjs +4 -0
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +3 -1
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.js +2 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/linkSpace.cjs +11 -2
  20. package/dist/linkSpace.cjs.map +1 -1
  21. package/dist/linkSpace.d.cts +23 -0
  22. package/dist/linkSpace.d.ts +23 -0
  23. package/dist/linkSpace.js +11 -2
  24. package/dist/linkSpace.js.map +1 -1
  25. package/dist/llm.cjs +18 -3
  26. package/dist/llm.cjs.map +1 -1
  27. package/dist/llm.d.cts +38 -3
  28. package/dist/llm.d.ts +38 -3
  29. package/dist/llm.js +14 -2
  30. package/dist/llm.js.map +1 -1
  31. package/dist/protocolDeadline.cjs +205 -0
  32. package/dist/protocolDeadline.cjs.map +1 -0
  33. package/dist/protocolDeadline.d.cts +146 -0
  34. package/dist/protocolDeadline.d.ts +146 -0
  35. package/dist/protocolDeadline.js +168 -0
  36. package/dist/protocolDeadline.js.map +1 -0
  37. package/dist/protocolStream.cjs +60 -6
  38. package/dist/protocolStream.cjs.map +1 -1
  39. package/dist/protocolStream.d.cts +9 -2
  40. package/dist/protocolStream.d.ts +9 -2
  41. package/dist/protocolStream.js +67 -6
  42. package/dist/protocolStream.js.map +1 -1
  43. package/dist/pushChannel.cjs +13 -9
  44. package/dist/pushChannel.cjs.map +1 -1
  45. package/dist/pushChannel.js +12 -8
  46. package/dist/pushChannel.js.map +1 -1
  47. package/dist/sandboxUtils.cjs +74 -24
  48. package/dist/sandboxUtils.cjs.map +1 -1
  49. package/dist/sandboxUtils.d.cts +37 -4
  50. package/dist/sandboxUtils.d.ts +37 -4
  51. package/dist/sandboxUtils.js +79 -22
  52. package/dist/sandboxUtils.js.map +1 -1
  53. package/dist/version.cjs +1 -1
  54. package/dist/version.cjs.map +1 -1
  55. package/dist/version.d.cts +1 -1
  56. package/dist/version.d.ts +1 -1
  57. package/dist/version.js +1 -1
  58. package/dist/version.js.map +1 -1
  59. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sandboxUtils.ts"],"sourcesContent":["// Host transport access (SDK_PACKAGING_SPEC §4, expose-transport). All host comms\n// (sendMessage / protocolRequest / onMessage) go through ONE resolver so the SDK is\n// transport-agnostic: it works whether it was INJECTED into the bundler's evaluation\n// context (the current path — `module.evaluation.module.bundler.messageBus`) OR\n// fetched from npm as an ordinary dependency, in which case the sandbox runtime hands\n// it the transport via the §4 discovery global (`globalThis.__immediatelyRun__`).\n//\n// Dual-mode (§8): injection wins while it's active, so existing behaviour is\n// byte-for-byte preserved; the global is the fallback the npm-fetched SDK uses. When\n// injection is removed (phase 3), only the global path remains — `bundler.*` stops\n// being API, which is the whole point.\nimport { getHostRuntime } from './hostRuntime';\n\ninterface HostTransport {\n sendMessage(type: string, data?: Record<string, any>): void;\n protocolRequest(protocolName: string, method: string, params: Array<any>): Promise<any>;\n onMessage(handler: (msg: any) => void): { dispose(): void };\n}\n\nfunction transport(): HostTransport {\n // Injected bundler messageBus first — the current path, unchanged.\n try {\n // @ts-ignore - `module.evaluation` is injected by the sandbox runtime\n const injected = module?.evaluation?.module?.bundler?.messageBus;\n if (injected && typeof injected.sendMessage === 'function') return injected;\n } catch {\n /* no injection in this realm — fall through to the §4 global */\n }\n // §4 runtime-discovery transport (the npm-fetched SDK path).\n const t = getHostRuntime()?.transport as HostTransport | undefined;\n if (t && typeof t.sendMessage === 'function') return t;\n throw new Error('immediately.run: no host transport (neither injected nor __immediatelyRun__)');\n}\n\nexport const sendMessage = (type: string, data: Record<string, any> = {}) => {\n transport().sendMessage(type, data);\n};\n\nexport const protocolRequest = (\n protocolName: string,\n method: string,\n params: Array<any>,\n): Promise<any> => transport().protocolRequest(protocolName, method, params);\n\nexport const addListener = (\n msgType: string,\n handler: (msg: any) => void,\n event?: any,\n): (() => void) => {\n const onMessage = event ?? transport().onMessage;\n const disposable = onMessage((msg: any) => {\n if (msg.type === msgType) {\n handler(msg);\n }\n });\n return () => disposable.dispose();\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,yBAA+B;AAQ/B,SAAS,YAA2B;AAElC,MAAI;AAEF,UAAM,WAAW,QAAQ,YAAY,QAAQ,SAAS;AACtD,QAAI,YAAY,OAAO,SAAS,gBAAgB,WAAY,QAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AAEA,QAAM,QAAI,mCAAe,GAAG;AAC5B,MAAI,KAAK,OAAO,EAAE,gBAAgB,WAAY,QAAO;AACrD,QAAM,IAAI,MAAM,8EAA8E;AAChG;AAEO,MAAM,cAAc,CAAC,MAAc,OAA4B,CAAC,MAAM;AAC3E,YAAU,EAAE,YAAY,MAAM,IAAI;AACpC;AAEO,MAAM,kBAAkB,CAC7B,cACA,QACA,WACiB,UAAU,EAAE,gBAAgB,cAAc,QAAQ,MAAM;AAEpE,MAAM,cAAc,CACzB,SACA,SACA,UACiB;AACjB,QAAM,YAAY,SAAS,UAAU,EAAE;AACvC,QAAM,aAAa,UAAU,CAAC,QAAa;AACzC,QAAI,IAAI,SAAS,SAAS;AACxB,cAAQ,GAAG;AAAA,IACb;AAAA,EACF,CAAC;AACD,SAAO,MAAM,WAAW,QAAQ;AAClC;","names":[]}
1
+ {"version":3,"sources":["../src/sandboxUtils.ts"],"sourcesContent":["// Bounded host calls (R3-298) + the host-attention-aware deadline (R3-307).\n//\n// The transport primitives this builds on moved to `hostTransport.ts` — see that file for\n// why. `sendMessage` and `addListener` are re-exported here unchanged, so every existing\n// import site (and this module's api-snapshot entry) is exactly as it was.\nimport { transport } from './hostTransport';\nimport { onHostAttentionChange, type HostAttention } from './hostAttention';\n\nimport {\n attendanceOf,\n attendanceReason,\n boundsFor,\n createSuspendableDeadline,\n PENDING_NOTICE_MS,\n ProtocolCancelledError,\n ProtocolTimeoutError,\n type BoundedCallOptions,\n type CallBounds,\n} from './protocolDeadline';\n\nexport { sendMessage, addListener } from './hostTransport';\n\n/**\n * One host protocol request, BOUNDED (R3-298).\n *\n * The bound comes from the call's classification (`protocolDeadline.ts`): an unattended\n * channel round-trip gets tens of seconds, a call that may draw host chrome and wait for a\n * person gets minutes. Nothing is unbounded — an unbounded wait is the failure this fixes.\n *\n * Since R3-307 a call whose prompts the host actually announces runs on the SHORT bound and\n * is suspended only while the host says a person is being asked, so the common\n * grant-already-held path reports a fault in seconds instead of minutes. The absolute\n * ceiling still applies: the signal may extend a deadline, never remove it.\n *\n * The host's own work is NOT cancelled by `signal` or by the deadline. The one-shot\n * transport allocates its `msgId` internally, so the SDK has no handle to send the host a\n * cancel for (streams do, and `consumeStream` uses it). What the caller gets back is\n * control: it stops waiting and gets a typed error instead of hanging. A host prompt that\n * is already on screen stays there until the user dismisses it, which is correct — the SDK\n * must not be able to tear down host chrome the user is looking at.\n */\nexport const protocolRequest = (\n protocolName: string,\n method: string,\n params: Array<any>,\n opts?: BoundedCallOptions,\n): Promise<any> =>\n withDeadline(\n protocolName,\n method,\n () => transport().protocolRequest(protocolName, method, params),\n opts,\n );\n\n/**\n * Race a host call against its deadline, a caller abort, and a pending notice.\n *\n * Kept separate from the transport so it is unit-testable against a promise that simply\n * never settles — which is the whole scenario, and one no live transport reproduces on\n * demand.\n *\n * An explicit `opts.timeoutMs` is the WHOLE bound and is never suspended: a caller that\n * names a number owns the wait, and silently stretching it past what they asked for would\n * be the same class of surprise this machinery exists to remove.\n */\nexport async function withDeadline<T>(\n scheme: string,\n method: string,\n start: () => Promise<T>,\n opts?: BoundedCallOptions,\n): Promise<T> {\n const call = `${scheme}:${method}`;\n const attendance = attendanceOf(scheme, method);\n const bounds: CallBounds =\n opts?.timeoutMs !== undefined\n ? { idleMs: opts.timeoutMs, ceilingMs: opts.timeoutMs }\n : boundsFor(scheme, method);\n const signal = opts?.signal;\n\n if (signal?.aborted) throw new ProtocolCancelledError(call);\n\n const work = start();\n // No bound and no abort wanted: hand back the untouched promise rather than wrapping it\n // in timers that would never fire.\n if (!Number.isFinite(bounds.ceilingMs) && !signal && !opts?.onPending) return work;\n\n let deadline: { setAwaiting: (a: boolean) => void; dispose: () => void } | undefined;\n let notice: ReturnType<typeof setTimeout> | undefined;\n let unsubscribeAttention: (() => void) | undefined;\n let onAbort: (() => void) | undefined;\n const started = Date.now();\n\n try {\n return await new Promise<T>((resolve, reject) => {\n // `work` settling always wins — a call that answered a millisecond before its\n // deadline must not be reported as a timeout.\n work.then(resolve, reject);\n\n // The host's live \"a person is being asked\" signal. Read defensively: a host that\n // never pushes the channel leaves this at \"not awaiting\", which is exactly the\n // pre-R3-307 behaviour for a call on its idle bound.\n let attention: HostAttention | undefined;\n let noticeFired = false;\n const fireNotice = () => {\n try {\n opts?.onPending?.({\n call,\n attendance,\n elapsedMs: Date.now() - started,\n ...(attendanceReason(scheme, method)\n ? { reason: attendanceReason(scheme, method) as string }\n : {}),\n ...(attention?.awaiting\n ? { awaiting: { kind: attention.kind, since: attention.since } }\n : {}),\n });\n } catch {\n /* a caller's render callback must never break the call it describes */\n }\n };\n\n deadline = createSuspendableDeadline({\n bounds,\n onExpire: (bound, elapsedBoundMs) =>\n reject(new ProtocolTimeoutError(call, elapsedBoundMs, attendance, bound)),\n });\n\n if (opts?.onPending) {\n notice = setTimeout(() => {\n noticeFired = true;\n fireNotice();\n }, PENDING_NOTICE_MS);\n }\n\n // Subscribing invokes the listener immediately with the current value, so the\n // deadline starts in the right state even if a prompt was already up.\n //\n // Guarded because this sits on the path of EVERY host call: a fault in the attention\n // channel must degrade to \"no signal\" (the pre-R3-307 behaviour, bounds unsuspended),\n // never take down every request in the SDK.\n try {\n unsubscribeAttention = onHostAttentionChange((next) => {\n const wasAwaiting = attention?.awaiting ?? false;\n attention = next;\n deadline?.setAwaiting(next.awaiting);\n // Re-notify once the notice has fired: \"still waiting\" → \"tap your passkey\" is\n // the whole point, and a caller rendering a waiting state wants the live\n // sentence, not the one that was true three seconds in.\n if (noticeFired && next.awaiting !== wasAwaiting) fireNotice();\n });\n } catch {\n /* no attention signal available — the bounds simply never suspend */\n }\n\n if (signal) {\n onAbort = () => reject(new ProtocolCancelledError(call));\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n } finally {\n deadline?.dispose();\n if (notice !== undefined) clearTimeout(notice);\n unsubscribeAttention?.();\n if (signal && onAbort) signal.removeEventListener('abort', onAbort);\n // The host may still answer after we stopped waiting; swallow it so a late rejection\n // does not surface as an unhandled promise rejection in the app's console.\n work.catch(() => undefined);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,2BAA0B;AAC1B,2BAA0D;AAE1D,8BAUO;AAEP,IAAAA,wBAAyC;AAqBlC,MAAM,kBAAkB,CAC7B,cACA,QACA,QACA,SAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA,UAAM,gCAAU,EAAE,gBAAgB,cAAc,QAAQ,MAAM;AAAA,EAC9D;AACF;AAaF,eAAsB,aACpB,QACA,QACA,OACA,MACY;AACZ,QAAM,OAAO,GAAG,MAAM,IAAI,MAAM;AAChC,QAAM,iBAAa,sCAAa,QAAQ,MAAM;AAC9C,QAAM,SACJ,MAAM,cAAc,SAChB,EAAE,QAAQ,KAAK,WAAW,WAAW,KAAK,UAAU,QACpD,mCAAU,QAAQ,MAAM;AAC9B,QAAM,SAAS,MAAM;AAErB,MAAI,QAAQ,QAAS,OAAM,IAAI,+CAAuB,IAAI;AAE1D,QAAM,OAAO,MAAM;AAGnB,MAAI,CAAC,OAAO,SAAS,OAAO,SAAS,KAAK,CAAC,UAAU,CAAC,MAAM,UAAW,QAAO;AAE9E,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,KAAK,IAAI;AAEzB,MAAI;AACF,WAAO,MAAM,IAAI,QAAW,CAAC,SAAS,WAAW;AAG/C,WAAK,KAAK,SAAS,MAAM;AAKzB,UAAI;AACJ,UAAI,cAAc;AAClB,YAAM,aAAa,MAAM;AACvB,YAAI;AACF,gBAAM,YAAY;AAAA,YAChB;AAAA,YACA;AAAA,YACA,WAAW,KAAK,IAAI,IAAI;AAAA,YACxB,OAAI,0CAAiB,QAAQ,MAAM,IAC/B,EAAE,YAAQ,0CAAiB,QAAQ,MAAM,EAAY,IACrD,CAAC;AAAA,YACL,GAAI,WAAW,WACX,EAAE,UAAU,EAAE,MAAM,UAAU,MAAM,OAAO,UAAU,MAAM,EAAE,IAC7D,CAAC;AAAA,UACP,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,qBAAW,mDAA0B;AAAA,QACnC;AAAA,QACA,UAAU,CAAC,OAAO,mBAChB,OAAO,IAAI,6CAAqB,MAAM,gBAAgB,YAAY,KAAK,CAAC;AAAA,MAC5E,CAAC;AAED,UAAI,MAAM,WAAW;AACnB,iBAAS,WAAW,MAAM;AACxB,wBAAc;AACd,qBAAW;AAAA,QACb,GAAG,yCAAiB;AAAA,MACtB;AAQA,UAAI;AACF,mCAAuB,4CAAsB,CAAC,SAAS;AACrD,gBAAM,cAAc,WAAW,YAAY;AAC3C,sBAAY;AACZ,oBAAU,YAAY,KAAK,QAAQ;AAInC,cAAI,eAAe,KAAK,aAAa,YAAa,YAAW;AAAA,QAC/D,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAEA,UAAI,QAAQ;AACV,kBAAU,MAAM,OAAO,IAAI,+CAAuB,IAAI,CAAC;AACvD,eAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH,UAAE;AACA,cAAU,QAAQ;AAClB,QAAI,WAAW,OAAW,cAAa,MAAM;AAC7C,2BAAuB;AACvB,QAAI,UAAU,QAAS,QAAO,oBAAoB,SAAS,OAAO;AAGlE,SAAK,MAAM,MAAM,MAAS;AAAA,EAC5B;AACF;","names":["import_hostTransport"]}
@@ -1,5 +1,38 @@
1
- declare const sendMessage: (type: string, data?: Record<string, any>) => void;
2
- declare const protocolRequest: (protocolName: string, method: string, params: Array<any>) => Promise<any>;
3
- declare const addListener: (msgType: string, handler: (msg: any) => void, event?: any) => (() => void);
1
+ import { BoundedCallOptions } from './protocolDeadline.cjs';
2
+ export { addListener, sendMessage } from './hostTransport.cjs';
3
+ import './hostAttention.cjs';
4
4
 
5
- export { addListener, protocolRequest, sendMessage };
5
+ /**
6
+ * One host protocol request, BOUNDED (R3-298).
7
+ *
8
+ * The bound comes from the call's classification (`protocolDeadline.ts`): an unattended
9
+ * channel round-trip gets tens of seconds, a call that may draw host chrome and wait for a
10
+ * person gets minutes. Nothing is unbounded — an unbounded wait is the failure this fixes.
11
+ *
12
+ * Since R3-307 a call whose prompts the host actually announces runs on the SHORT bound and
13
+ * is suspended only while the host says a person is being asked, so the common
14
+ * grant-already-held path reports a fault in seconds instead of minutes. The absolute
15
+ * ceiling still applies: the signal may extend a deadline, never remove it.
16
+ *
17
+ * The host's own work is NOT cancelled by `signal` or by the deadline. The one-shot
18
+ * transport allocates its `msgId` internally, so the SDK has no handle to send the host a
19
+ * cancel for (streams do, and `consumeStream` uses it). What the caller gets back is
20
+ * control: it stops waiting and gets a typed error instead of hanging. A host prompt that
21
+ * is already on screen stays there until the user dismisses it, which is correct — the SDK
22
+ * must not be able to tear down host chrome the user is looking at.
23
+ */
24
+ declare const protocolRequest: (protocolName: string, method: string, params: Array<any>, opts?: BoundedCallOptions) => Promise<any>;
25
+ /**
26
+ * Race a host call against its deadline, a caller abort, and a pending notice.
27
+ *
28
+ * Kept separate from the transport so it is unit-testable against a promise that simply
29
+ * never settles — which is the whole scenario, and one no live transport reproduces on
30
+ * demand.
31
+ *
32
+ * An explicit `opts.timeoutMs` is the WHOLE bound and is never suspended: a caller that
33
+ * names a number owns the wait, and silently stretching it past what they asked for would
34
+ * be the same class of surprise this machinery exists to remove.
35
+ */
36
+ declare function withDeadline<T>(scheme: string, method: string, start: () => Promise<T>, opts?: BoundedCallOptions): Promise<T>;
37
+
38
+ export { protocolRequest, withDeadline };
@@ -1,5 +1,38 @@
1
- declare const sendMessage: (type: string, data?: Record<string, any>) => void;
2
- declare const protocolRequest: (protocolName: string, method: string, params: Array<any>) => Promise<any>;
3
- declare const addListener: (msgType: string, handler: (msg: any) => void, event?: any) => (() => void);
1
+ import { BoundedCallOptions } from './protocolDeadline.js';
2
+ export { addListener, sendMessage } from './hostTransport.js';
3
+ import './hostAttention.js';
4
4
 
5
- export { addListener, protocolRequest, sendMessage };
5
+ /**
6
+ * One host protocol request, BOUNDED (R3-298).
7
+ *
8
+ * The bound comes from the call's classification (`protocolDeadline.ts`): an unattended
9
+ * channel round-trip gets tens of seconds, a call that may draw host chrome and wait for a
10
+ * person gets minutes. Nothing is unbounded — an unbounded wait is the failure this fixes.
11
+ *
12
+ * Since R3-307 a call whose prompts the host actually announces runs on the SHORT bound and
13
+ * is suspended only while the host says a person is being asked, so the common
14
+ * grant-already-held path reports a fault in seconds instead of minutes. The absolute
15
+ * ceiling still applies: the signal may extend a deadline, never remove it.
16
+ *
17
+ * The host's own work is NOT cancelled by `signal` or by the deadline. The one-shot
18
+ * transport allocates its `msgId` internally, so the SDK has no handle to send the host a
19
+ * cancel for (streams do, and `consumeStream` uses it). What the caller gets back is
20
+ * control: it stops waiting and gets a typed error instead of hanging. A host prompt that
21
+ * is already on screen stays there until the user dismisses it, which is correct — the SDK
22
+ * must not be able to tear down host chrome the user is looking at.
23
+ */
24
+ declare const protocolRequest: (protocolName: string, method: string, params: Array<any>, opts?: BoundedCallOptions) => Promise<any>;
25
+ /**
26
+ * Race a host call against its deadline, a caller abort, and a pending notice.
27
+ *
28
+ * Kept separate from the transport so it is unit-testable against a promise that simply
29
+ * never settles — which is the whole scenario, and one no live transport reproduces on
30
+ * demand.
31
+ *
32
+ * An explicit `opts.timeoutMs` is the WHOLE bound and is never suspended: a caller that
33
+ * names a number owns the wait, and silently stretching it past what they asked for would
34
+ * be the same class of surprise this machinery exists to remove.
35
+ */
36
+ declare function withDeadline<T>(scheme: string, method: string, start: () => Promise<T>, opts?: BoundedCallOptions): Promise<T>;
37
+
38
+ export { protocolRequest, withDeadline };
@@ -1,31 +1,88 @@
1
1
  import "./chunk-VHAA22YE.js";
2
- import { getHostRuntime } from "./hostRuntime";
3
- function transport() {
2
+ import { transport } from "./hostTransport";
3
+ import { onHostAttentionChange } from "./hostAttention";
4
+ import {
5
+ attendanceOf,
6
+ attendanceReason,
7
+ boundsFor,
8
+ createSuspendableDeadline,
9
+ PENDING_NOTICE_MS,
10
+ ProtocolCancelledError,
11
+ ProtocolTimeoutError
12
+ } from "./protocolDeadline";
13
+ import { sendMessage, addListener } from "./hostTransport";
14
+ const protocolRequest = (protocolName, method, params, opts) => withDeadline(
15
+ protocolName,
16
+ method,
17
+ () => transport().protocolRequest(protocolName, method, params),
18
+ opts
19
+ );
20
+ async function withDeadline(scheme, method, start, opts) {
21
+ const call = `${scheme}:${method}`;
22
+ const attendance = attendanceOf(scheme, method);
23
+ const bounds = opts?.timeoutMs !== void 0 ? { idleMs: opts.timeoutMs, ceilingMs: opts.timeoutMs } : boundsFor(scheme, method);
24
+ const signal = opts?.signal;
25
+ if (signal?.aborted) throw new ProtocolCancelledError(call);
26
+ const work = start();
27
+ if (!Number.isFinite(bounds.ceilingMs) && !signal && !opts?.onPending) return work;
28
+ let deadline;
29
+ let notice;
30
+ let unsubscribeAttention;
31
+ let onAbort;
32
+ const started = Date.now();
4
33
  try {
5
- const injected = module?.evaluation?.module?.bundler?.messageBus;
6
- if (injected && typeof injected.sendMessage === "function") return injected;
7
- } catch {
34
+ return await new Promise((resolve, reject) => {
35
+ work.then(resolve, reject);
36
+ let attention;
37
+ let noticeFired = false;
38
+ const fireNotice = () => {
39
+ try {
40
+ opts?.onPending?.({
41
+ call,
42
+ attendance,
43
+ elapsedMs: Date.now() - started,
44
+ ...attendanceReason(scheme, method) ? { reason: attendanceReason(scheme, method) } : {},
45
+ ...attention?.awaiting ? { awaiting: { kind: attention.kind, since: attention.since } } : {}
46
+ });
47
+ } catch {
48
+ }
49
+ };
50
+ deadline = createSuspendableDeadline({
51
+ bounds,
52
+ onExpire: (bound, elapsedBoundMs) => reject(new ProtocolTimeoutError(call, elapsedBoundMs, attendance, bound))
53
+ });
54
+ if (opts?.onPending) {
55
+ notice = setTimeout(() => {
56
+ noticeFired = true;
57
+ fireNotice();
58
+ }, PENDING_NOTICE_MS);
59
+ }
60
+ try {
61
+ unsubscribeAttention = onHostAttentionChange((next) => {
62
+ const wasAwaiting = attention?.awaiting ?? false;
63
+ attention = next;
64
+ deadline?.setAwaiting(next.awaiting);
65
+ if (noticeFired && next.awaiting !== wasAwaiting) fireNotice();
66
+ });
67
+ } catch {
68
+ }
69
+ if (signal) {
70
+ onAbort = () => reject(new ProtocolCancelledError(call));
71
+ signal.addEventListener("abort", onAbort, { once: true });
72
+ }
73
+ });
74
+ } finally {
75
+ deadline?.dispose();
76
+ if (notice !== void 0) clearTimeout(notice);
77
+ unsubscribeAttention?.();
78
+ if (signal && onAbort) signal.removeEventListener("abort", onAbort);
79
+ work.catch(() => void 0);
8
80
  }
9
- const t = getHostRuntime()?.transport;
10
- if (t && typeof t.sendMessage === "function") return t;
11
- throw new Error("immediately.run: no host transport (neither injected nor __immediatelyRun__)");
12
81
  }
13
- const sendMessage = (type, data = {}) => {
14
- transport().sendMessage(type, data);
15
- };
16
- const protocolRequest = (protocolName, method, params) => transport().protocolRequest(protocolName, method, params);
17
- const addListener = (msgType, handler, event) => {
18
- const onMessage = event ?? transport().onMessage;
19
- const disposable = onMessage((msg) => {
20
- if (msg.type === msgType) {
21
- handler(msg);
22
- }
23
- });
24
- return () => disposable.dispose();
25
- };
26
82
  export {
27
83
  addListener,
28
84
  protocolRequest,
29
- sendMessage
85
+ sendMessage,
86
+ withDeadline
30
87
  };
31
88
  //# sourceMappingURL=sandboxUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sandboxUtils.ts"],"sourcesContent":["// Host transport access (SDK_PACKAGING_SPEC §4, expose-transport). All host comms\n// (sendMessage / protocolRequest / onMessage) go through ONE resolver so the SDK is\n// transport-agnostic: it works whether it was INJECTED into the bundler's evaluation\n// context (the current path — `module.evaluation.module.bundler.messageBus`) OR\n// fetched from npm as an ordinary dependency, in which case the sandbox runtime hands\n// it the transport via the §4 discovery global (`globalThis.__immediatelyRun__`).\n//\n// Dual-mode (§8): injection wins while it's active, so existing behaviour is\n// byte-for-byte preserved; the global is the fallback the npm-fetched SDK uses. When\n// injection is removed (phase 3), only the global path remains — `bundler.*` stops\n// being API, which is the whole point.\nimport { getHostRuntime } from './hostRuntime';\n\ninterface HostTransport {\n sendMessage(type: string, data?: Record<string, any>): void;\n protocolRequest(protocolName: string, method: string, params: Array<any>): Promise<any>;\n onMessage(handler: (msg: any) => void): { dispose(): void };\n}\n\nfunction transport(): HostTransport {\n // Injected bundler messageBus first — the current path, unchanged.\n try {\n // @ts-ignore - `module.evaluation` is injected by the sandbox runtime\n const injected = module?.evaluation?.module?.bundler?.messageBus;\n if (injected && typeof injected.sendMessage === 'function') return injected;\n } catch {\n /* no injection in this realm — fall through to the §4 global */\n }\n // §4 runtime-discovery transport (the npm-fetched SDK path).\n const t = getHostRuntime()?.transport as HostTransport | undefined;\n if (t && typeof t.sendMessage === 'function') return t;\n throw new Error('immediately.run: no host transport (neither injected nor __immediatelyRun__)');\n}\n\nexport const sendMessage = (type: string, data: Record<string, any> = {}) => {\n transport().sendMessage(type, data);\n};\n\nexport const protocolRequest = (\n protocolName: string,\n method: string,\n params: Array<any>,\n): Promise<any> => transport().protocolRequest(protocolName, method, params);\n\nexport const addListener = (\n msgType: string,\n handler: (msg: any) => void,\n event?: any,\n): (() => void) => {\n const onMessage = event ?? transport().onMessage;\n const disposable = onMessage((msg: any) => {\n if (msg.type === msgType) {\n handler(msg);\n }\n });\n return () => disposable.dispose();\n};\n"],"mappings":";AAWA,SAAS,sBAAsB;AAQ/B,SAAS,YAA2B;AAElC,MAAI;AAEF,UAAM,WAAW,QAAQ,YAAY,QAAQ,SAAS;AACtD,QAAI,YAAY,OAAO,SAAS,gBAAgB,WAAY,QAAO;AAAA,EACrE,QAAQ;AAAA,EAER;AAEA,QAAM,IAAI,eAAe,GAAG;AAC5B,MAAI,KAAK,OAAO,EAAE,gBAAgB,WAAY,QAAO;AACrD,QAAM,IAAI,MAAM,8EAA8E;AAChG;AAEO,MAAM,cAAc,CAAC,MAAc,OAA4B,CAAC,MAAM;AAC3E,YAAU,EAAE,YAAY,MAAM,IAAI;AACpC;AAEO,MAAM,kBAAkB,CAC7B,cACA,QACA,WACiB,UAAU,EAAE,gBAAgB,cAAc,QAAQ,MAAM;AAEpE,MAAM,cAAc,CACzB,SACA,SACA,UACiB;AACjB,QAAM,YAAY,SAAS,UAAU,EAAE;AACvC,QAAM,aAAa,UAAU,CAAC,QAAa;AACzC,QAAI,IAAI,SAAS,SAAS;AACxB,cAAQ,GAAG;AAAA,IACb;AAAA,EACF,CAAC;AACD,SAAO,MAAM,WAAW,QAAQ;AAClC;","names":[]}
1
+ {"version":3,"sources":["../src/sandboxUtils.ts"],"sourcesContent":["// Bounded host calls (R3-298) + the host-attention-aware deadline (R3-307).\n//\n// The transport primitives this builds on moved to `hostTransport.ts` — see that file for\n// why. `sendMessage` and `addListener` are re-exported here unchanged, so every existing\n// import site (and this module's api-snapshot entry) is exactly as it was.\nimport { transport } from './hostTransport';\nimport { onHostAttentionChange, type HostAttention } from './hostAttention';\n\nimport {\n attendanceOf,\n attendanceReason,\n boundsFor,\n createSuspendableDeadline,\n PENDING_NOTICE_MS,\n ProtocolCancelledError,\n ProtocolTimeoutError,\n type BoundedCallOptions,\n type CallBounds,\n} from './protocolDeadline';\n\nexport { sendMessage, addListener } from './hostTransport';\n\n/**\n * One host protocol request, BOUNDED (R3-298).\n *\n * The bound comes from the call's classification (`protocolDeadline.ts`): an unattended\n * channel round-trip gets tens of seconds, a call that may draw host chrome and wait for a\n * person gets minutes. Nothing is unbounded — an unbounded wait is the failure this fixes.\n *\n * Since R3-307 a call whose prompts the host actually announces runs on the SHORT bound and\n * is suspended only while the host says a person is being asked, so the common\n * grant-already-held path reports a fault in seconds instead of minutes. The absolute\n * ceiling still applies: the signal may extend a deadline, never remove it.\n *\n * The host's own work is NOT cancelled by `signal` or by the deadline. The one-shot\n * transport allocates its `msgId` internally, so the SDK has no handle to send the host a\n * cancel for (streams do, and `consumeStream` uses it). What the caller gets back is\n * control: it stops waiting and gets a typed error instead of hanging. A host prompt that\n * is already on screen stays there until the user dismisses it, which is correct — the SDK\n * must not be able to tear down host chrome the user is looking at.\n */\nexport const protocolRequest = (\n protocolName: string,\n method: string,\n params: Array<any>,\n opts?: BoundedCallOptions,\n): Promise<any> =>\n withDeadline(\n protocolName,\n method,\n () => transport().protocolRequest(protocolName, method, params),\n opts,\n );\n\n/**\n * Race a host call against its deadline, a caller abort, and a pending notice.\n *\n * Kept separate from the transport so it is unit-testable against a promise that simply\n * never settles — which is the whole scenario, and one no live transport reproduces on\n * demand.\n *\n * An explicit `opts.timeoutMs` is the WHOLE bound and is never suspended: a caller that\n * names a number owns the wait, and silently stretching it past what they asked for would\n * be the same class of surprise this machinery exists to remove.\n */\nexport async function withDeadline<T>(\n scheme: string,\n method: string,\n start: () => Promise<T>,\n opts?: BoundedCallOptions,\n): Promise<T> {\n const call = `${scheme}:${method}`;\n const attendance = attendanceOf(scheme, method);\n const bounds: CallBounds =\n opts?.timeoutMs !== undefined\n ? { idleMs: opts.timeoutMs, ceilingMs: opts.timeoutMs }\n : boundsFor(scheme, method);\n const signal = opts?.signal;\n\n if (signal?.aborted) throw new ProtocolCancelledError(call);\n\n const work = start();\n // No bound and no abort wanted: hand back the untouched promise rather than wrapping it\n // in timers that would never fire.\n if (!Number.isFinite(bounds.ceilingMs) && !signal && !opts?.onPending) return work;\n\n let deadline: { setAwaiting: (a: boolean) => void; dispose: () => void } | undefined;\n let notice: ReturnType<typeof setTimeout> | undefined;\n let unsubscribeAttention: (() => void) | undefined;\n let onAbort: (() => void) | undefined;\n const started = Date.now();\n\n try {\n return await new Promise<T>((resolve, reject) => {\n // `work` settling always wins — a call that answered a millisecond before its\n // deadline must not be reported as a timeout.\n work.then(resolve, reject);\n\n // The host's live \"a person is being asked\" signal. Read defensively: a host that\n // never pushes the channel leaves this at \"not awaiting\", which is exactly the\n // pre-R3-307 behaviour for a call on its idle bound.\n let attention: HostAttention | undefined;\n let noticeFired = false;\n const fireNotice = () => {\n try {\n opts?.onPending?.({\n call,\n attendance,\n elapsedMs: Date.now() - started,\n ...(attendanceReason(scheme, method)\n ? { reason: attendanceReason(scheme, method) as string }\n : {}),\n ...(attention?.awaiting\n ? { awaiting: { kind: attention.kind, since: attention.since } }\n : {}),\n });\n } catch {\n /* a caller's render callback must never break the call it describes */\n }\n };\n\n deadline = createSuspendableDeadline({\n bounds,\n onExpire: (bound, elapsedBoundMs) =>\n reject(new ProtocolTimeoutError(call, elapsedBoundMs, attendance, bound)),\n });\n\n if (opts?.onPending) {\n notice = setTimeout(() => {\n noticeFired = true;\n fireNotice();\n }, PENDING_NOTICE_MS);\n }\n\n // Subscribing invokes the listener immediately with the current value, so the\n // deadline starts in the right state even if a prompt was already up.\n //\n // Guarded because this sits on the path of EVERY host call: a fault in the attention\n // channel must degrade to \"no signal\" (the pre-R3-307 behaviour, bounds unsuspended),\n // never take down every request in the SDK.\n try {\n unsubscribeAttention = onHostAttentionChange((next) => {\n const wasAwaiting = attention?.awaiting ?? false;\n attention = next;\n deadline?.setAwaiting(next.awaiting);\n // Re-notify once the notice has fired: \"still waiting\" → \"tap your passkey\" is\n // the whole point, and a caller rendering a waiting state wants the live\n // sentence, not the one that was true three seconds in.\n if (noticeFired && next.awaiting !== wasAwaiting) fireNotice();\n });\n } catch {\n /* no attention signal available — the bounds simply never suspend */\n }\n\n if (signal) {\n onAbort = () => reject(new ProtocolCancelledError(call));\n signal.addEventListener('abort', onAbort, { once: true });\n }\n });\n } finally {\n deadline?.dispose();\n if (notice !== undefined) clearTimeout(notice);\n unsubscribeAttention?.();\n if (signal && onAbort) signal.removeEventListener('abort', onAbort);\n // The host may still answer after we stopped waiting; swallow it so a late rejection\n // does not surface as an unhandled promise rejection in the app's console.\n work.catch(() => undefined);\n }\n}\n"],"mappings":";AAKA,SAAS,iBAAiB;AAC1B,SAAS,6BAAiD;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAEP,SAAS,aAAa,mBAAmB;AAqBlC,MAAM,kBAAkB,CAC7B,cACA,QACA,QACA,SAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA,MAAM,UAAU,EAAE,gBAAgB,cAAc,QAAQ,MAAM;AAAA,EAC9D;AACF;AAaF,eAAsB,aACpB,QACA,QACA,OACA,MACY;AACZ,QAAM,OAAO,GAAG,MAAM,IAAI,MAAM;AAChC,QAAM,aAAa,aAAa,QAAQ,MAAM;AAC9C,QAAM,SACJ,MAAM,cAAc,SAChB,EAAE,QAAQ,KAAK,WAAW,WAAW,KAAK,UAAU,IACpD,UAAU,QAAQ,MAAM;AAC9B,QAAM,SAAS,MAAM;AAErB,MAAI,QAAQ,QAAS,OAAM,IAAI,uBAAuB,IAAI;AAE1D,QAAM,OAAO,MAAM;AAGnB,MAAI,CAAC,OAAO,SAAS,OAAO,SAAS,KAAK,CAAC,UAAU,CAAC,MAAM,UAAW,QAAO;AAE9E,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,MAAI;AACJ,QAAM,UAAU,KAAK,IAAI;AAEzB,MAAI;AACF,WAAO,MAAM,IAAI,QAAW,CAAC,SAAS,WAAW;AAG/C,WAAK,KAAK,SAAS,MAAM;AAKzB,UAAI;AACJ,UAAI,cAAc;AAClB,YAAM,aAAa,MAAM;AACvB,YAAI;AACF,gBAAM,YAAY;AAAA,YAChB;AAAA,YACA;AAAA,YACA,WAAW,KAAK,IAAI,IAAI;AAAA,YACxB,GAAI,iBAAiB,QAAQ,MAAM,IAC/B,EAAE,QAAQ,iBAAiB,QAAQ,MAAM,EAAY,IACrD,CAAC;AAAA,YACL,GAAI,WAAW,WACX,EAAE,UAAU,EAAE,MAAM,UAAU,MAAM,OAAO,UAAU,MAAM,EAAE,IAC7D,CAAC;AAAA,UACP,CAAC;AAAA,QACH,QAAQ;AAAA,QAER;AAAA,MACF;AAEA,iBAAW,0BAA0B;AAAA,QACnC;AAAA,QACA,UAAU,CAAC,OAAO,mBAChB,OAAO,IAAI,qBAAqB,MAAM,gBAAgB,YAAY,KAAK,CAAC;AAAA,MAC5E,CAAC;AAED,UAAI,MAAM,WAAW;AACnB,iBAAS,WAAW,MAAM;AACxB,wBAAc;AACd,qBAAW;AAAA,QACb,GAAG,iBAAiB;AAAA,MACtB;AAQA,UAAI;AACF,+BAAuB,sBAAsB,CAAC,SAAS;AACrD,gBAAM,cAAc,WAAW,YAAY;AAC3C,sBAAY;AACZ,oBAAU,YAAY,KAAK,QAAQ;AAInC,cAAI,eAAe,KAAK,aAAa,YAAa,YAAW;AAAA,QAC/D,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAEA,UAAI,QAAQ;AACV,kBAAU,MAAM,OAAO,IAAI,uBAAuB,IAAI,CAAC;AACvD,eAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH,UAAE;AACA,cAAU,QAAQ;AAClB,QAAI,WAAW,OAAW,cAAa,MAAM;AAC7C,2BAAuB;AACvB,QAAI,UAAU,QAAS,QAAO,oBAAoB,SAAS,OAAO;AAGlE,SAAK,MAAM,MAAM,MAAS;AAAA,EAC5B;AACF;","names":[]}
package/dist/version.cjs CHANGED
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  SDK_VERSION: () => SDK_VERSION
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const SDK_VERSION = "0.45.3";
24
+ const SDK_VERSION = "0.46.0";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  SDK_VERSION
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.45.3';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.46.0';\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc;","names":[]}
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.45.3";
2
+ declare const SDK_VERSION = "0.46.0";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** This SDK's package version, baked from package.json at build (SP2-6). */
2
- declare const SDK_VERSION = "0.45.3";
2
+ declare const SDK_VERSION = "0.46.0";
3
3
 
4
4
  export { SDK_VERSION };
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import "./chunk-VHAA22YE.js";
2
- const SDK_VERSION = "0.45.3";
2
+ const SDK_VERSION = "0.46.0";
3
3
  export {
4
4
  SDK_VERSION
5
5
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.45.3';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// GENERATED by scripts/gen-version.mjs from package.json — do not edit by hand.\n// Regenerated on every build (prebuild); kept honest by version.test.ts.\n\n/** This SDK's package version, baked from package.json at build (SP2-6). */\nexport const SDK_VERSION = '0.46.0';\n"],"mappings":";AAIO,MAAM,cAAc;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immediately-run/sdk",
3
- "version": "0.45.3",
3
+ "version": "0.46.0",
4
4
  "description": "Runtime SDK for code executing inside an immediately.run sandbox.",
5
5
  "license": "MIT",
6
6
  "repository": "github:immediately-run/immediately-run-sdk",
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@immediately-run/platform-constants": "0.2.0",
54
- "@immediately-run/sandbox-protocol": "0.3.1",
54
+ "@immediately-run/sandbox-protocol": "0.4.0",
55
55
  "react-error-boundary": "^6.0.0"
56
56
  },
57
57
  "devDependencies": {