Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 1 addition & 16 deletions packages/build/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
"./extensions/typescript": "./src/extensions/typescript.ts",
"./extensions/puppeteer": "./src/extensions/puppeteer.ts",
"./extensions/playwright": "./src/extensions/playwright.ts",
"./extensions/lightpanda": "./src/extensions/lightpanda.ts",
"./extensions/secureExec": "./src/extensions/secureExec.ts"
"./extensions/lightpanda": "./src/extensions/lightpanda.ts"
},
"sourceDialects": [
"@triggerdotdev/source"
Expand Down Expand Up @@ -66,9 +65,6 @@
],
"extensions/lightpanda": [
"dist/commonjs/extensions/lightpanda.d.ts"
],
"extensions/secureExec": [
"dist/commonjs/extensions/secureExec.d.ts"
]
}
},
Expand Down Expand Up @@ -211,17 +207,6 @@
"types": "./dist/commonjs/extensions/lightpanda.d.ts",
"default": "./dist/commonjs/extensions/lightpanda.js"
}
},
"./extensions/secureExec": {
"import": {
"@triggerdotdev/source": "./src/extensions/secureExec.ts",
"types": "./dist/esm/extensions/secureExec.d.ts",
"default": "./dist/esm/extensions/secureExec.js"
},
"require": {
"types": "./dist/commonjs/extensions/secureExec.d.ts",
"default": "./dist/commonjs/extensions/secureExec.js"
}
}
},
"main": "./dist/commonjs/index.js",
Expand Down
172 changes: 0 additions & 172 deletions packages/build/src/extensions/secureExec.ts

This file was deleted.

10 changes: 7 additions & 3 deletions packages/core/src/v3/apiClient/runStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,10 @@ export class SSEStreamSubscription implements StreamSubscription {
// reset the timer naturally.
stallTimeoutMs?: number;
// HTTP statuses that should NOT be retried — fail the stream
// permanently. `404` (stream gone) and `410` (session closed)
// are sensible defaults; tune per-caller for other 4xx.
// permanently. Defaults cover the permanent client-error set:
// `400` (bad request), `404` (stream gone), `409` (conflict),
// `410` (session closed), `422` (unprocessable). Tune per-caller
// for other 4xx.
nonRetryableStatuses?: readonly number[];
// Optional fetch override. Used by transports that need to route
// the SSE connect through a custom path (proxy, custom headers,
Expand All @@ -249,7 +251,9 @@ export class SSEStreamSubscription implements StreamSubscription {
this.retryJitter = options.retryJitter ?? 0.5;
this.fetchTimeoutMs = options.fetchTimeoutMs ?? 30_000;
this.stallTimeoutMs = options.stallTimeoutMs ?? 0;
this.nonRetryableStatuses = new Set(options.nonRetryableStatuses ?? [404, 410]);
this.nonRetryableStatuses = new Set(
options.nonRetryableStatuses ?? [400, 404, 409, 410, 422]
);
}

/**
Expand Down
3 changes: 1 addition & 2 deletions packages/trigger-sdk/src/v3/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ type PublicTokenPermissionProperties = {
*
* `read:sessions:{id}` lets the bearer read both the `.out` and `.in`
* channels and list runs on the session. `write:sessions:{id}` lets the
* bearer append to the session's channels. `trigger:sessions:{id}` permits
* triggering new runs on the session.
* bearer append to the session's channels and create new runs against it.
*/
sessions?: string | string[];
};
Expand Down
30 changes: 22 additions & 8 deletions packages/trigger-sdk/src/v3/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,17 +671,23 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
}

// Hydrate session state from response headers so subsequent turns
// skip the endpoint and write directly to session.in.
// skip the endpoint and write directly to session.in. Failing fast
// when the header is missing avoids a quiet degraded state where
// every later turn re-runs the handover route instead of taking
// the slim-wire path.
const accessToken = response.headers.get("X-Trigger-Chat-Access-Token");
const chatId = args.chatId;
if (accessToken) {
const state: ChatSessionState = {
publicAccessToken: accessToken,
isStreaming: true,
};
this.sessions.set(chatId, state);
this.notifySessionChange(chatId, state);
if (!accessToken) {
throw new Error(
"chat.handover response is missing the X-Trigger-Chat-Access-Token header. chat.agent's handover endpoint must echo the session PAT so the transport can hydrate."
);
}
const state: ChatSessionState = {
publicAccessToken: accessToken,
isStreaming: true,
};
this.sessions.set(chatId, state);
this.notifySessionChange(chatId, state);

// Filter the parsed UIMessage stream:
// - Drop control chunks (`trigger:turn-complete`,
Expand Down Expand Up @@ -953,6 +959,14 @@ export class TriggerChatTransport implements ChatTransport<UIMessage> {
this.coordinator?.removeMessagesListener(fn);
}
dispose(): void {
// Tear down any open session.out subscriptions before the coordinator
// goes away. Otherwise controllers in `activeStreams` keep reading
// until they time out, leaking network and memory on every
// unmount/navigation.
for (const controller of this.activeStreams.values()) {
controller.abort();
}
this.activeStreams.clear();
this.coordinator?.dispose();
this.coordinator = null;
}
Expand Down
30 changes: 27 additions & 3 deletions packages/trigger-sdk/src/v3/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,30 @@ export class SessionOutputChannel {
const readableStreamSource = ensureReadableStream(value);

const abortController = new AbortController();
const combinedSignal = options?.signal
? AbortSignal.any?.([options.signal, abortController.signal]) ?? abortController.signal
: abortController.signal;
// `AbortSignal.any` lands in Node 20.3; the SDK still supports Node
// 18.20+. On older runtimes fall back to wiring `options.signal` into
// `abortController` manually so caller-driven cancellation propagates.
let combinedSignal: AbortSignal = abortController.signal;
// Set in the Node 18 fallback path so the caller's `signal.addEventListener`
// registration can be cleared once the stream finishes. Without this, a
// long-lived caller signal (e.g. one reused across many `writer()` calls)
// accumulates listeners on every completed turn.
let removeCallerAbortListener: (() => void) | undefined;
if (options?.signal) {
if (typeof AbortSignal.any === "function") {
combinedSignal = AbortSignal.any([options.signal, abortController.signal]);
} else {
const callerSignal = options.signal;
if (callerSignal.aborted) {
abortController.abort(callerSignal.reason);
} else {
const onCallerAbort = () => abortController.abort(callerSignal.reason);
callerSignal.addEventListener("abort", onCallerAbort, { once: true });
removeCallerAbortListener = () =>
callerSignal.removeEventListener("abort", onCallerAbort);
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

// Resolve the init promise eagerly so we can capture which one this
// writer uses for reactive invalidation below.
Expand Down Expand Up @@ -499,9 +520,11 @@ export class SessionOutputChannel {
// from surfacing as unhandled.
instance.wait().then(
() => {
removeCallerAbortListener?.();
span.end();
},
() => {
removeCallerAbortListener?.();
if (this.#initPromise === writerInitPromise) {
this.#initPromise = undefined;
}
Expand All @@ -516,6 +539,7 @@ export class SessionOutputChannel {
},
};
} catch (error) {
removeCallerAbortListener?.();
if (error instanceof Error && error.name === "AbortError") {
span.end();
throw error;
Expand Down
3 changes: 2 additions & 1 deletion packages/trigger-sdk/src/v3/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2585,7 +2585,8 @@ async function triggerAndSubscribe_internal<TIdentifier extends string, TPayload
debounce: options?.debounce,
},
},
{}
{},
requestOptions
);

// Set attributes after trigger so the dashboard can link to the child run
Expand Down
Loading