@immediately-run/sdk 0.45.3 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MDXProvider.cjs +1 -5
- package/dist/MDXProvider.cjs.map +1 -1
- package/dist/MDXProvider.js +1 -5
- package/dist/MDXProvider.js.map +1 -1
- package/dist/RoutingSpec.cjs.map +1 -1
- package/dist/TinkerableContext.cjs.map +1 -1
- package/dist/TinkerableContext.js.map +1 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/boot.cjs +2 -9
- package/dist/boot.cjs.map +1 -1
- package/dist/boot.d.cts +2 -2
- package/dist/boot.d.ts +2 -2
- package/dist/boot.js +2 -9
- package/dist/boot.js.map +1 -1
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/components/Admonition.cjs +4 -13
- package/dist/components/Admonition.cjs.map +1 -1
- package/dist/components/Admonition.js +4 -13
- package/dist/components/Admonition.js.map +1 -1
- package/dist/components/FileRouter.cjs +11 -3
- package/dist/components/FileRouter.cjs.map +1 -1
- package/dist/components/FileRouter.js +11 -3
- package/dist/components/FileRouter.js.map +1 -1
- package/dist/components/HeadingAnchor.cjs.map +1 -1
- package/dist/components/HeadingAnchor.js.map +1 -1
- package/dist/components/Link.cjs.map +1 -1
- package/dist/components/Link.js.map +1 -1
- package/dist/components/MainContent.cjs +11 -1
- package/dist/components/MainContent.cjs.map +1 -1
- package/dist/components/MainContent.js +11 -1
- package/dist/components/MainContent.js.map +1 -1
- package/dist/components/MountImage.cjs +1 -9
- package/dist/components/MountImage.cjs.map +1 -1
- package/dist/components/MountImage.js +1 -9
- package/dist/components/MountImage.js.map +1 -1
- package/dist/components/Routes.cjs +1 -4
- package/dist/components/Routes.cjs.map +1 -1
- package/dist/components/Routes.d.cts +1 -1
- package/dist/components/Routes.d.ts +1 -1
- package/dist/components/Routes.js +1 -4
- package/dist/components/Routes.js.map +1 -1
- package/dist/components/SafeInclude.cjs +1 -4
- package/dist/components/SafeInclude.cjs.map +1 -1
- package/dist/components/SafeInclude.js +1 -4
- package/dist/components/SafeInclude.js.map +1 -1
- package/dist/components/WikiLink.cjs +1 -10
- package/dist/components/WikiLink.cjs.map +1 -1
- package/dist/components/WikiLink.js +1 -10
- package/dist/components/WikiLink.js.map +1 -1
- package/dist/components/defaults.cjs.map +1 -1
- package/dist/components/defaults.d.cts +1 -1
- package/dist/components/defaults.d.ts +1 -1
- package/dist/components/defaults.js.map +1 -1
- package/dist/components/errors.cjs +3 -1
- package/dist/components/errors.cjs.map +1 -1
- package/dist/components/errors.js +3 -1
- package/dist/components/errors.js.map +1 -1
- package/dist/contextUtils.cjs.map +1 -1
- package/dist/contextUtils.js.map +1 -1
- package/dist/contribute.cjs.map +1 -1
- package/dist/contribute.js.map +1 -1
- package/dist/debug.cjs +2 -1
- package/dist/debug.cjs.map +1 -1
- package/dist/debug.js +3 -8
- package/dist/debug.js.map +1 -1
- package/dist/diagnostics.cjs.map +1 -1
- package/dist/diagnostics.js.map +1 -1
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js.map +1 -1
- package/dist/editorContext.cjs.map +1 -1
- package/dist/editorContext.js.map +1 -1
- package/dist/fs.cjs.map +1 -1
- package/dist/fs.js.map +1 -1
- package/dist/hostAttention.cjs +57 -0
- package/dist/hostAttention.cjs.map +1 -0
- package/dist/hostAttention.d.cts +39 -0
- package/dist/hostAttention.d.ts +39 -0
- package/dist/hostAttention.js +31 -0
- package/dist/hostAttention.js.map +1 -0
- package/dist/hostTransport.cjs +55 -0
- package/dist/hostTransport.cjs.map +1 -0
- package/dist/hostTransport.d.cts +12 -0
- package/dist/hostTransport.d.ts +12 -0
- package/dist/hostTransport.js +30 -0
- package/dist/hostTransport.js.map +1 -0
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/injectedBundler.cjs.map +1 -1
- package/dist/injectedBundler.js.map +1 -1
- package/dist/ipc.cjs +1 -4
- package/dist/ipc.cjs.map +1 -1
- package/dist/ipc.js +1 -4
- package/dist/ipc.js.map +1 -1
- package/dist/irMarkers.cjs.map +1 -1
- package/dist/irMarkers.d.cts +11 -11
- package/dist/irMarkers.d.ts +11 -11
- package/dist/irMarkers.js.map +1 -1
- package/dist/launch.cjs.map +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/linkSpace.cjs +11 -2
- package/dist/linkSpace.cjs.map +1 -1
- package/dist/linkSpace.d.cts +23 -0
- package/dist/linkSpace.d.ts +23 -0
- package/dist/linkSpace.js +11 -2
- package/dist/linkSpace.js.map +1 -1
- package/dist/llm.cjs +26 -8
- package/dist/llm.cjs.map +1 -1
- package/dist/llm.d.cts +75 -3
- package/dist/llm.d.ts +75 -3
- package/dist/llm.js +21 -7
- package/dist/llm.js.map +1 -1
- package/dist/loading.cjs +18 -14
- package/dist/loading.cjs.map +1 -1
- package/dist/loading.d.cts +2 -2
- package/dist/loading.d.ts +2 -2
- package/dist/loading.js +19 -19
- package/dist/loading.js.map +1 -1
- package/dist/markers.cjs.map +1 -1
- package/dist/markers.js.map +1 -1
- package/dist/metadataSource.cjs +1 -5
- package/dist/metadataSource.cjs.map +1 -1
- package/dist/metadataSource.d.cts +1 -1
- package/dist/metadataSource.d.ts +1 -1
- package/dist/metadataSource.js +1 -5
- package/dist/metadataSource.js.map +1 -1
- package/dist/moduleCache.cjs +4 -1
- package/dist/moduleCache.cjs.map +1 -1
- package/dist/moduleCache.d.cts +1 -1
- package/dist/moduleCache.d.ts +1 -1
- package/dist/moduleCache.js +4 -1
- package/dist/moduleCache.js.map +1 -1
- package/dist/mountMatch.cjs.map +1 -1
- package/dist/mountMatch.js.map +1 -1
- package/dist/mounts.cjs +6 -1
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +2 -2
- package/dist/mounts.d.ts +2 -2
- package/dist/mounts.js +6 -1
- package/dist/mounts.js.map +1 -1
- package/dist/netFetch.cjs +3 -5
- package/dist/netFetch.cjs.map +1 -1
- package/dist/netFetch.js +3 -5
- package/dist/netFetch.js.map +1 -1
- package/dist/onFsChange.cjs.map +1 -1
- package/dist/onFsChange.js.map +1 -1
- package/dist/pathUtils.cjs +9 -12
- package/dist/pathUtils.cjs.map +1 -1
- package/dist/pathUtils.js +9 -12
- package/dist/pathUtils.js.map +1 -1
- package/dist/protocolDeadline.cjs +205 -0
- package/dist/protocolDeadline.cjs.map +1 -0
- package/dist/protocolDeadline.d.cts +146 -0
- package/dist/protocolDeadline.d.ts +146 -0
- package/dist/protocolDeadline.js +168 -0
- package/dist/protocolDeadline.js.map +1 -0
- package/dist/protocolStream.cjs +49 -6
- package/dist/protocolStream.cjs.map +1 -1
- package/dist/protocolStream.d.cts +9 -2
- package/dist/protocolStream.d.ts +9 -2
- package/dist/protocolStream.js +56 -6
- package/dist/protocolStream.js.map +1 -1
- package/dist/pushChannel.cjs +13 -9
- package/dist/pushChannel.cjs.map +1 -1
- package/dist/pushChannel.js +12 -8
- package/dist/pushChannel.js.map +1 -1
- package/dist/ready.cjs.map +1 -1
- package/dist/ready.js.map +1 -1
- package/dist/routing.cjs +3 -1
- package/dist/routing.cjs.map +1 -1
- package/dist/routing.js +3 -1
- package/dist/routing.js.map +1 -1
- package/dist/safeContent/index.cjs.map +1 -1
- package/dist/safeContent/index.js.map +1 -1
- package/dist/safeContent/parseSafeMdast.cjs.map +1 -1
- package/dist/safeContent/parseSafeMdast.js.map +1 -1
- package/dist/safeContent/renderMdast.cjs.map +1 -1
- package/dist/safeContent/renderMdast.js.map +1 -1
- package/dist/sandboxTypes.cjs.map +1 -1
- package/dist/sandboxUtils.cjs +69 -24
- package/dist/sandboxUtils.cjs.map +1 -1
- package/dist/sandboxUtils.d.cts +37 -4
- package/dist/sandboxUtils.d.ts +37 -4
- package/dist/sandboxUtils.js +74 -22
- package/dist/sandboxUtils.js.map +1 -1
- package/dist/scrollToId.cjs.map +1 -1
- package/dist/scrollToId.js.map +1 -1
- package/dist/secrets.cjs.map +1 -1
- package/dist/secrets.js.map +1 -1
- package/dist/tasks.cjs +12 -2
- package/dist/tasks.cjs.map +1 -1
- package/dist/tasks.js +12 -2
- package/dist/tasks.js.map +1 -1
- package/dist/testing.cjs +1 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js +1 -3
- package/dist/testing.js.map +1 -1
- package/dist/theme.cjs.map +1 -1
- package/dist/theme.js.map +1 -1
- package/dist/urlUtils.cjs +7 -18
- package/dist/urlUtils.cjs.map +1 -1
- package/dist/urlUtils.js +7 -18
- package/dist/urlUtils.js.map +1 -1
- package/dist/vcs.cjs +1 -3
- package/dist/vcs.cjs.map +1 -1
- package/dist/vcs.js +1 -3
- package/dist/vcs.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +11 -5
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import "./chunk-VHAA22YE.js";
|
|
2
|
+
const UNATTENDED_TIMEOUT_MS = 3e4;
|
|
3
|
+
const NETWORK_TIMEOUT_MS = 12e4;
|
|
4
|
+
const ATTENDED_TIMEOUT_MS = 6e5;
|
|
5
|
+
const ATTENDED_FIRST_FRAME_MS = 3e5;
|
|
6
|
+
const STREAM_IDLE_TIMEOUT_MS = 12e4;
|
|
7
|
+
const PENDING_NOTICE_MS = 3e3;
|
|
8
|
+
const ATTENDED = {
|
|
9
|
+
// The powerbox and the add-secret modal are host-drawn and wait for the user to type or
|
|
10
|
+
// pick; the first use of any stored secret additionally raises a WebAuthn assertion
|
|
11
|
+
// (SECRETS_SPEC §3 — one unlock per session, from a live gesture). All three are wrapped
|
|
12
|
+
// presenters, so the signal covers this scheme completely.
|
|
13
|
+
secrets: {
|
|
14
|
+
reason: "host-drawn key entry / picker, and the per-session passkey unlock",
|
|
15
|
+
idleMs: UNATTENDED_TIMEOUT_MS
|
|
16
|
+
},
|
|
17
|
+
// Consent is raised INSIDE the request: presentMountConsent, presentGrantPicker,
|
|
18
|
+
// presentCreateConsent, presentShareDisclosure, presentReferenceConsent — every one of
|
|
19
|
+
// them a wrapped presenter. Unattended once the grant is held, attended on first use, and
|
|
20
|
+
// since R3-307 the host says which of those is happening.
|
|
21
|
+
spaces: {
|
|
22
|
+
reason: "first-use mount/share/create consent is drawn inside the request",
|
|
23
|
+
idleMs: UNATTENDED_TIMEOUT_MS
|
|
24
|
+
},
|
|
25
|
+
settings: {
|
|
26
|
+
reason: "settings verbs reach the same consent and picker surfaces as spaces",
|
|
27
|
+
idleMs: UNATTENDED_TIMEOUT_MS
|
|
28
|
+
},
|
|
29
|
+
// The contribute flow shows the full diff for approval before anything is written
|
|
30
|
+
// (TRUST_AND_SAFETY TS-19b: the approval MUST show the real diff, so a human reads it).
|
|
31
|
+
// NOT a wrapped presenter — no `idleMs`.
|
|
32
|
+
contribute: { reason: "the diff-approval step is a human read of the whole change" },
|
|
33
|
+
// A task is an app bound to a transient slot that the user interacts with; it returns
|
|
34
|
+
// when they finish, which is human-paced by construction. That is an APP's interaction,
|
|
35
|
+
// not a host prompt, so the attention channel never fires for it — no `idleMs`.
|
|
36
|
+
task: { reason: "a task app runs an interaction and returns when the user finishes" },
|
|
37
|
+
// Launching a target can raise consent for a not-yet-granted app — through the launch
|
|
38
|
+
// flow's own surface, not one of the wrapped presenters. No `idleMs`.
|
|
39
|
+
launch: { reason: "may raise first-use consent for the launched target" },
|
|
40
|
+
// A drag is a gesture in progress — its duration is the user's hand, and no host prompt
|
|
41
|
+
// is up while it happens. No `idleMs`.
|
|
42
|
+
dnd: { reason: "a drag is a human gesture in flight" },
|
|
43
|
+
// The chat stream's FIRST frame sits behind the session's first passkey unseal — the
|
|
44
|
+
// exact hang the dogfood run found — and that unseal IS a wrapped presenter. But the idle
|
|
45
|
+
// bound here is the NETWORK one, not the channel one: with no prompt up, this call is
|
|
46
|
+
// waiting on an arbitrary upstream model, and thirty seconds is a normal generation.
|
|
47
|
+
llm: {
|
|
48
|
+
reason: "the first frame can sit behind the session passkey unseal",
|
|
49
|
+
idleMs: NETWORK_TIMEOUT_MS
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
function attendedEntry(scheme, method) {
|
|
53
|
+
return ATTENDED[`${scheme}:${method}`] ?? ATTENDED[scheme];
|
|
54
|
+
}
|
|
55
|
+
function attendedReason(scheme, method) {
|
|
56
|
+
return attendedEntry(scheme, method)?.reason;
|
|
57
|
+
}
|
|
58
|
+
function attendanceOf(scheme, method) {
|
|
59
|
+
return attendedReason(scheme, method) ? "attended" : "unattended";
|
|
60
|
+
}
|
|
61
|
+
function attendanceReason(scheme, method) {
|
|
62
|
+
return attendedReason(scheme, method);
|
|
63
|
+
}
|
|
64
|
+
function timeoutFor(scheme, method) {
|
|
65
|
+
if (attendanceOf(scheme, method) === "attended") return ATTENDED_TIMEOUT_MS;
|
|
66
|
+
if (scheme === "fetch") return NETWORK_TIMEOUT_MS;
|
|
67
|
+
return UNATTENDED_TIMEOUT_MS;
|
|
68
|
+
}
|
|
69
|
+
function firstFrameTimeoutFor(scheme, method) {
|
|
70
|
+
return attendanceOf(scheme, method) === "attended" ? ATTENDED_FIRST_FRAME_MS : NETWORK_TIMEOUT_MS;
|
|
71
|
+
}
|
|
72
|
+
function boundsFor(scheme, method) {
|
|
73
|
+
const ceilingMs = timeoutFor(scheme, method);
|
|
74
|
+
const idleMs = attendedEntry(scheme, method)?.idleMs;
|
|
75
|
+
return { idleMs: idleMs === void 0 ? ceilingMs : Math.min(idleMs, ceilingMs), ceilingMs };
|
|
76
|
+
}
|
|
77
|
+
function firstFrameBoundsFor(scheme, method) {
|
|
78
|
+
const ceilingMs = firstFrameTimeoutFor(scheme, method);
|
|
79
|
+
const idleMs = attendedEntry(scheme, method)?.idleMs;
|
|
80
|
+
return { idleMs: idleMs === void 0 ? ceilingMs : Math.min(idleMs, ceilingMs), ceilingMs };
|
|
81
|
+
}
|
|
82
|
+
function createSuspendableDeadline(opts) {
|
|
83
|
+
const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
84
|
+
const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h));
|
|
85
|
+
const { idleMs, ceilingMs } = opts.bounds;
|
|
86
|
+
const hasIdleLeg = Number.isFinite(idleMs) && idleMs < ceilingMs;
|
|
87
|
+
let done = false;
|
|
88
|
+
let idle;
|
|
89
|
+
let ceiling;
|
|
90
|
+
const expire = (bound, boundMs) => {
|
|
91
|
+
if (done) return;
|
|
92
|
+
done = true;
|
|
93
|
+
opts.onExpire(bound, boundMs);
|
|
94
|
+
};
|
|
95
|
+
const armIdle = () => {
|
|
96
|
+
if (done || !hasIdleLeg || idle !== void 0) return;
|
|
97
|
+
idle = setTimer(() => {
|
|
98
|
+
idle = void 0;
|
|
99
|
+
expire("idle", idleMs);
|
|
100
|
+
}, idleMs);
|
|
101
|
+
};
|
|
102
|
+
const disarmIdle = () => {
|
|
103
|
+
if (idle !== void 0) {
|
|
104
|
+
clearTimer(idle);
|
|
105
|
+
idle = void 0;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
if (Number.isFinite(ceilingMs)) {
|
|
109
|
+
ceiling = setTimer(() => {
|
|
110
|
+
ceiling = void 0;
|
|
111
|
+
expire("ceiling", ceilingMs);
|
|
112
|
+
}, ceilingMs);
|
|
113
|
+
}
|
|
114
|
+
armIdle();
|
|
115
|
+
return {
|
|
116
|
+
setAwaiting(awaiting) {
|
|
117
|
+
if (done) return;
|
|
118
|
+
if (awaiting) disarmIdle();
|
|
119
|
+
else armIdle();
|
|
120
|
+
},
|
|
121
|
+
dispose() {
|
|
122
|
+
done = true;
|
|
123
|
+
disarmIdle();
|
|
124
|
+
if (ceiling !== void 0) {
|
|
125
|
+
clearTimer(ceiling);
|
|
126
|
+
ceiling = void 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
class ProtocolTimeoutError extends Error {
|
|
132
|
+
constructor(call, timeoutMs, attendance, bound = attendance === "attended" ? "ceiling" : "idle") {
|
|
133
|
+
super(
|
|
134
|
+
attendance === "attended" && bound === "ceiling" ? `immediately.run: ${call} was abandoned after ${Math.round(timeoutMs / 1e3)}s waiting for you` : `immediately.run: ${call} did not respond within ${Math.round(timeoutMs / 1e3)}s`
|
|
135
|
+
);
|
|
136
|
+
this.code = "timeout";
|
|
137
|
+
this.name = "ProtocolTimeoutError";
|
|
138
|
+
this.call = call;
|
|
139
|
+
this.timeoutMs = timeoutMs;
|
|
140
|
+
this.attendance = attendance;
|
|
141
|
+
this.bound = bound;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
class ProtocolCancelledError extends Error {
|
|
145
|
+
constructor(call) {
|
|
146
|
+
super(`immediately.run: ${call} was cancelled`);
|
|
147
|
+
this.code = "cancelled";
|
|
148
|
+
this.name = "ProtocolCancelledError";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
ATTENDED_FIRST_FRAME_MS,
|
|
153
|
+
ATTENDED_TIMEOUT_MS,
|
|
154
|
+
NETWORK_TIMEOUT_MS,
|
|
155
|
+
PENDING_NOTICE_MS,
|
|
156
|
+
ProtocolCancelledError,
|
|
157
|
+
ProtocolTimeoutError,
|
|
158
|
+
STREAM_IDLE_TIMEOUT_MS,
|
|
159
|
+
UNATTENDED_TIMEOUT_MS,
|
|
160
|
+
attendanceOf,
|
|
161
|
+
attendanceReason,
|
|
162
|
+
boundsFor,
|
|
163
|
+
createSuspendableDeadline,
|
|
164
|
+
firstFrameBoundsFor,
|
|
165
|
+
firstFrameTimeoutFor,
|
|
166
|
+
timeoutFor
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=protocolDeadline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/protocolDeadline.ts"],"sourcesContent":["// Deadlines for host protocol calls (R3-298) — so no platform request can hang forever.\n//\n// THE FAILURE THIS FIXES. `protocolRequest` and `hostFetch` carried no timeout, so a host\n// operation that never resolved presented as an indefinite \"Running…\" with no error, no\n// cancel, and nothing in the console. The GLM dogfood run hit exactly this: the first\n// `chat()` of a session parks on a WebAuthn unseal that never completes, and the surface\n// simply waits. A first-run user inside the setup wizard (R3-299) would be stranded on\n// \"Testing…\" — at the worst possible moment, on the screen whose whole purpose is to prove\n// setup worked.\n//\n// WHY A SINGLE CONSTANT IS THE WRONG ANSWER, AND WHAT IS DONE INSTEAD. A blanket timeout\n// was deferred once as risky, correctly: some host operations legitimately await a HUMAN —\n// a passkey tap, a consent decision, a file picker — and a flat deadline aborts those. So\n// calls are classified, and the two classes get very different bounds:\n//\n// unattended — a network call or a channel round-trip. Nobody is being asked anything, so\n// a reply that has not arrived in tens of seconds is a fault. Short bound.\n// attended — the host may draw chrome and wait for the user. The bound exists only to\n// stop an ABANDONED prompt from pinning the caller forever, so it is set far\n// beyond human reaction time.\n//\n// NOTHING IS UNBOUNDED. \"A long or absent deadline\" was the licence; absent is declined,\n// because absent is the bug. An attended bound of minutes never aborts a person who is\n// actually deciding, and does release a caller whose user walked away — which is the\n// difference between a slow flow and a wedged one.\n//\n// THE IMPRECISION, AND HOW R3-307 REMOVED IT. Attendedness is classified per\n// (scheme, method), but it is really a property of a MOMENT: `spaces:mount` is unattended\n// when the grant is already held and attended on first use, because the host raises consent\n// inside the request (`spaceHandler` `presentMountConsent`). A per-method table cannot see\n// that, so R3-298 classified any method that MAY prompt as `attended` and gave it the long\n// bound — which meant the common grant-held path also waited ten minutes before reporting a\n// fault.\n//\n// R3-307 added the host-attention channel (`hostAttention.ts`), on which the host says\n// whether a person is being asked something RIGHT NOW. So a call now carries TWO bounds:\n//\n// idle — in force while the host is not prompting. A may-prompt call runs on this,\n// which is a real correctness gain: the grant-held `spaces:mount` faults in\n// seconds, as it always should have.\n// ceiling — absolute, from call start, NEVER suspended. An abandoned prompt still\n// releases the caller. The signal may EXTEND a deadline, never remove it.\n//\n// AND THE SHORTENING IS OPT-IN PER ENTRY, which is the part worth not losing. A scheme\n// drops to the short idle bound only when EVERY prompt it can raise is one the host\n// actually announces on that channel (the powerbox, the add-secret modal, the passkey\n// unlock, and the spaceHandler consent surfaces — the presenters site-main wraps). A task\n// app's interaction and the contribute diff-approval are human-paced but are NOT host\n// prompts, so no signal would ever fire for them and they keep the full attended bound. A\n// signal that cannot fire must never be allowed to shorten a deadline.\n\nimport type { HostAttentionKind } from './hostAttention';\n\n/** Whether a call may block on a human being asked something. */\nexport type Attendance = 'unattended' | 'attended';\n\n/** Milliseconds. Exported so callers can reason about the defaults they are overriding. */\nexport const UNATTENDED_TIMEOUT_MS = 30_000;\n/** Network calls reach arbitrary upstreams; the host bounds the fetch itself, so this is a\n * backstop against the host never replying, not a request budget. */\nexport const NETWORK_TIMEOUT_MS = 120_000;\n/** Far beyond human reaction time — this exists only so an ABANDONED prompt releases the\n * caller. It must never be short enough to abort someone who is deciding. */\nexport const ATTENDED_TIMEOUT_MS = 600_000;\n/** A stream's first frame may be behind an unseal, so it gets an attended-scale bound of\n * its own. See `firstFrameTimeoutFor`. */\nexport const ATTENDED_FIRST_FRAME_MS = 300_000;\n/** After the first frame, silence this long means the stream is wedged: the host is no\n * longer producing and no human is being asked. */\nexport const STREAM_IDLE_TIMEOUT_MS = 120_000;\n/** When a call passes this, `onPending` fires so a caller can render a waiting state\n * instead of an unexplained stall. */\nexport const PENDING_NOTICE_MS = 3_000;\n\n/**\n * Methods that may draw host chrome and wait for the user.\n *\n * Each entry is a `scheme:method` or a bare `scheme` (matching every method of it). The\n * REASON is recorded per entry, because this table is the item's actual content — a future\n * reader must be able to see why a method is on the long bound without re-deriving it from\n * the host source.\n */\ninterface AttendedEntry {\n /** Why this call may block on a human. */\n reason: string;\n /**\n * The bound this call runs on while the host reports NOBODY is being asked (R3-307).\n *\n * Set it ONLY when every prompt this entry can raise is one the host announces on the\n * host-attention channel — i.e. a presenter site-main wraps (`hostAttention.ts` in that\n * repo). Omitted ⇒ the call keeps the full attended bound at all times, because a signal\n * that never fires must not be allowed to shorten a deadline.\n */\n idleMs?: number;\n}\n\nconst ATTENDED: Record<string, AttendedEntry> = {\n // The powerbox and the add-secret modal are host-drawn and wait for the user to type or\n // pick; the first use of any stored secret additionally raises a WebAuthn assertion\n // (SECRETS_SPEC §3 — one unlock per session, from a live gesture). All three are wrapped\n // presenters, so the signal covers this scheme completely.\n secrets: {\n reason: 'host-drawn key entry / picker, and the per-session passkey unlock',\n idleMs: UNATTENDED_TIMEOUT_MS,\n },\n // Consent is raised INSIDE the request: presentMountConsent, presentGrantPicker,\n // presentCreateConsent, presentShareDisclosure, presentReferenceConsent — every one of\n // them a wrapped presenter. Unattended once the grant is held, attended on first use, and\n // since R3-307 the host says which of those is happening.\n spaces: {\n reason: 'first-use mount/share/create consent is drawn inside the request',\n idleMs: UNATTENDED_TIMEOUT_MS,\n },\n settings: {\n reason: 'settings verbs reach the same consent and picker surfaces as spaces',\n idleMs: UNATTENDED_TIMEOUT_MS,\n },\n // The contribute flow shows the full diff for approval before anything is written\n // (TRUST_AND_SAFETY TS-19b: the approval MUST show the real diff, so a human reads it).\n // NOT a wrapped presenter — no `idleMs`.\n contribute: { reason: 'the diff-approval step is a human read of the whole change' },\n // A task is an app bound to a transient slot that the user interacts with; it returns\n // when they finish, which is human-paced by construction. That is an APP's interaction,\n // not a host prompt, so the attention channel never fires for it — no `idleMs`.\n task: { reason: 'a task app runs an interaction and returns when the user finishes' },\n // Launching a target can raise consent for a not-yet-granted app — through the launch\n // flow's own surface, not one of the wrapped presenters. No `idleMs`.\n launch: { reason: 'may raise first-use consent for the launched target' },\n // A drag is a gesture in progress — its duration is the user's hand, and no host prompt\n // is up while it happens. No `idleMs`.\n dnd: { reason: 'a drag is a human gesture in flight' },\n // The chat stream's FIRST frame sits behind the session's first passkey unseal — the\n // exact hang the dogfood run found — and that unseal IS a wrapped presenter. But the idle\n // bound here is the NETWORK one, not the channel one: with no prompt up, this call is\n // waiting on an arbitrary upstream model, and thirty seconds is a normal generation.\n llm: {\n reason: 'the first frame can sit behind the session passkey unseal',\n idleMs: NETWORK_TIMEOUT_MS,\n },\n};\n\n/** Look up a scheme/method in the attended table, preferring the exact method entry. */\nfunction attendedEntry(scheme: string, method: string): AttendedEntry | undefined {\n return ATTENDED[`${scheme}:${method}`] ?? ATTENDED[scheme];\n}\n\n/** Look up a scheme/method in the attended table, preferring the exact method entry. */\nfunction attendedReason(scheme: string, method: string): string | undefined {\n return attendedEntry(scheme, method)?.reason;\n}\n\n/** Whether a call may block on a human. Exported for the classification test + tooling. */\nexport function attendanceOf(scheme: string, method: string): Attendance {\n return attendedReason(scheme, method) ? 'attended' : 'unattended';\n}\n\n/** Why a call is classified attended, or `undefined` when it is not. Exported so the\n * classification is legible from a test failure rather than only from this source. */\nexport function attendanceReason(scheme: string, method: string): string | undefined {\n return attendedReason(scheme, method);\n}\n\n/** The default deadline for a one-shot `protocolRequest`. */\nexport function timeoutFor(scheme: string, method: string): number {\n if (attendanceOf(scheme, method) === 'attended') return ATTENDED_TIMEOUT_MS;\n // `fetch` reaches an arbitrary upstream, so it gets the network bound rather than the\n // channel-round-trip one.\n if (scheme === 'fetch') return NETWORK_TIMEOUT_MS;\n return UNATTENDED_TIMEOUT_MS;\n}\n\n/**\n * The default TIME-TO-FIRST-FRAME bound for a stream.\n *\n * Deliberately not a total-duration bound: a long generation that is streaming normally is\n * healthy, and killing it would be a worse bug than the one being fixed. The hang has a\n * distinct shape — NO frames at all — so that is what is bounded, plus an idle gap between\n * frames once flowing. Together they fire exactly on a wedged stream and never on a slow\n * one.\n */\nexport function firstFrameTimeoutFor(scheme: string, method: string): number {\n return attendanceOf(scheme, method) === 'attended' ? ATTENDED_FIRST_FRAME_MS : NETWORK_TIMEOUT_MS;\n}\n\n/**\n * The two bounds a call runs under (R3-307).\n *\n * `idleMs` is in force while the host reports nobody is being asked; it is cleared while a\n * host prompt is up and restarted, in full, when the prompt goes away. `ceilingMs` runs from\n * call start and is NEVER suspended — it is what releases a caller whose user walked away.\n */\nexport interface CallBounds {\n /** The bound in force while the host is not waiting on a person. */\n idleMs: number;\n /** The absolute bound from call start. Never suspended. */\n ceilingMs: number;\n}\n\n/** Which of a call's two bounds elapsed. `idle` means the host was NOT prompting — nobody\n * was being asked anything, so this is a fault. `ceiling` means the absolute bound ran out,\n * which for an attended call is an abandoned prompt. */\nexport type DeadlineBound = 'idle' | 'ceiling';\n\n/** The bounds for a one-shot `protocolRequest`. */\nexport function boundsFor(scheme: string, method: string): CallBounds {\n const ceilingMs = timeoutFor(scheme, method);\n const idleMs = attendedEntry(scheme, method)?.idleMs;\n // `Math.min` so an `idleMs` can only ever tighten: an entry that named a bound longer than\n // its own ceiling would otherwise silently disable the idle leg.\n return { idleMs: idleMs === undefined ? ceilingMs : Math.min(idleMs, ceilingMs), ceilingMs };\n}\n\n/** The bounds for a stream's time-to-first-frame. */\nexport function firstFrameBoundsFor(scheme: string, method: string): CallBounds {\n const ceilingMs = firstFrameTimeoutFor(scheme, method);\n const idleMs = attendedEntry(scheme, method)?.idleMs;\n return { idleMs: idleMs === undefined ? ceilingMs : Math.min(idleMs, ceilingMs), ceilingMs };\n}\n\n/** A live deadline that the host-attention signal can suspend. */\nexport interface SuspendableDeadline {\n /** Tell the deadline whether the host is waiting on a person right now. */\n setAwaiting(awaiting: boolean): void;\n /** Clear every timer. Idempotent — safe to call from a `finally`. */\n dispose(): void;\n}\n\n/**\n * A deadline with a suspendable idle leg and an unsuspendable ceiling.\n *\n * Pure and injectable (`setTimer`/`clearTimer`) so the suspension rules are unit-testable\n * against a fake clock rather than by waiting minutes for real ones.\n *\n * The idle leg RESTARTS in full when a prompt clears rather than resuming where it left off.\n * That is deliberate: after the user dismisses a prompt the host begins fresh work, and the\n * seconds that elapsed before the prompt say nothing about how long that work should take.\n * The ceiling is what stops a repeatedly-prompting call from running forever.\n */\nexport function createSuspendableDeadline(opts: {\n bounds: CallBounds;\n /** Called once, with the bound that elapsed and its length in ms. */\n onExpire: (bound: DeadlineBound, boundMs: number) => void;\n setTimer?: (fn: () => void, ms: number) => unknown;\n clearTimer?: (handle: unknown) => void;\n}): SuspendableDeadline {\n const setTimer = opts.setTimer ?? ((fn, ms) => setTimeout(fn, ms));\n const clearTimer = opts.clearTimer ?? ((h) => clearTimeout(h as ReturnType<typeof setTimeout>));\n const { idleMs, ceilingMs } = opts.bounds;\n // An idle leg at or above the ceiling can never fire first, so don't arm one — that keeps\n // the common unattended case (idle === ceiling) on exactly one timer, as before R3-307.\n const hasIdleLeg = Number.isFinite(idleMs) && idleMs < ceilingMs;\n\n let done = false;\n let idle: unknown;\n let ceiling: unknown;\n\n const expire = (bound: DeadlineBound, boundMs: number) => {\n if (done) return;\n done = true;\n opts.onExpire(bound, boundMs);\n };\n\n const armIdle = () => {\n if (done || !hasIdleLeg || idle !== undefined) return;\n idle = setTimer(() => {\n idle = undefined;\n expire('idle', idleMs);\n }, idleMs);\n };\n const disarmIdle = () => {\n if (idle !== undefined) {\n clearTimer(idle);\n idle = undefined;\n }\n };\n\n if (Number.isFinite(ceilingMs)) {\n ceiling = setTimer(() => {\n ceiling = undefined;\n expire('ceiling', ceilingMs);\n }, ceilingMs);\n }\n armIdle();\n\n return {\n setAwaiting(awaiting: boolean) {\n if (done) return;\n if (awaiting) disarmIdle();\n else armIdle();\n },\n dispose() {\n done = true;\n disarmIdle();\n if (ceiling !== undefined) {\n clearTimer(ceiling);\n ceiling = undefined;\n }\n },\n };\n}\n\n/** The error a bounded call rejects with. `code` is `'timeout'` — the code R3-303's typed\n * provider-error taxonomy adopts, so apps see one vocabulary. */\nexport class ProtocolTimeoutError extends Error {\n readonly code = 'timeout';\n /** `scheme:method` of the call that timed out. */\n readonly call: string;\n /** The bound that elapsed, in ms. */\n readonly timeoutMs: number;\n /** Whether the call was on the attended or unattended bound — the first thing anyone\n * debugging a timeout needs, and otherwise invisible. */\n readonly attendance: Attendance;\n /** WHICH bound elapsed (R3-307). An attended call that faults on its `idle` bound was not\n * waiting on anyone — the host said so — and that is a genuinely different diagnosis from\n * an abandoned prompt hitting the `ceiling`. */\n readonly bound: DeadlineBound;\n constructor(\n call: string,\n timeoutMs: number,\n attendance: Attendance,\n bound: DeadlineBound = attendance === 'attended' ? 'ceiling' : 'idle',\n ) {\n super(\n attendance === 'attended' && bound === 'ceiling'\n ? `immediately.run: ${call} was abandoned after ${Math.round(timeoutMs / 1000)}s waiting for you`\n : `immediately.run: ${call} did not respond within ${Math.round(timeoutMs / 1000)}s`,\n );\n this.name = 'ProtocolTimeoutError';\n this.call = call;\n this.timeoutMs = timeoutMs;\n this.attendance = attendance;\n this.bound = bound;\n }\n}\n\n/** The error a cancelled call rejects with. */\nexport class ProtocolCancelledError extends Error {\n readonly code = 'cancelled';\n constructor(call: string) {\n super(`immediately.run: ${call} was cancelled`);\n this.name = 'ProtocolCancelledError';\n }\n}\n\n/** What the host is waiting for at this moment, as reported on the host-attention channel\n * (R3-307). Present on a {@link PendingState} only while the host IS prompting. */\nexport interface PendingAttention {\n /** The kind of prompt on screen, or `null` when the host reports a wait it cannot name. */\n kind: HostAttentionKind | null;\n /** `Date.now()` when the wait began, or `null`. */\n since: number | null;\n}\n\n/** What `onPending` is told when a call is taking a while. */\nexport interface PendingState {\n call: string;\n attendance: Attendance;\n elapsedMs: number;\n /** Why this call *may* be waiting on a person — present only when attended. It comes from\n * the classification table, so it is a standing possibility, not a live fact. */\n reason?: string;\n /**\n * What the host is waiting for RIGHT NOW (R3-307) — present only while a host prompt is\n * actually up. Prefer it over {@link reason} when rendering: \"tap your passkey\" is a\n * sentence the user can act on; \"this may need you\" is not.\n */\n awaiting?: PendingAttention;\n}\n\n/** Options accepted by every bounded host call. */\nexport interface BoundedCallOptions {\n /** Override the classified default. `Infinity` disables the bound — an escape hatch for a\n * caller that genuinely owns the wait (it must then provide its own way out).\n *\n * An explicit value is the WHOLE bound: it is never suspended by the host-attention\n * signal, because a caller that named a number owns the wait. */\n timeoutMs?: number;\n /** Abort the wait. The SDK stops waiting and rejects with `code: 'cancelled'`. */\n signal?: AbortSignal;\n /** Fired when the call passes `PENDING_NOTICE_MS`, so a caller can render a waiting state\n * rather than an unexplained pause — and again, after that, whenever the host starts or\n * stops waiting on the user, so the waiting state can name what is on screen now. */\n onPending?: (state: PendingState) => void;\n}\n"],"mappings":"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","names":[]}
|
package/dist/protocolStream.cjs
CHANGED
|
@@ -24,6 +24,7 @@ __export(protocolStream_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(protocolStream_exports);
|
|
26
26
|
var import_sandboxUtils = require("./sandboxUtils");
|
|
27
|
+
var import_protocolDeadline = require("./protocolDeadline");
|
|
27
28
|
class StreamError extends Error {
|
|
28
29
|
constructor(code, message) {
|
|
29
30
|
super(message);
|
|
@@ -36,7 +37,7 @@ const nextMsgId = () => {
|
|
|
36
37
|
streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
37
38
|
return streamCounter;
|
|
38
39
|
};
|
|
39
|
-
async function* consumeStream(transport, type, method, params, msgId = nextMsgId(), signal) {
|
|
40
|
+
async function* consumeStream(transport, type, method, params, msgId = nextMsgId(), signal, opts) {
|
|
40
41
|
const queue = [];
|
|
41
42
|
let wake = null;
|
|
42
43
|
let settled = false;
|
|
@@ -57,6 +58,49 @@ async function* consumeStream(transport, type, method, params, msgId = nextMsgId
|
|
|
57
58
|
w?.();
|
|
58
59
|
};
|
|
59
60
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
61
|
+
const scheme = type.startsWith("protocol-") ? type.slice("protocol-".length) : type;
|
|
62
|
+
const call = `${scheme}:${method}`;
|
|
63
|
+
const attendance = (0, import_protocolDeadline.attendanceOf)(scheme, method);
|
|
64
|
+
const firstFrameMs = opts?.timeoutMs ?? (0, import_protocolDeadline.firstFrameTimeoutFor)(scheme, method);
|
|
65
|
+
const idleMs = opts?.idleTimeoutMs ?? import_protocolDeadline.STREAM_IDLE_TIMEOUT_MS;
|
|
66
|
+
let sawFrame = false;
|
|
67
|
+
let noticed = false;
|
|
68
|
+
const awaitFrame = async () => {
|
|
69
|
+
const budget = sawFrame ? idleMs : firstFrameMs;
|
|
70
|
+
if (!Number.isFinite(budget)) {
|
|
71
|
+
await new Promise((resolve) => {
|
|
72
|
+
wake = resolve;
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const startedAt = Date.now();
|
|
77
|
+
let deadline;
|
|
78
|
+
let notice;
|
|
79
|
+
try {
|
|
80
|
+
await new Promise((resolve, reject) => {
|
|
81
|
+
wake = resolve;
|
|
82
|
+
deadline = setTimeout(() => reject(new import_protocolDeadline.ProtocolTimeoutError(call, budget, attendance)), budget);
|
|
83
|
+
if (opts?.onPending && !noticed) {
|
|
84
|
+
notice = setTimeout(() => {
|
|
85
|
+
noticed = true;
|
|
86
|
+
try {
|
|
87
|
+
opts.onPending?.({
|
|
88
|
+
call,
|
|
89
|
+
attendance,
|
|
90
|
+
elapsedMs: Date.now() - startedAt,
|
|
91
|
+
...(0, import_protocolDeadline.attendanceReason)(scheme, method) ? { reason: (0, import_protocolDeadline.attendanceReason)(scheme, method) } : {}
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}, import_protocolDeadline.PENDING_NOTICE_MS);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
} finally {
|
|
99
|
+
if (deadline !== void 0) clearTimeout(deadline);
|
|
100
|
+
if (notice !== void 0) clearTimeout(notice);
|
|
101
|
+
wake = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
60
104
|
try {
|
|
61
105
|
if (signal?.aborted) throw new StreamError("aborted", "stream aborted before start");
|
|
62
106
|
transport.send({ type, method, params, msgId, stream: true });
|
|
@@ -64,11 +108,10 @@ async function* consumeStream(transport, type, method, params, msgId = nextMsgId
|
|
|
64
108
|
while (true) {
|
|
65
109
|
if (signal?.aborted) throw new StreamError("aborted", "stream aborted");
|
|
66
110
|
if (queue.length === 0) {
|
|
67
|
-
await
|
|
68
|
-
wake = resolve;
|
|
69
|
-
});
|
|
111
|
+
await awaitFrame();
|
|
70
112
|
continue;
|
|
71
113
|
}
|
|
114
|
+
sawFrame = true;
|
|
72
115
|
const frame = queue.shift();
|
|
73
116
|
if (frame.kind === "event") {
|
|
74
117
|
yield frame.value;
|
|
@@ -91,8 +134,8 @@ const bundlerTransport = {
|
|
|
91
134
|
subscribe: (type, handler) => (0, import_sandboxUtils.addListener)(type, (msg) => handler(msg)),
|
|
92
135
|
cancel: (msg) => (0, import_sandboxUtils.sendMessage)(msg.type, msg)
|
|
93
136
|
};
|
|
94
|
-
function protocolStream(protocolName, method, params, signal) {
|
|
95
|
-
return consumeStream(bundlerTransport, protocolName, method, params, void 0, signal);
|
|
137
|
+
function protocolStream(protocolName, method, params, signal, opts) {
|
|
138
|
+
return consumeStream(bundlerTransport, protocolName, method, params, void 0, signal, opts);
|
|
96
139
|
}
|
|
97
140
|
// Annotate the CommonJS export names for ESM import in node:
|
|
98
141
|
0 && (module.exports = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\n/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\n/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n // Tell the host to STOP the stream early — abort the in-flight generation (and,\n // for `llm:chat`, the upstream provider fetch so it stops BILLING). Sent when the\n // consumer bails before a terminal frame: an early `break`/`return` out of the\n // `for await`, or an `AbortSignal` firing. Carries the same `(type, msgId)` the\n // host tagged its frames with, so the host aborts the matching generator\n // (LLM_AND_AGENTS_SPEC §3.3 \"abort the in-flight LLM request\"). Optional — a\n // transport predating the cancel frame simply omits it and the old behavior\n // (host runs to completion) stands.\n cancel?: (msg: { type: string; msgId: number; cancel: true }) => void;\n}\n\n/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n *\n * `signal` wires a caller {@link AbortSignal} to mid-stream cancellation: when it\n * fires (or the consumer `break`s before a terminal frame), the `finally` sends a\n * `cancel` frame back over `transport.cancel` so the HOST stops generating — without\n * it, aborting only stops the app-side iterator while the upstream provider keeps\n * streaming and BILLING (LLM_AND_AGENTS_SPEC §3.3, R3-224 / adversarial F2).\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId(),\n signal?: AbortSignal\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n // True once a terminal (`done`/`error`) frame arrived — so the `finally` knows the\n // host already stopped and must NOT send a redundant cancel. Left false when the\n // consumer bails early or the signal aborts (the two cases that DO need a cancel).\n let settled = false;\n // True once the request frame went out. A cancel is only meaningful for a stream\n // the host actually started — an abort BEFORE `send` sends nothing to cancel.\n let started = false;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n // An abort wakes the pull loop out of its idle `await`; the loop then throws.\n const onAbort = () => {\n const w = wake;\n wake = null;\n w?.();\n };\n if (signal) signal.addEventListener('abort', onAbort);\n\n try {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted before start');\n transport.send({ type, method, params, msgId, stream: true });\n started = true;\n while (true) {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted');\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n settled = true;\n return frame.value as R;\n } else {\n settled = true;\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n if (signal) signal.removeEventListener('abort', onAbort);\n // Consumer stopped pulling before a terminal frame (early break/return, or the\n // signal aborted): tell the host to stop the in-flight generation + billing.\n if (started && !settled) transport.cancel?.({ type, msgId, cancel: true });\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers. `cancel`\n// rides the same messageBus as `send` — a `{type, msgId, cancel:true}` frame the\n// host dispatcher routes to the in-flight generator's AbortController.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n *\n * Pass `signal` to abort the stream (and the host's in-flight work) mid-flight.\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[],\n signal?: AbortSignal\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params, undefined, signal);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAUA,0BAAyC;AA8BlC,MAAM,oBAAoB,MAAM;AAAA,EAErC,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAI,gBAAgB;AACpB,MAAM,YAAY,MAAc;AAI9B,mBAAiB,gBAAgB,KAAK,OAAO;AAC7C,SAAO;AACT;AAeA,gBAAuB,cACrB,WACA,MACA,QACA,QACA,QAAgB,UAAU,GAC1B,QAC4B;AAC5B,QAAM,QAAuB,CAAC;AAC9B,MAAI,OAA4B;AAIhC,MAAI,UAAU;AAGd,MAAI,UAAU;AACd,QAAM,OAAO,CAAC,UAAuB;AACnC,UAAM,KAAK,KAAK;AAChB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AAEA,QAAM,cAAc,UAAU,UAAU,MAAM,CAAC,QAAQ;AACrD,QAAI,IAAI,UAAU,SAAS,CAAC,IAAI,OAAQ;AACxC,SAAK,IAAI,MAAM;AAAA,EACjB,CAAC;AAGD,QAAM,UAAU,MAAM;AACpB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AACA,MAAI,OAAQ,QAAO,iBAAiB,SAAS,OAAO;AAEpD,MAAI;AACF,QAAI,QAAQ,QAAS,OAAM,IAAI,YAAY,WAAW,6BAA6B;AACnF,cAAU,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,CAAC;AAC5D,cAAU;AACV,WAAO,MAAM;AACX,UAAI,QAAQ,QAAS,OAAM,IAAI,YAAY,WAAW,gBAAgB;AACtE,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,MAAM,SAAS,SAAS;AAC1B,cAAM,MAAM;AAAA,MACd,WAAW,MAAM,SAAS,QAAQ;AAChC,kBAAU;AACV,eAAO,MAAM;AAAA,MACf,OAAO;AACL,kBAAU;AACV,cAAM,IAAI,YAAY,MAAM,MAAM,MAAM,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,EACF,UAAE;AACA,gBAAY;AACZ,QAAI,OAAQ,QAAO,oBAAoB,SAAS,OAAO;AAGvD,QAAI,WAAW,CAAC,QAAS,WAAU,SAAS,EAAE,MAAM,OAAO,QAAQ,KAAK,CAAC;AAAA,EAC3E;AACF;AAKA,MAAM,mBAAoC;AAAA,EACxC,MAAM,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,gBAChB,iCAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA,EACrF,QAAQ,CAAC,YAAQ,iCAAY,IAAI,MAAM,GAAyC;AAClF;AASO,SAAS,eACd,cACA,QACA,QACA,QAC4B;AAC5B,SAAO,cAAoB,kBAAkB,cAAc,QAAQ,QAAQ,QAAW,MAAM;AAC9F;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\nimport {\n attendanceOf,\n attendanceReason,\n firstFrameTimeoutFor,\n PENDING_NOTICE_MS,\n ProtocolTimeoutError,\n STREAM_IDLE_TIMEOUT_MS,\n type BoundedCallOptions,\n} from './protocolDeadline';\n\n/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\n/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (type: string, handler: (msg: { msgId?: number; stream?: StreamFrame }) => void) => () => void;\n // Tell the host to STOP the stream early — abort the in-flight generation (and,\n // for `llm:chat`, the upstream provider fetch so it stops BILLING). Sent when the\n // consumer bails before a terminal frame: an early `break`/`return` out of the\n // `for await`, or an `AbortSignal` firing. Carries the same `(type, msgId)` the\n // host tagged its frames with, so the host aborts the matching generator\n // (LLM_AND_AGENTS_SPEC §3.3 \"abort the in-flight LLM request\"). Optional — a\n // transport predating the cancel frame simply omits it and the old behavior\n // (host runs to completion) stands.\n cancel?: (msg: { type: string; msgId: number; cancel: true }) => void;\n}\n\n/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n *\n * `signal` wires a caller {@link AbortSignal} to mid-stream cancellation: when it\n * fires (or the consumer `break`s before a terminal frame), the `finally` sends a\n * `cancel` frame back over `transport.cancel` so the HOST stops generating — without\n * it, aborting only stops the app-side iterator while the upstream provider keeps\n * streaming and BILLING (LLM_AND_AGENTS_SPEC §3.3, R3-224 / adversarial F2).\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId(),\n signal?: AbortSignal,\n opts?: BoundedCallOptions & { idleTimeoutMs?: number },\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n // True once a terminal (`done`/`error`) frame arrived — so the `finally` knows the\n // host already stopped and must NOT send a redundant cancel. Left false when the\n // consumer bails early or the signal aborts (the two cases that DO need a cancel).\n let settled = false;\n // True once the request frame went out. A cancel is only meaningful for a stream\n // the host actually started — an abort BEFORE `send` sends nothing to cancel.\n let started = false;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n // An abort wakes the pull loop out of its idle `await`; the loop then throws.\n const onAbort = () => {\n const w = wake;\n wake = null;\n w?.();\n };\n if (signal) signal.addEventListener('abort', onAbort);\n\n // R3-298 — a stream is bounded by SILENCE, not by duration.\n //\n // A total-duration deadline would be wrong here: a long generation that is streaming\n // normally is healthy, and aborting it would be a worse bug than the hang. The hang has a\n // distinct shape — no frames at all — so that is what is bounded: a time-to-FIRST-FRAME\n // deadline, then an idle-gap deadline once frames are flowing. Together they fire exactly\n // on a wedged stream and never on a slow one.\n //\n // The scheme's `type` is `protocol-llm`; the classification table is keyed on the bare\n // scheme, so strip the prefix. The chat stream is classified ATTENDED because its first\n // frame can sit behind the session's first passkey unseal — the dogfood hang.\n const scheme = type.startsWith('protocol-') ? type.slice('protocol-'.length) : type;\n const call = `${scheme}:${method}`;\n const attendance = attendanceOf(scheme, method);\n const firstFrameMs = opts?.timeoutMs ?? firstFrameTimeoutFor(scheme, method);\n const idleMs = opts?.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;\n let sawFrame = false;\n let noticed = false;\n\n /** Wait for the next frame, bounded. Rejects with a typed timeout rather than parking. */\n const awaitFrame = async (): Promise<void> => {\n const budget = sawFrame ? idleMs : firstFrameMs;\n if (!Number.isFinite(budget)) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n return;\n }\n const startedAt = Date.now();\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let notice: ReturnType<typeof setTimeout> | undefined;\n try {\n await new Promise<void>((resolve, reject) => {\n wake = resolve;\n deadline = setTimeout(() => reject(new ProtocolTimeoutError(call, budget, attendance)), budget);\n if (opts?.onPending && !noticed) {\n notice = setTimeout(() => {\n noticed = true;\n try {\n opts.onPending?.({\n call,\n attendance,\n elapsedMs: Date.now() - startedAt,\n ...(attendanceReason(scheme, method) ? { reason: attendanceReason(scheme, method) as string } : {}),\n });\n } catch {\n /* a caller's render callback must never break the stream it describes */\n }\n }, PENDING_NOTICE_MS);\n }\n });\n } finally {\n if (deadline !== undefined) clearTimeout(deadline);\n if (notice !== undefined) clearTimeout(notice);\n wake = null;\n }\n };\n\n try {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted before start');\n transport.send({ type, method, params, msgId, stream: true });\n started = true;\n while (true) {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted');\n if (queue.length === 0) {\n // A timeout throws out of here into the `finally`, which sends the host a real\n // cancel frame — so a wedged stream stops generating and BILLING rather than being\n // merely abandoned by the app. Streams can do this because they own their `msgId`.\n await awaitFrame();\n continue;\n }\n sawFrame = true;\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n settled = true;\n return frame.value as R;\n } else {\n settled = true;\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n if (signal) signal.removeEventListener('abort', onAbort);\n // Consumer stopped pulling before a terminal frame (early break/return, or the\n // signal aborted): tell the host to stop the in-flight generation + billing.\n if (started && !settled) transport.cancel?.({ type, msgId, cancel: true });\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers. `cancel`\n// rides the same messageBus as `send` — a `{type, msgId, cancel:true}` frame the\n// host dispatcher routes to the in-flight generator's AbortController.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) => addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n *\n * Pass `signal` to abort the stream (and the host's in-flight work) mid-flight.\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[],\n signal?: AbortSignal,\n opts?: BoundedCallOptions & { idleTimeoutMs?: number },\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params, undefined, signal, opts);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { BoundedCallOptions } from './protocolDeadline.cjs';
|
|
2
|
+
import './hostAttention.cjs';
|
|
3
|
+
|
|
1
4
|
/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */
|
|
2
5
|
type StreamFrame = {
|
|
3
6
|
kind: 'event';
|
|
@@ -47,7 +50,9 @@ declare class StreamError extends Error {
|
|
|
47
50
|
* it, aborting only stops the app-side iterator while the upstream provider keeps
|
|
48
51
|
* streaming and BILLING (LLM_AND_AGENTS_SPEC §3.3, R3-224 / adversarial F2).
|
|
49
52
|
*/
|
|
50
|
-
declare function consumeStream<T = unknown, R = unknown>(transport: StreamTransport, type: string, method: string, params: unknown[], msgId?: number, signal?: AbortSignal
|
|
53
|
+
declare function consumeStream<T = unknown, R = unknown>(transport: StreamTransport, type: string, method: string, params: unknown[], msgId?: number, signal?: AbortSignal, opts?: BoundedCallOptions & {
|
|
54
|
+
idleTimeoutMs?: number;
|
|
55
|
+
}): AsyncGenerator<T, R, void>;
|
|
51
56
|
/**
|
|
52
57
|
* Consume an elevated streaming protocol method from app code.
|
|
53
58
|
*
|
|
@@ -55,6 +60,8 @@ declare function consumeStream<T = unknown, R = unknown>(transport: StreamTransp
|
|
|
55
60
|
*
|
|
56
61
|
* Pass `signal` to abort the stream (and the host's in-flight work) mid-flight.
|
|
57
62
|
*/
|
|
58
|
-
declare function protocolStream<T = unknown, R = unknown>(protocolName: string, method: string, params: unknown[], signal?: AbortSignal
|
|
63
|
+
declare function protocolStream<T = unknown, R = unknown>(protocolName: string, method: string, params: unknown[], signal?: AbortSignal, opts?: BoundedCallOptions & {
|
|
64
|
+
idleTimeoutMs?: number;
|
|
65
|
+
}): AsyncGenerator<T, R, void>;
|
|
59
66
|
|
|
60
67
|
export { StreamError, type StreamFrame, type StreamTransport, consumeStream, protocolStream };
|
package/dist/protocolStream.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { BoundedCallOptions } from './protocolDeadline.js';
|
|
2
|
+
import './hostAttention.js';
|
|
3
|
+
|
|
1
4
|
/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */
|
|
2
5
|
type StreamFrame = {
|
|
3
6
|
kind: 'event';
|
|
@@ -47,7 +50,9 @@ declare class StreamError extends Error {
|
|
|
47
50
|
* it, aborting only stops the app-side iterator while the upstream provider keeps
|
|
48
51
|
* streaming and BILLING (LLM_AND_AGENTS_SPEC §3.3, R3-224 / adversarial F2).
|
|
49
52
|
*/
|
|
50
|
-
declare function consumeStream<T = unknown, R = unknown>(transport: StreamTransport, type: string, method: string, params: unknown[], msgId?: number, signal?: AbortSignal
|
|
53
|
+
declare function consumeStream<T = unknown, R = unknown>(transport: StreamTransport, type: string, method: string, params: unknown[], msgId?: number, signal?: AbortSignal, opts?: BoundedCallOptions & {
|
|
54
|
+
idleTimeoutMs?: number;
|
|
55
|
+
}): AsyncGenerator<T, R, void>;
|
|
51
56
|
/**
|
|
52
57
|
* Consume an elevated streaming protocol method from app code.
|
|
53
58
|
*
|
|
@@ -55,6 +60,8 @@ declare function consumeStream<T = unknown, R = unknown>(transport: StreamTransp
|
|
|
55
60
|
*
|
|
56
61
|
* Pass `signal` to abort the stream (and the host's in-flight work) mid-flight.
|
|
57
62
|
*/
|
|
58
|
-
declare function protocolStream<T = unknown, R = unknown>(protocolName: string, method: string, params: unknown[], signal?: AbortSignal
|
|
63
|
+
declare function protocolStream<T = unknown, R = unknown>(protocolName: string, method: string, params: unknown[], signal?: AbortSignal, opts?: BoundedCallOptions & {
|
|
64
|
+
idleTimeoutMs?: number;
|
|
65
|
+
}): AsyncGenerator<T, R, void>;
|
|
59
66
|
|
|
60
67
|
export { StreamError, type StreamFrame, type StreamTransport, consumeStream, protocolStream };
|
package/dist/protocolStream.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import "./chunk-VHAA22YE.js";
|
|
2
2
|
import { addListener, sendMessage } from "./sandboxUtils";
|
|
3
|
+
import {
|
|
4
|
+
attendanceOf,
|
|
5
|
+
attendanceReason,
|
|
6
|
+
firstFrameTimeoutFor,
|
|
7
|
+
PENDING_NOTICE_MS,
|
|
8
|
+
ProtocolTimeoutError,
|
|
9
|
+
STREAM_IDLE_TIMEOUT_MS
|
|
10
|
+
} from "./protocolDeadline";
|
|
3
11
|
class StreamError extends Error {
|
|
4
12
|
constructor(code, message) {
|
|
5
13
|
super(message);
|
|
@@ -12,7 +20,7 @@ const nextMsgId = () => {
|
|
|
12
20
|
streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
13
21
|
return streamCounter;
|
|
14
22
|
};
|
|
15
|
-
async function* consumeStream(transport, type, method, params, msgId = nextMsgId(), signal) {
|
|
23
|
+
async function* consumeStream(transport, type, method, params, msgId = nextMsgId(), signal, opts) {
|
|
16
24
|
const queue = [];
|
|
17
25
|
let wake = null;
|
|
18
26
|
let settled = false;
|
|
@@ -33,6 +41,49 @@ async function* consumeStream(transport, type, method, params, msgId = nextMsgId
|
|
|
33
41
|
w?.();
|
|
34
42
|
};
|
|
35
43
|
if (signal) signal.addEventListener("abort", onAbort);
|
|
44
|
+
const scheme = type.startsWith("protocol-") ? type.slice("protocol-".length) : type;
|
|
45
|
+
const call = `${scheme}:${method}`;
|
|
46
|
+
const attendance = attendanceOf(scheme, method);
|
|
47
|
+
const firstFrameMs = opts?.timeoutMs ?? firstFrameTimeoutFor(scheme, method);
|
|
48
|
+
const idleMs = opts?.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;
|
|
49
|
+
let sawFrame = false;
|
|
50
|
+
let noticed = false;
|
|
51
|
+
const awaitFrame = async () => {
|
|
52
|
+
const budget = sawFrame ? idleMs : firstFrameMs;
|
|
53
|
+
if (!Number.isFinite(budget)) {
|
|
54
|
+
await new Promise((resolve) => {
|
|
55
|
+
wake = resolve;
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const startedAt = Date.now();
|
|
60
|
+
let deadline;
|
|
61
|
+
let notice;
|
|
62
|
+
try {
|
|
63
|
+
await new Promise((resolve, reject) => {
|
|
64
|
+
wake = resolve;
|
|
65
|
+
deadline = setTimeout(() => reject(new ProtocolTimeoutError(call, budget, attendance)), budget);
|
|
66
|
+
if (opts?.onPending && !noticed) {
|
|
67
|
+
notice = setTimeout(() => {
|
|
68
|
+
noticed = true;
|
|
69
|
+
try {
|
|
70
|
+
opts.onPending?.({
|
|
71
|
+
call,
|
|
72
|
+
attendance,
|
|
73
|
+
elapsedMs: Date.now() - startedAt,
|
|
74
|
+
...attendanceReason(scheme, method) ? { reason: attendanceReason(scheme, method) } : {}
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
}, PENDING_NOTICE_MS);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
} finally {
|
|
82
|
+
if (deadline !== void 0) clearTimeout(deadline);
|
|
83
|
+
if (notice !== void 0) clearTimeout(notice);
|
|
84
|
+
wake = null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
36
87
|
try {
|
|
37
88
|
if (signal?.aborted) throw new StreamError("aborted", "stream aborted before start");
|
|
38
89
|
transport.send({ type, method, params, msgId, stream: true });
|
|
@@ -40,11 +91,10 @@ async function* consumeStream(transport, type, method, params, msgId = nextMsgId
|
|
|
40
91
|
while (true) {
|
|
41
92
|
if (signal?.aborted) throw new StreamError("aborted", "stream aborted");
|
|
42
93
|
if (queue.length === 0) {
|
|
43
|
-
await
|
|
44
|
-
wake = resolve;
|
|
45
|
-
});
|
|
94
|
+
await awaitFrame();
|
|
46
95
|
continue;
|
|
47
96
|
}
|
|
97
|
+
sawFrame = true;
|
|
48
98
|
const frame = queue.shift();
|
|
49
99
|
if (frame.kind === "event") {
|
|
50
100
|
yield frame.value;
|
|
@@ -67,8 +117,8 @@ const bundlerTransport = {
|
|
|
67
117
|
subscribe: (type, handler) => addListener(type, (msg) => handler(msg)),
|
|
68
118
|
cancel: (msg) => sendMessage(msg.type, msg)
|
|
69
119
|
};
|
|
70
|
-
function protocolStream(protocolName, method, params, signal) {
|
|
71
|
-
return consumeStream(bundlerTransport, protocolName, method, params, void 0, signal);
|
|
120
|
+
function protocolStream(protocolName, method, params, signal, opts) {
|
|
121
|
+
return consumeStream(bundlerTransport, protocolName, method, params, void 0, signal, opts);
|
|
72
122
|
}
|
|
73
123
|
export {
|
|
74
124
|
StreamError,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\n\n/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\n/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (\n type: string,\n handler: (msg: { msgId?: number; stream?: StreamFrame }) => void\n ) => () => void;\n // Tell the host to STOP the stream early — abort the in-flight generation (and,\n // for `llm:chat`, the upstream provider fetch so it stops BILLING). Sent when the\n // consumer bails before a terminal frame: an early `break`/`return` out of the\n // `for await`, or an `AbortSignal` firing. Carries the same `(type, msgId)` the\n // host tagged its frames with, so the host aborts the matching generator\n // (LLM_AND_AGENTS_SPEC §3.3 \"abort the in-flight LLM request\"). Optional — a\n // transport predating the cancel frame simply omits it and the old behavior\n // (host runs to completion) stands.\n cancel?: (msg: { type: string; msgId: number; cancel: true }) => void;\n}\n\n/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n *\n * `signal` wires a caller {@link AbortSignal} to mid-stream cancellation: when it\n * fires (or the consumer `break`s before a terminal frame), the `finally` sends a\n * `cancel` frame back over `transport.cancel` so the HOST stops generating — without\n * it, aborting only stops the app-side iterator while the upstream provider keeps\n * streaming and BILLING (LLM_AND_AGENTS_SPEC §3.3, R3-224 / adversarial F2).\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId(),\n signal?: AbortSignal\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n // True once a terminal (`done`/`error`) frame arrived — so the `finally` knows the\n // host already stopped and must NOT send a redundant cancel. Left false when the\n // consumer bails early or the signal aborts (the two cases that DO need a cancel).\n let settled = false;\n // True once the request frame went out. A cancel is only meaningful for a stream\n // the host actually started — an abort BEFORE `send` sends nothing to cancel.\n let started = false;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n // An abort wakes the pull loop out of its idle `await`; the loop then throws.\n const onAbort = () => {\n const w = wake;\n wake = null;\n w?.();\n };\n if (signal) signal.addEventListener('abort', onAbort);\n\n try {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted before start');\n transport.send({ type, method, params, msgId, stream: true });\n started = true;\n while (true) {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted');\n if (queue.length === 0) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n continue;\n }\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n settled = true;\n return frame.value as R;\n } else {\n settled = true;\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n if (signal) signal.removeEventListener('abort', onAbort);\n // Consumer stopped pulling before a terminal frame (early break/return, or the\n // signal aborted): tell the host to stop the in-flight generation + billing.\n if (started && !settled) transport.cancel?.({ type, msgId, cancel: true });\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers. `cancel`\n// rides the same messageBus as `send` — a `{type, msgId, cancel:true}` frame the\n// host dispatcher routes to the in-flight generator's AbortController.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) =>\n addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n *\n * Pass `signal` to abort the stream (and the host's in-flight work) mid-flight.\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[],\n signal?: AbortSignal\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params, undefined, signal);\n}\n"],"mappings":";AAUA,SAAS,aAAa,mBAAmB;AA8BlC,MAAM,oBAAoB,MAAM;AAAA,EAErC,YAAY,MAAc,SAAiB;AACzC,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AAAA,EACd;AACF;AAEA,IAAI,gBAAgB;AACpB,MAAM,YAAY,MAAc;AAI9B,mBAAiB,gBAAgB,KAAK,OAAO;AAC7C,SAAO;AACT;AAeA,gBAAuB,cACrB,WACA,MACA,QACA,QACA,QAAgB,UAAU,GAC1B,QAC4B;AAC5B,QAAM,QAAuB,CAAC;AAC9B,MAAI,OAA4B;AAIhC,MAAI,UAAU;AAGd,MAAI,UAAU;AACd,QAAM,OAAO,CAAC,UAAuB;AACnC,UAAM,KAAK,KAAK;AAChB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AAEA,QAAM,cAAc,UAAU,UAAU,MAAM,CAAC,QAAQ;AACrD,QAAI,IAAI,UAAU,SAAS,CAAC,IAAI,OAAQ;AACxC,SAAK,IAAI,MAAM;AAAA,EACjB,CAAC;AAGD,QAAM,UAAU,MAAM;AACpB,UAAM,IAAI;AACV,WAAO;AACP,QAAI;AAAA,EACN;AACA,MAAI,OAAQ,QAAO,iBAAiB,SAAS,OAAO;AAEpD,MAAI;AACF,QAAI,QAAQ,QAAS,OAAM,IAAI,YAAY,WAAW,6BAA6B;AACnF,cAAU,KAAK,EAAE,MAAM,QAAQ,QAAQ,OAAO,QAAQ,KAAK,CAAC;AAC5D,cAAU;AACV,WAAO,MAAM;AACX,UAAI,QAAQ,QAAS,OAAM,IAAI,YAAY,WAAW,gBAAgB;AACtE,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,IAAI,QAAc,CAAC,YAAY;AACnC,iBAAO;AAAA,QACT,CAAC;AACD;AAAA,MACF;AACA,YAAM,QAAQ,MAAM,MAAM;AAC1B,UAAI,MAAM,SAAS,SAAS;AAC1B,cAAM,MAAM;AAAA,MACd,WAAW,MAAM,SAAS,QAAQ;AAChC,kBAAU;AACV,eAAO,MAAM;AAAA,MACf,OAAO;AACL,kBAAU;AACV,cAAM,IAAI,YAAY,MAAM,MAAM,MAAM,OAAO;AAAA,MACjD;AAAA,IACF;AAAA,EACF,UAAE;AACA,gBAAY;AACZ,QAAI,OAAQ,QAAO,oBAAoB,SAAS,OAAO;AAGvD,QAAI,WAAW,CAAC,QAAS,WAAU,SAAS,EAAE,MAAM,OAAO,QAAQ,KAAK,CAAC;AAAA,EAC3E;AACF;AAKA,MAAM,mBAAoC;AAAA,EACxC,MAAM,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAAA,EAC9E,WAAW,CAAC,MAAM,YAChB,YAAY,MAAM,CAAC,QAAQ,QAAQ,GAA+C,CAAC;AAAA,EACrF,QAAQ,CAAC,QAAQ,YAAY,IAAI,MAAM,GAAyC;AAClF;AASO,SAAS,eACd,cACA,QACA,QACA,QAC4B;AAC5B,SAAO,cAAoB,kBAAkB,cAAc,QAAQ,QAAQ,QAAW,MAAM;AAC9F;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/protocolStream.ts"],"sourcesContent":["// SDK-side consumer for the host streaming transport (UI_AS_APPS_SPEC §5.1).\n//\n// The host `pumpGenerator` emits, per request msgId, a run of `stream.event`\n// frames terminated by one `stream.done` (with the return value) or `stream.error`\n// frame. This reassembles that run into an AsyncGenerator: each `event` is a\n// `yield`, the `done` value is the generator's `return`, an `error` is a `throw`.\n//\n// `consumeStream` takes an injected `StreamTransport` so it's unit-tested with a\n// fake send/subscribe — no bundler. `protocolStream`/`contribute` below wire it to\n// the real sandbox messageBus via sandboxUtils.\nimport { addListener, sendMessage } from './sandboxUtils';\nimport {\n attendanceOf,\n attendanceReason,\n firstFrameTimeoutFor,\n PENDING_NOTICE_MS,\n ProtocolTimeoutError,\n STREAM_IDLE_TIMEOUT_MS,\n type BoundedCallOptions,\n} from './protocolDeadline';\n\n/** One frame of a host stream: an `event` value, the terminal `done` value, or an `error`. */\nexport type StreamFrame =\n | { kind: 'event'; value: unknown }\n | { kind: 'done'; value: unknown }\n | { kind: 'error'; code: string; message: string };\n\n/** The send/subscribe transport {@link consumeStream} drives (injected so it can be faked in tests). */\nexport interface StreamTransport {\n // Fire the request that starts the stream. The host replies with frames tagged\n // by the same `msgId`.\n send: (msg: { type: string; method: string; params: unknown[]; msgId: number; stream: true }) => void;\n // Subscribe to inbound frames for `type`; returns an unsubscribe.\n subscribe: (type: string, handler: (msg: { msgId?: number; stream?: StreamFrame }) => void) => () => void;\n // Tell the host to STOP the stream early — abort the in-flight generation (and,\n // for `llm:chat`, the upstream provider fetch so it stops BILLING). Sent when the\n // consumer bails before a terminal frame: an early `break`/`return` out of the\n // `for await`, or an `AbortSignal` firing. Carries the same `(type, msgId)` the\n // host tagged its frames with, so the host aborts the matching generator\n // (LLM_AND_AGENTS_SPEC §3.3 \"abort the in-flight LLM request\"). Optional — a\n // transport predating the cancel frame simply omits it and the old behavior\n // (host runs to completion) stands.\n cancel?: (msg: { type: string; msgId: number; cancel: true }) => void;\n}\n\n/** Thrown when a stream ends in an `error` frame; carries the host's `code`. */\nexport class StreamError extends Error {\n code: string;\n constructor(code: string, message: string) {\n super(message);\n this.name = 'StreamError';\n this.code = code;\n }\n}\n\nlet streamCounter = 0;\nconst nextMsgId = (): number => {\n // Distinct from the bundler's own protocolRequest counter space is unnecessary —\n // frames are filtered by (type, msgId, stream) so a collision with a one-shot\n // reply (which has `result`, not `stream`) can't be misread.\n streamCounter = (streamCounter + 1) % Number.MAX_SAFE_INTEGER;\n return streamCounter;\n};\n\n/**\n * Drive one streamed request to completion over an injected transport.\n *\n * Yields each event value; returns the `done` value; throws `StreamError` on an\n * error frame. Always unsubscribes (via the generator's `finally`) so an early\n * `break` in the consumer doesn't leak the listener.\n *\n * `signal` wires a caller {@link AbortSignal} to mid-stream cancellation: when it\n * fires (or the consumer `break`s before a terminal frame), the `finally` sends a\n * `cancel` frame back over `transport.cancel` so the HOST stops generating — without\n * it, aborting only stops the app-side iterator while the upstream provider keeps\n * streaming and BILLING (LLM_AND_AGENTS_SPEC §3.3, R3-224 / adversarial F2).\n */\nexport async function* consumeStream<T = unknown, R = unknown>(\n transport: StreamTransport,\n type: string,\n method: string,\n params: unknown[],\n msgId: number = nextMsgId(),\n signal?: AbortSignal,\n opts?: BoundedCallOptions & { idleTimeoutMs?: number },\n): AsyncGenerator<T, R, void> {\n const queue: StreamFrame[] = [];\n let wake: (() => void) | null = null;\n // True once a terminal (`done`/`error`) frame arrived — so the `finally` knows the\n // host already stopped and must NOT send a redundant cancel. Left false when the\n // consumer bails early or the signal aborts (the two cases that DO need a cancel).\n let settled = false;\n // True once the request frame went out. A cancel is only meaningful for a stream\n // the host actually started — an abort BEFORE `send` sends nothing to cancel.\n let started = false;\n const push = (frame: StreamFrame) => {\n queue.push(frame);\n const w = wake;\n wake = null;\n w?.();\n };\n\n const unsubscribe = transport.subscribe(type, (msg) => {\n if (msg.msgId !== msgId || !msg.stream) return;\n push(msg.stream);\n });\n\n // An abort wakes the pull loop out of its idle `await`; the loop then throws.\n const onAbort = () => {\n const w = wake;\n wake = null;\n w?.();\n };\n if (signal) signal.addEventListener('abort', onAbort);\n\n // R3-298 — a stream is bounded by SILENCE, not by duration.\n //\n // A total-duration deadline would be wrong here: a long generation that is streaming\n // normally is healthy, and aborting it would be a worse bug than the hang. The hang has a\n // distinct shape — no frames at all — so that is what is bounded: a time-to-FIRST-FRAME\n // deadline, then an idle-gap deadline once frames are flowing. Together they fire exactly\n // on a wedged stream and never on a slow one.\n //\n // The scheme's `type` is `protocol-llm`; the classification table is keyed on the bare\n // scheme, so strip the prefix. The chat stream is classified ATTENDED because its first\n // frame can sit behind the session's first passkey unseal — the dogfood hang.\n const scheme = type.startsWith('protocol-') ? type.slice('protocol-'.length) : type;\n const call = `${scheme}:${method}`;\n const attendance = attendanceOf(scheme, method);\n const firstFrameMs = opts?.timeoutMs ?? firstFrameTimeoutFor(scheme, method);\n const idleMs = opts?.idleTimeoutMs ?? STREAM_IDLE_TIMEOUT_MS;\n let sawFrame = false;\n let noticed = false;\n\n /** Wait for the next frame, bounded. Rejects with a typed timeout rather than parking. */\n const awaitFrame = async (): Promise<void> => {\n const budget = sawFrame ? idleMs : firstFrameMs;\n if (!Number.isFinite(budget)) {\n await new Promise<void>((resolve) => {\n wake = resolve;\n });\n return;\n }\n const startedAt = Date.now();\n let deadline: ReturnType<typeof setTimeout> | undefined;\n let notice: ReturnType<typeof setTimeout> | undefined;\n try {\n await new Promise<void>((resolve, reject) => {\n wake = resolve;\n deadline = setTimeout(() => reject(new ProtocolTimeoutError(call, budget, attendance)), budget);\n if (opts?.onPending && !noticed) {\n notice = setTimeout(() => {\n noticed = true;\n try {\n opts.onPending?.({\n call,\n attendance,\n elapsedMs: Date.now() - startedAt,\n ...(attendanceReason(scheme, method) ? { reason: attendanceReason(scheme, method) as string } : {}),\n });\n } catch {\n /* a caller's render callback must never break the stream it describes */\n }\n }, PENDING_NOTICE_MS);\n }\n });\n } finally {\n if (deadline !== undefined) clearTimeout(deadline);\n if (notice !== undefined) clearTimeout(notice);\n wake = null;\n }\n };\n\n try {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted before start');\n transport.send({ type, method, params, msgId, stream: true });\n started = true;\n while (true) {\n if (signal?.aborted) throw new StreamError('aborted', 'stream aborted');\n if (queue.length === 0) {\n // A timeout throws out of here into the `finally`, which sends the host a real\n // cancel frame — so a wedged stream stops generating and BILLING rather than being\n // merely abandoned by the app. Streams can do this because they own their `msgId`.\n await awaitFrame();\n continue;\n }\n sawFrame = true;\n const frame = queue.shift() as StreamFrame;\n if (frame.kind === 'event') {\n yield frame.value as T;\n } else if (frame.kind === 'done') {\n settled = true;\n return frame.value as R;\n } else {\n settled = true;\n throw new StreamError(frame.code, frame.message);\n }\n }\n } finally {\n unsubscribe();\n if (signal) signal.removeEventListener('abort', onAbort);\n // Consumer stopped pulling before a terminal frame (early break/return, or the\n // signal aborted): tell the host to stop the in-flight generation + billing.\n if (started && !settled) transport.cancel?.({ type, msgId, cancel: true });\n }\n}\n\n// The real sandbox transport, built from the bundler messageBus helpers. `cancel`\n// rides the same messageBus as `send` — a `{type, msgId, cancel:true}` frame the\n// host dispatcher routes to the in-flight generator's AbortController.\nconst bundlerTransport: StreamTransport = {\n send: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n subscribe: (type, handler) => addListener(type, (msg) => handler(msg as { msgId?: number; stream?: StreamFrame })),\n cancel: (msg) => sendMessage(msg.type, msg as unknown as Record<string, unknown>),\n};\n\n/**\n * Consume an elevated streaming protocol method from app code.\n *\n * `for await (const ev of protocolStream('protocol-contribute', 'run', [opts])) …`\n *\n * Pass `signal` to abort the stream (and the host's in-flight work) mid-flight.\n */\nexport function protocolStream<T = unknown, R = unknown>(\n protocolName: string,\n method: string,\n params: unknown[],\n signal?: AbortSignal,\n opts?: BoundedCallOptions & { idleTimeoutMs?: number },\n): AsyncGenerator<T, R, void> {\n return consumeStream<T, R>(bundlerTransport, protocolName, method, params, undefined, signal, opts);\n}\n"],"mappings":"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","names":[]}
|
package/dist/pushChannel.cjs
CHANGED
|
@@ -22,21 +22,25 @@ __export(pushChannel_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(pushChannel_exports);
|
|
24
24
|
var import_react = require("react");
|
|
25
|
-
var
|
|
26
|
-
function createPushChannel(opts, transport = { sendMessage:
|
|
25
|
+
var import_hostTransport = require("./hostTransport");
|
|
26
|
+
function createPushChannel(opts, transport = { sendMessage: import_hostTransport.sendMessage, addListener: import_hostTransport.addListener }) {
|
|
27
27
|
let current = opts.initial;
|
|
28
28
|
const listeners = /* @__PURE__ */ new Set();
|
|
29
29
|
let started = false;
|
|
30
30
|
const start = () => {
|
|
31
31
|
if (started) return;
|
|
32
|
+
try {
|
|
33
|
+
transport.addListener(opts.pushType, (msg) => {
|
|
34
|
+
const next = opts.parse(msg);
|
|
35
|
+
if (next !== void 0) {
|
|
36
|
+
current = next;
|
|
37
|
+
listeners.forEach((l) => l(current));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
32
43
|
started = true;
|
|
33
|
-
transport.addListener(opts.pushType, (msg) => {
|
|
34
|
-
const next = opts.parse(msg);
|
|
35
|
-
if (next !== void 0) {
|
|
36
|
-
current = next;
|
|
37
|
-
listeners.forEach((l) => l(current));
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
44
|
if (opts.requestType) {
|
|
41
45
|
try {
|
|
42
46
|
transport.sendMessage(opts.requestType);
|
package/dist/pushChannel.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/pushChannel.ts"],"sourcesContent":["// Generic host→sandbox state channel read over the TRANSPORT\n// (SDK_PACKAGING_SPEC §4, expose-transport). The host pushes a `pushType`\n// message whenever the value changes and answers a `requestType` poll with the\n// current value — gated per-frame by the read ACL (site-main channelBridge.ts /\n// channelRouter). Historically the SDK read these values off injected sandbox\n// service objects (`module.evaluation.module.bundler.auth` etc.); this caches\n// the latest value SDK-side instead, so the SDK is self-contained and the\n// bundler services can eventually be retired.\n//\n// Every state helper (formFactor, auth, editorContext, theme, catalog) is a thin\n// wrapper over one of these — the get / onChange / use trio is identical.\nimport { useEffect, useState } from 'react';\nimport { sendMessage as defaultSend, addListener as defaultAddListener } from './
|
|
1
|
+
{"version":3,"sources":["../src/pushChannel.ts"],"sourcesContent":["// Generic host→sandbox state channel read over the TRANSPORT\n// (SDK_PACKAGING_SPEC §4, expose-transport). The host pushes a `pushType`\n// message whenever the value changes and answers a `requestType` poll with the\n// current value — gated per-frame by the read ACL (site-main channelBridge.ts /\n// channelRouter). Historically the SDK read these values off injected sandbox\n// service objects (`module.evaluation.module.bundler.auth` etc.); this caches\n// the latest value SDK-side instead, so the SDK is self-contained and the\n// bundler services can eventually be retired.\n//\n// Every state helper (formFactor, auth, editorContext, theme, catalog) is a thin\n// wrapper over one of these — the get / onChange / use trio is identical.\nimport { useEffect, useState } from 'react';\nimport { sendMessage as defaultSend, addListener as defaultAddListener } from './hostTransport';\n\nexport interface PushChannel<T> {\n /** Pollable snapshot of the current value. */\n get(): T;\n /** Subscribe; invoked immediately with the current value, then on every change. Returns unsubscribe. */\n onChange(listener: (value: T) => void): () => void;\n /** React hook returning the current value, re-rendering on change. */\n use(): T;\n}\n\n/** Injectable transport — defaults to the real one; overridden in tests. */\nexport interface ChannelTransport {\n sendMessage: (type: string, data?: Record<string, unknown>) => void;\n addListener: (type: string, handler: (msg: Record<string, unknown>) => void) => () => void;\n}\n\nexport function createPushChannel<T>(\n opts: {\n /** Host→sandbox push message type (e.g. `form-factor`). */\n pushType: string;\n /** Poll message type the SDK sends to pull the current value (e.g. `request-form-factor`). */\n requestType?: string;\n /** Value assumed before the host answers — also the value when the app may not read the channel. */\n initial: T;\n /** Extract + validate the value from a push message; return `undefined` to ignore the message. */\n parse: (msg: Record<string, unknown>) => T | undefined;\n },\n transport: ChannelTransport = { sendMessage: defaultSend, addListener: defaultAddListener },\n): PushChannel<T> {\n let current = opts.initial;\n const listeners = new Set<(value: T) => void>();\n let started = false;\n\n // Lazily start on first read: register the push listener + send one poll, so a\n // late-mounting app still gets the current value. A channel the app may not\n // read is simply never answered → `initial` stands.\n const start = () => {\n if (started) return;\n try {\n transport.addListener(opts.pushType, (msg) => {\n const next = opts.parse(msg);\n if (next !== undefined) {\n current = next;\n listeners.forEach((l) => l(current));\n }\n });\n } catch {\n // No host transport in this realm yet (the resolver throws when neither the injected\n // bundler bus nor the §4 discovery global is present). Leave `started` false so the\n // NEXT read retries, and let `initial` stand meanwhile — which is exactly the\n // documented behaviour for a channel the host never answers. Throwing here instead\n // would make a plain `getHostTheme()` fatal outside a sandbox, and since R3-307 the\n // deadline on every host call reads a channel, so one throw would be every call.\n return;\n }\n started = true;\n if (opts.requestType) {\n try {\n transport.sendMessage(opts.requestType);\n } catch {\n /* transport not ready — a proactive push will still arrive */\n }\n }\n };\n\n const get = (): T => {\n start();\n return current;\n };\n\n const onChange = (listener: (value: T) => void): (() => void) => {\n start();\n listeners.add(listener);\n listener(current);\n return () => {\n listeners.delete(listener);\n };\n };\n\n const use = (): T => {\n const [value, setValue] = useState<T>(get);\n useEffect(() => onChange(setValue), []);\n return value;\n };\n\n return { get, onChange, use };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,mBAAoC;AACpC,2BAA8E;AAiBvE,SAAS,kBACd,MAUA,YAA8B,EAAE,aAAa,qBAAAA,aAAa,aAAa,qBAAAC,YAAmB,GAC1E;AAChB,MAAI,UAAU,KAAK;AACnB,QAAM,YAAY,oBAAI,IAAwB;AAC9C,MAAI,UAAU;AAKd,QAAM,QAAQ,MAAM;AAClB,QAAI,QAAS;AACb,QAAI;AACF,gBAAU,YAAY,KAAK,UAAU,CAAC,QAAQ;AAC5C,cAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,YAAI,SAAS,QAAW;AACtB,oBAAU;AACV,oBAAU,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;AAAA,QACrC;AAAA,MACF,CAAC;AAAA,IACH,QAAQ;AAON;AAAA,IACF;AACA,cAAU;AACV,QAAI,KAAK,aAAa;AACpB,UAAI;AACF,kBAAU,YAAY,KAAK,WAAW;AAAA,MACxC,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,MAAM,MAAS;AACnB,UAAM;AACN,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,CAAC,aAA+C;AAC/D,UAAM;AACN,cAAU,IAAI,QAAQ;AACtB,aAAS,OAAO;AAChB,WAAO,MAAM;AACX,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,MAAM,MAAS;AACnB,UAAM,CAAC,OAAO,QAAQ,QAAI,uBAAY,GAAG;AACzC,gCAAU,MAAM,SAAS,QAAQ,GAAG,CAAC,CAAC;AACtC,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,KAAK,UAAU,IAAI;AAC9B;","names":["defaultSend","defaultAddListener"]}
|