feat(coding-agent): add extension project trust decisions
This commit is contained in:
@@ -98,6 +98,10 @@ export type {
|
||||
MessageUpdateEvent,
|
||||
ModelSelectEvent,
|
||||
ModelSelectSource,
|
||||
ProjectTrustContext,
|
||||
ProjectTrustEvent,
|
||||
ProjectTrustEventResult,
|
||||
ProjectTrustHandler,
|
||||
// Provider Registration
|
||||
ProviderConfig,
|
||||
ProviderModelConfig,
|
||||
|
||||
@@ -410,15 +410,20 @@ export async function loadExtensionFromFactory(
|
||||
/**
|
||||
* Load extensions from paths.
|
||||
*/
|
||||
export async function loadExtensions(paths: string[], cwd: string, eventBus?: EventBus): Promise<LoadExtensionsResult> {
|
||||
export async function loadExtensions(
|
||||
paths: string[],
|
||||
cwd: string,
|
||||
eventBus?: EventBus,
|
||||
runtime?: ExtensionRuntime,
|
||||
): Promise<LoadExtensionsResult> {
|
||||
const extensions: Extension[] = [];
|
||||
const errors: Array<{ path: string; error: string }> = [];
|
||||
const resolvedCwd = resolvePath(cwd);
|
||||
const resolvedEventBus = eventBus ?? createEventBus();
|
||||
const runtime = createExtensionRuntime();
|
||||
const resolvedRuntime = runtime ?? createExtensionRuntime();
|
||||
|
||||
for (const extPath of paths) {
|
||||
const { extension, error } = await loadExtension(extPath, resolvedCwd, resolvedEventBus, runtime);
|
||||
const { extension, error } = await loadExtension(extPath, resolvedCwd, resolvedEventBus, resolvedRuntime);
|
||||
|
||||
if (error) {
|
||||
errors.push({ path: extPath, error });
|
||||
@@ -433,7 +438,7 @@ export async function loadExtensions(paths: string[], cwd: string, eventBus?: Ev
|
||||
return {
|
||||
extensions,
|
||||
errors,
|
||||
runtime,
|
||||
runtime: resolvedRuntime,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,13 @@ import type {
|
||||
InputEvent,
|
||||
InputEventResult,
|
||||
InputSource,
|
||||
LoadExtensionsResult,
|
||||
MessageEndEvent,
|
||||
MessageEndEventResult,
|
||||
MessageRenderer,
|
||||
ProjectTrustContext,
|
||||
ProjectTrustEvent,
|
||||
ProjectTrustEventResult,
|
||||
ProviderConfig,
|
||||
RegisteredCommand,
|
||||
RegisteredTool,
|
||||
@@ -116,6 +120,7 @@ interface BeforeAgentStartCombinedResult {
|
||||
type RunnerEmitEvent = Exclude<
|
||||
ExtensionEvent,
|
||||
| ToolCallEvent
|
||||
| ProjectTrustEvent
|
||||
| ToolResultEvent
|
||||
| UserBashEvent
|
||||
| ContextEvent
|
||||
@@ -189,6 +194,35 @@ export async function emitSessionShutdownEvent(
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function emitProjectTrustEvent(
|
||||
extensionsResult: LoadExtensionsResult,
|
||||
event: ProjectTrustEvent,
|
||||
ctx: ProjectTrustContext,
|
||||
): Promise<{ result?: ProjectTrustEventResult; errors: ExtensionError[] }> {
|
||||
const errors: ExtensionError[] = [];
|
||||
for (const ext of extensionsResult.extensions) {
|
||||
// A single extension may register multiple handlers for the same event.
|
||||
// The first project_trust handler that returns a decision wins.
|
||||
const handlers = ext.handlers.get("project_trust");
|
||||
if (!handlers || handlers.length === 0) continue;
|
||||
|
||||
for (const handler of handlers) {
|
||||
try {
|
||||
const handlerResult = (await handler(event, ctx)) as ProjectTrustEventResult;
|
||||
return { result: handlerResult, errors };
|
||||
} catch (error) {
|
||||
errors.push({
|
||||
extensionPath: ext.path,
|
||||
event: event.type,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return { errors };
|
||||
}
|
||||
|
||||
const noOpUIContext: ExtensionUIContext = {
|
||||
select: async () => undefined,
|
||||
confirm: async () => false,
|
||||
|
||||
@@ -495,9 +495,31 @@ export function defineTool<TParams extends TSchema, TDetails = unknown, TState =
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Resource Events
|
||||
// Startup/Resource Events
|
||||
// ============================================================================
|
||||
|
||||
export interface ProjectTrustEvent {
|
||||
type: "project_trust";
|
||||
cwd: string;
|
||||
}
|
||||
|
||||
export interface ProjectTrustEventResult {
|
||||
trusted: boolean;
|
||||
remember?: boolean;
|
||||
}
|
||||
|
||||
export interface ProjectTrustContext {
|
||||
cwd: string;
|
||||
mode: ExtensionMode;
|
||||
hasUI: boolean;
|
||||
ui: Pick<ExtensionUIContext, "select" | "confirm" | "input" | "notify">;
|
||||
}
|
||||
|
||||
export type ProjectTrustHandler = (
|
||||
event: ProjectTrustEvent,
|
||||
ctx: ProjectTrustContext,
|
||||
) => Promise<ProjectTrustEventResult> | ProjectTrustEventResult;
|
||||
|
||||
/** Fired after session_start to allow extensions to provide additional resource paths. */
|
||||
export interface ResourcesDiscoverEvent {
|
||||
type: "resources_discover";
|
||||
@@ -957,6 +979,7 @@ export function isToolCallEventType(toolName: string, event: ToolCallEvent): boo
|
||||
|
||||
/** Union of all event types */
|
||||
export type ExtensionEvent =
|
||||
| ProjectTrustEvent
|
||||
| ResourcesDiscoverEvent
|
||||
| SessionEvent
|
||||
| ContextEvent
|
||||
@@ -1095,6 +1118,7 @@ export interface ExtensionAPI {
|
||||
// Event Subscription
|
||||
// =========================================================================
|
||||
|
||||
on(event: "project_trust", handler: ProjectTrustHandler): void;
|
||||
on(event: "resources_discover", handler: ExtensionHandler<ResourcesDiscoverEvent, ResourcesDiscoverResult>): void;
|
||||
on(event: "session_start", handler: ExtensionHandler<SessionStartEvent>): void;
|
||||
on(
|
||||
|
||||
Reference in New Issue
Block a user