From 6d3e4764ccc5c3444b55f0ab9e1a723892a40547 Mon Sep 17 00:00:00 2001 From: Sehoon Shon Date: Thu, 19 Mar 2026 00:30:10 -0400 Subject: [PATCH] feat(cli): support extension installation for teleportation --- packages/cli/src/config/extension-manager.ts | 19 +++++++++ packages/cli/src/config/extension.ts | 5 +++ packages/core/src/utils/extensionLoader.ts | 44 ++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index 2c46a845e6..9001ed8cb5 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -51,6 +51,7 @@ import { type HookDefinition, type HookEventName, type ResolvedExtensionSetting, + type TrajectoryProvider, coreEvents, applyAdminAllowlist, getAdminBlockedMcpServersMessage, @@ -957,6 +958,23 @@ Would you like to attempt to install via "git clone" instead?`, ); } + let trajectoryProviderModule: TrajectoryProvider | undefined; + if (config.trajectoryProvider) { + try { + const expectedPath = path.resolve( + effectiveExtensionPath, + config.trajectoryProvider, + ); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + trajectoryProviderModule = (await import(expectedPath)) + .default as TrajectoryProvider; + } catch (e) { + debugLogger.warn( + `Failed to import trajectoryProvider at ${config.trajectoryProvider} for extension ${config.name}: ${getErrorMessage(e)}`, + ); + } + } + return { name: config.name, version: config.version, @@ -980,6 +998,7 @@ Would you like to attempt to install via "git clone" instead?`, rules, checkers, plan: config.plan, + trajectoryProviderModule, }; } catch (e) { const extName = path.basename(extensionDir); diff --git a/packages/cli/src/config/extension.ts b/packages/cli/src/config/extension.ts index 564c4fbb6f..2c8763c0bf 100644 --- a/packages/cli/src/config/extension.ts +++ b/packages/cli/src/config/extension.ts @@ -46,6 +46,11 @@ export interface ExtensionConfig { * Used to migrate an extension to a new repository source. */ migratedTo?: string; + /** + * Path to a module that implements the TrajectoryProvider interface. + * Used for importing binary chat histories like Jetski Teleportation. + */ + trajectoryProvider?: string; } export interface ExtensionUpdateInfo { diff --git a/packages/core/src/utils/extensionLoader.ts b/packages/core/src/utils/extensionLoader.ts index 053d4c2b13..05fad2f476 100644 --- a/packages/core/src/utils/extensionLoader.ts +++ b/packages/core/src/utils/extensionLoader.ts @@ -242,6 +242,50 @@ export abstract class ExtensionLoader { await this.stopExtension(extension); await this.startExtension(extension); } + + /** + * Returns the most recent session from all extensions if it's within the threshold. + */ + async getRecentExternalSession( + workspaceUri?: string, + thresholdMs: number = 10 * 60 * 1000, + ): Promise<{ prefix: string; id: string; displayName?: string } | null> { + const activeExtensions = this.getExtensions().filter((e) => e.isActive); + let mostRecent: { + prefix: string; + id: string; + displayName?: string; + mtime: number; + } | null = null; + + for (const extension of activeExtensions) { + if (extension.trajectoryProviderModule) { + try { + const sessions = + await extension.trajectoryProviderModule.listSessions(workspaceUri); + for (const s of sessions) { + const mtime = new Date(s.mtime).getTime(); + if (!mostRecent || mtime > mostRecent.mtime) { + mostRecent = { + prefix: extension.trajectoryProviderModule.prefix || '', + id: s.id, + displayName: s.displayName, + mtime, + }; + } + } + } catch (_e) { + // Ignore extension errors + } + } + } + + if (mostRecent && Date.now() - mostRecent.mtime < thresholdMs) { + return mostRecent; + } + + return null; + } } export interface ExtensionEvents {