feat(cli): support extension installation for teleportation

This commit is contained in:
Sehoon Shon
2026-03-19 00:30:10 -04:00
parent 4f8534b223
commit 6d3e4764cc
3 changed files with 68 additions and 0 deletions
@@ -51,6 +51,7 @@ import {
type HookDefinition, type HookDefinition,
type HookEventName, type HookEventName,
type ResolvedExtensionSetting, type ResolvedExtensionSetting,
type TrajectoryProvider,
coreEvents, coreEvents,
applyAdminAllowlist, applyAdminAllowlist,
getAdminBlockedMcpServersMessage, 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 { return {
name: config.name, name: config.name,
version: config.version, version: config.version,
@@ -980,6 +998,7 @@ Would you like to attempt to install via "git clone" instead?`,
rules, rules,
checkers, checkers,
plan: config.plan, plan: config.plan,
trajectoryProviderModule,
}; };
} catch (e) { } catch (e) {
const extName = path.basename(extensionDir); const extName = path.basename(extensionDir);
+5
View File
@@ -46,6 +46,11 @@ export interface ExtensionConfig {
* Used to migrate an extension to a new repository source. * Used to migrate an extension to a new repository source.
*/ */
migratedTo?: string; 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 { export interface ExtensionUpdateInfo {
@@ -242,6 +242,50 @@ export abstract class ExtensionLoader {
await this.stopExtension(extension); await this.stopExtension(extension);
await this.startExtension(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 { export interface ExtensionEvents {