fix(coding-agent): unify source provenance, closes #1734
This commit is contained in:
@@ -75,7 +75,8 @@ import type { ResourceExtensionPaths, ResourceLoader } from "./resource-loader.j
|
||||
import type { BranchSummaryEntry, CompactionEntry, SessionManager } from "./session-manager.js";
|
||||
import { CURRENT_SESSION_VERSION, getLatestCompactionEntry, type SessionHeader } from "./session-manager.js";
|
||||
import type { SettingsManager } from "./settings-manager.js";
|
||||
import type { SlashCommandInfo, SlashCommandLocation } from "./slash-commands.js";
|
||||
import type { SlashCommandInfo } from "./slash-commands.js";
|
||||
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
||||
import { buildSystemPrompt } from "./system-prompt.js";
|
||||
import type { BashOperations } from "./tools/bash.js";
|
||||
import { createAllToolDefinitions } from "./tools/index.js";
|
||||
@@ -201,6 +202,11 @@ export interface SessionStats {
|
||||
cost: number;
|
||||
}
|
||||
|
||||
interface ToolDefinitionEntry {
|
||||
definition: ToolDefinition;
|
||||
sourceInfo: SourceInfo;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
@@ -274,7 +280,7 @@ export class AgentSession {
|
||||
|
||||
// Tool registry for extension getTools/setTools
|
||||
private _toolRegistry: Map<string, AgentTool> = new Map();
|
||||
private _toolDefinitions: Map<string, ToolDefinition> = new Map();
|
||||
private _toolDefinitions: Map<string, ToolDefinitionEntry> = new Map();
|
||||
private _toolPromptSnippets: Map<string, string> = new Map();
|
||||
private _toolPromptGuidelines: Map<string, string[]> = new Map();
|
||||
|
||||
@@ -710,18 +716,19 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all configured tools with name, description, and parameter schema.
|
||||
* Get all configured tools with name, description, parameter schema, and source metadata.
|
||||
*/
|
||||
getAllTools(): ToolInfo[] {
|
||||
return Array.from(this._toolDefinitions.values()).map((t) => ({
|
||||
name: t.name,
|
||||
description: t.description,
|
||||
parameters: t.parameters,
|
||||
return Array.from(this._toolDefinitions.values()).map(({ definition, sourceInfo }) => ({
|
||||
name: definition.name,
|
||||
description: definition.description,
|
||||
parameters: definition.parameters,
|
||||
sourceInfo,
|
||||
}));
|
||||
}
|
||||
|
||||
getToolDefinition(name: string): ToolDefinition | undefined {
|
||||
return this._toolDefinitions.get(name);
|
||||
return this._toolDefinitions.get(name)?.definition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2084,20 +2091,12 @@ export class AgentSession {
|
||||
}
|
||||
|
||||
private _bindExtensionCore(runner: ExtensionRunner): void {
|
||||
const normalizeLocation = (source: string): SlashCommandLocation | undefined => {
|
||||
if (source === "user" || source === "project" || source === "path") {
|
||||
return source;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getCommands = (): SlashCommandInfo[] => {
|
||||
const extensionCommands: SlashCommandInfo[] = runner.getRegisteredCommands().map((command) => ({
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
source: "extension",
|
||||
sourceInfo: command.sourceInfo,
|
||||
path: command.extensionPath,
|
||||
}));
|
||||
|
||||
const templates: SlashCommandInfo[] = this.promptTemplates.map((template) => ({
|
||||
@@ -2105,8 +2104,6 @@ export class AgentSession {
|
||||
description: template.description,
|
||||
source: "prompt",
|
||||
sourceInfo: template.sourceInfo,
|
||||
location: normalizeLocation(template.source),
|
||||
path: template.filePath,
|
||||
}));
|
||||
|
||||
const skills: SlashCommandInfo[] = this._resourceLoader.getSkills().skills.map((skill) => ({
|
||||
@@ -2114,8 +2111,6 @@ export class AgentSession {
|
||||
description: skill.description,
|
||||
source: "skill",
|
||||
sourceInfo: skill.sourceInfo,
|
||||
location: normalizeLocation(skill.source),
|
||||
path: skill.filePath,
|
||||
}));
|
||||
|
||||
return [...extensionCommands, ...templates, ...skills];
|
||||
@@ -2209,16 +2204,30 @@ export class AgentSession {
|
||||
const registeredTools = this._extensionRunner?.getAllRegisteredTools() ?? [];
|
||||
const allCustomTools = [
|
||||
...registeredTools,
|
||||
...this._customTools.map((def) => ({ definition: def, extensionPath: "<sdk>" })),
|
||||
...this._customTools.map((definition) => ({
|
||||
definition,
|
||||
sourceInfo: createSyntheticSourceInfo(`<sdk:${definition.name}>`, { source: "sdk" }),
|
||||
})),
|
||||
];
|
||||
const definitionRegistry = new Map(this._baseToolDefinitions);
|
||||
for (const { definition } of allCustomTools) {
|
||||
definitionRegistry.set(definition.name, definition);
|
||||
const definitionRegistry = new Map<string, ToolDefinitionEntry>(
|
||||
Array.from(this._baseToolDefinitions.entries()).map(([name, definition]) => [
|
||||
name,
|
||||
{
|
||||
definition,
|
||||
sourceInfo: createSyntheticSourceInfo(`<builtin:${name}>`, { source: "builtin" }),
|
||||
},
|
||||
]),
|
||||
);
|
||||
for (const tool of allCustomTools) {
|
||||
definitionRegistry.set(tool.definition.name, {
|
||||
definition: tool.definition,
|
||||
sourceInfo: tool.sourceInfo,
|
||||
});
|
||||
}
|
||||
this._toolDefinitions = definitionRegistry;
|
||||
this._toolPromptSnippets = new Map(
|
||||
Array.from(definitionRegistry.values())
|
||||
.map((definition) => {
|
||||
.map(({ definition }) => {
|
||||
const snippet = this._normalizePromptSnippet(definition.promptSnippet);
|
||||
return snippet ? ([definition.name, snippet] as const) : undefined;
|
||||
})
|
||||
@@ -2226,7 +2235,7 @@ export class AgentSession {
|
||||
);
|
||||
this._toolPromptGuidelines = new Map(
|
||||
Array.from(definitionRegistry.values())
|
||||
.map((definition) => {
|
||||
.map(({ definition }) => {
|
||||
const guidelines = this._normalizePromptGuidelines(definition.promptGuidelines);
|
||||
return guidelines.length > 0 ? ([definition.name, guidelines] as const) : undefined;
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import { basename, join } from "path";
|
||||
import { APP_NAME, getExportTemplateDir } from "../../config.js";
|
||||
import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme.js";
|
||||
import type { ToolInfo } from "../extensions/types.js";
|
||||
import type { ToolDefinition } from "../extensions/types.js";
|
||||
import type { SessionEntry } from "../session-manager.js";
|
||||
import { SessionManager } from "../session-manager.js";
|
||||
|
||||
@@ -131,7 +131,7 @@ interface SessionData {
|
||||
entries: ReturnType<SessionManager["getEntries"]>;
|
||||
leafId: string | null;
|
||||
systemPrompt?: string;
|
||||
tools?: ToolInfo[];
|
||||
tools?: Array<Pick<ToolDefinition, "name" | "description" | "parameters">>;
|
||||
/** Pre-rendered HTML for custom tool calls/results, keyed by tool call ID */
|
||||
renderedTools?: Record<string, RenderedToolHtml>;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Extension system for lifecycle events and custom tools.
|
||||
*/
|
||||
|
||||
export type { SlashCommandInfo, SlashCommandLocation, SlashCommandSource } from "../slash-commands.js";
|
||||
export type { SlashCommandInfo, SlashCommandSource } from "../slash-commands.js";
|
||||
export type { SourceInfo } from "../source-info.js";
|
||||
export {
|
||||
createExtensionRuntime,
|
||||
|
||||
@@ -26,6 +26,7 @@ import * as _bundledPiCodingAgent from "../../index.js";
|
||||
import { createEventBus, type EventBus } from "../event-bus.js";
|
||||
import type { ExecOptions } from "../exec.js";
|
||||
import { execCommand } from "../exec.js";
|
||||
import { createSyntheticSourceInfo } from "../source-info.js";
|
||||
import type {
|
||||
Extension,
|
||||
ExtensionAPI,
|
||||
@@ -174,15 +175,14 @@ function createExtensionAPI(
|
||||
registerTool(tool: ToolDefinition): void {
|
||||
extension.tools.set(tool.name, {
|
||||
definition: tool,
|
||||
extensionPath: extension.path,
|
||||
sourceInfo: extension.sourceInfo,
|
||||
});
|
||||
runtime.refreshTools();
|
||||
},
|
||||
|
||||
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "extensionPath">): void {
|
||||
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "sourceInfo">): void {
|
||||
extension.commands.set(name, {
|
||||
name,
|
||||
extensionPath: extension.path,
|
||||
sourceInfo: extension.sourceInfo,
|
||||
...options,
|
||||
});
|
||||
@@ -307,9 +307,16 @@ async function loadExtensionModule(extensionPath: string) {
|
||||
* Create an Extension object with empty collections.
|
||||
*/
|
||||
function createExtension(extensionPath: string, resolvedPath: string): Extension {
|
||||
const source =
|
||||
extensionPath.startsWith("<") && extensionPath.endsWith(">")
|
||||
? extensionPath.slice(1, -1).split(":")[0] || "temporary"
|
||||
: "local";
|
||||
const baseDir = extensionPath.startsWith("<") ? undefined : path.dirname(resolvedPath);
|
||||
|
||||
return {
|
||||
path: extensionPath,
|
||||
resolvedPath,
|
||||
sourceInfo: createSyntheticSourceInfo(extensionPath, { source, baseDir }),
|
||||
handlers: new Map(),
|
||||
tools: new Map(),
|
||||
messageRenderers: new Map(),
|
||||
|
||||
@@ -961,8 +961,7 @@ export type MessageRenderer<T = unknown> = (
|
||||
|
||||
export interface RegisteredCommand {
|
||||
name: string;
|
||||
extensionPath: string;
|
||||
sourceInfo?: SourceInfo;
|
||||
sourceInfo: SourceInfo;
|
||||
description?: string;
|
||||
getArgumentCompletions?: (argumentPrefix: string) => AutocompleteItem[] | null;
|
||||
handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;
|
||||
@@ -1038,7 +1037,7 @@ export interface ExtensionAPI {
|
||||
// =========================================================================
|
||||
|
||||
/** Register a custom command. */
|
||||
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "extensionPath">): void;
|
||||
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "sourceInfo">): void;
|
||||
|
||||
/** Register a keyboard shortcut. */
|
||||
registerShortcut(
|
||||
@@ -1110,7 +1109,7 @@ export interface ExtensionAPI {
|
||||
/** Get the list of currently active tool names. */
|
||||
getActiveTools(): string[];
|
||||
|
||||
/** Get all configured tools with name and description. */
|
||||
/** Get all configured tools with parameter schema and source metadata. */
|
||||
getAllTools(): ToolInfo[];
|
||||
|
||||
/** Set the active tools by name. */
|
||||
@@ -1277,7 +1276,7 @@ export type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
|
||||
|
||||
export interface RegisteredTool {
|
||||
definition: ToolDefinition;
|
||||
extensionPath: string;
|
||||
sourceInfo: SourceInfo;
|
||||
}
|
||||
|
||||
export interface ExtensionFlag {
|
||||
@@ -1315,8 +1314,10 @@ export type GetSessionNameHandler = () => string | undefined;
|
||||
|
||||
export type GetActiveToolsHandler = () => string[];
|
||||
|
||||
/** Tool info with name, description, and parameter schema */
|
||||
export type ToolInfo = Pick<ToolDefinition, "name" | "description" | "parameters">;
|
||||
/** Tool info with name, description, parameter schema, and source metadata */
|
||||
export type ToolInfo = Pick<ToolDefinition, "name" | "description" | "parameters"> & {
|
||||
sourceInfo: SourceInfo;
|
||||
};
|
||||
|
||||
export type GetAllToolsHandler = () => ToolInfo[];
|
||||
|
||||
@@ -1417,7 +1418,7 @@ export interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionAction
|
||||
export interface Extension {
|
||||
path: string;
|
||||
resolvedPath: string;
|
||||
sourceInfo?: SourceInfo;
|
||||
sourceInfo: SourceInfo;
|
||||
handlers: Map<string, HandlerFn[]>;
|
||||
tools: Map<string, RegisteredTool>;
|
||||
messageRenderers: Map<string, MessageRenderer>;
|
||||
|
||||
@@ -14,7 +14,6 @@ export {
|
||||
export { type BashExecutorOptions, type BashResult, executeBash, executeBashWithOperations } from "./bash-executor.js";
|
||||
export type { CompactionResult } from "./compaction/index.js";
|
||||
export { createEventBus, type EventBus, type EventBusController } from "./event-bus.js";
|
||||
|
||||
// Extensions system
|
||||
export {
|
||||
type AgentEndEvent,
|
||||
@@ -59,3 +58,4 @@ export {
|
||||
type TurnEndEvent,
|
||||
type TurnStartEvent,
|
||||
} from "./extensions/index.js";
|
||||
export { createSyntheticSourceInfo } from "./source-info.js";
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
||||
import { homedir } from "os";
|
||||
import { basename, isAbsolute, join, resolve, sep } from "path";
|
||||
import { basename, dirname, isAbsolute, join, resolve, sep } from "path";
|
||||
import { CONFIG_DIR_NAME, getPromptsDir } from "../config.js";
|
||||
import { parseFrontmatter } from "../utils/frontmatter.js";
|
||||
import type { SourceInfo } from "./source-info.js";
|
||||
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
||||
|
||||
/**
|
||||
* Represents a prompt template loaded from a markdown file
|
||||
@@ -12,8 +12,7 @@ export interface PromptTemplate {
|
||||
name: string;
|
||||
description: string;
|
||||
content: string;
|
||||
source: string; // "user", "project", or "path"
|
||||
sourceInfo?: SourceInfo;
|
||||
sourceInfo: SourceInfo;
|
||||
filePath: string; // Absolute path to the template file
|
||||
}
|
||||
|
||||
@@ -101,7 +100,7 @@ export function substituteArgs(content: string, args: string[]): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
function loadTemplateFromFile(filePath: string, source: string): PromptTemplate | null {
|
||||
function loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {
|
||||
try {
|
||||
const rawContent = readFileSync(filePath, "utf-8");
|
||||
const { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);
|
||||
@@ -123,7 +122,7 @@ function loadTemplateFromFile(filePath: string, source: string): PromptTemplate
|
||||
name,
|
||||
description,
|
||||
content: body,
|
||||
source,
|
||||
sourceInfo,
|
||||
filePath,
|
||||
};
|
||||
} catch {
|
||||
@@ -134,7 +133,7 @@ function loadTemplateFromFile(filePath: string, source: string): PromptTemplate
|
||||
/**
|
||||
* Scan a directory for .md files (non-recursive) and load them as prompt templates.
|
||||
*/
|
||||
function loadTemplatesFromDir(dir: string, source: string): PromptTemplate[] {
|
||||
function loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {
|
||||
const templates: PromptTemplate[] = [];
|
||||
|
||||
if (!existsSync(dir)) {
|
||||
@@ -160,7 +159,7 @@ function loadTemplatesFromDir(dir: string, source: string): PromptTemplate[] {
|
||||
}
|
||||
|
||||
if (isFile && entry.name.endsWith(".md")) {
|
||||
const template = loadTemplateFromFile(fullPath, source);
|
||||
const template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));
|
||||
if (template) {
|
||||
templates.push(template);
|
||||
}
|
||||
@@ -211,18 +210,7 @@ export function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): P
|
||||
|
||||
const templates: PromptTemplate[] = [];
|
||||
|
||||
if (includeDefaults) {
|
||||
// 1. Load global templates from agentDir/prompts/
|
||||
// Note: if agentDir is provided, it should be the agent dir, not the prompts dir
|
||||
const globalPromptsDir = options.agentDir ? join(options.agentDir, "prompts") : resolvedAgentDir;
|
||||
templates.push(...loadTemplatesFromDir(globalPromptsDir, "user"));
|
||||
|
||||
// 2. Load project templates from cwd/{CONFIG_DIR_NAME}/prompts/
|
||||
const projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
|
||||
templates.push(...loadTemplatesFromDir(projectPromptsDir, "project"));
|
||||
}
|
||||
|
||||
const userPromptsDir = options.agentDir ? join(options.agentDir, "prompts") : resolvedAgentDir;
|
||||
const globalPromptsDir = options.agentDir ? join(options.agentDir, "prompts") : resolvedAgentDir;
|
||||
const projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
|
||||
|
||||
const isUnderPath = (target: string, root: string): boolean => {
|
||||
@@ -234,18 +222,32 @@ export function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): P
|
||||
return target.startsWith(prefix);
|
||||
};
|
||||
|
||||
const getSource = (resolvedPath: string): string => {
|
||||
if (!includeDefaults) {
|
||||
if (isUnderPath(resolvedPath, userPromptsDir)) {
|
||||
return "user";
|
||||
}
|
||||
if (isUnderPath(resolvedPath, projectPromptsDir)) {
|
||||
return "project";
|
||||
}
|
||||
const getSourceInfo = (resolvedPath: string): SourceInfo => {
|
||||
if (isUnderPath(resolvedPath, globalPromptsDir)) {
|
||||
return createSyntheticSourceInfo(resolvedPath, {
|
||||
source: "local",
|
||||
scope: "user",
|
||||
baseDir: globalPromptsDir,
|
||||
});
|
||||
}
|
||||
return "path";
|
||||
if (isUnderPath(resolvedPath, projectPromptsDir)) {
|
||||
return createSyntheticSourceInfo(resolvedPath, {
|
||||
source: "local",
|
||||
scope: "project",
|
||||
baseDir: projectPromptsDir,
|
||||
});
|
||||
}
|
||||
return createSyntheticSourceInfo(resolvedPath, {
|
||||
source: "local",
|
||||
baseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),
|
||||
});
|
||||
};
|
||||
|
||||
if (includeDefaults) {
|
||||
templates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));
|
||||
templates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));
|
||||
}
|
||||
|
||||
// 3. Load explicit prompt paths
|
||||
for (const rawPath of promptPaths) {
|
||||
const resolvedPath = resolvePromptPath(rawPath, resolvedCwd);
|
||||
@@ -255,11 +257,10 @@ export function loadPromptTemplates(options: LoadPromptTemplatesOptions = {}): P
|
||||
|
||||
try {
|
||||
const stats = statSync(resolvedPath);
|
||||
const source = getSource(resolvedPath);
|
||||
if (stats.isDirectory()) {
|
||||
templates.push(...loadTemplatesFromDir(resolvedPath, source));
|
||||
templates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));
|
||||
} else if (stats.isFile() && resolvedPath.endsWith(".md")) {
|
||||
const template = loadTemplateFromFile(resolvedPath, source);
|
||||
const template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));
|
||||
if (template) {
|
||||
templates.push(template);
|
||||
}
|
||||
|
||||
@@ -470,6 +470,7 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
...skill,
|
||||
sourceInfo:
|
||||
this.findSourceInfoForPath(skill.filePath, this.extensionSkillSourceInfos, metadataByPath) ??
|
||||
skill.sourceInfo ??
|
||||
this.getDefaultSourceInfoForPath(skill.filePath),
|
||||
}));
|
||||
this.skillDiagnostics = resolvedSkills.diagnostics;
|
||||
@@ -493,6 +494,7 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
...prompt,
|
||||
sourceInfo:
|
||||
this.findSourceInfoForPath(prompt.filePath, this.extensionPromptSourceInfos, metadataByPath) ??
|
||||
prompt.sourceInfo ??
|
||||
this.getDefaultSourceInfoForPath(prompt.filePath),
|
||||
}));
|
||||
this.promptDiagnostics = resolvedPrompts.diagnostics;
|
||||
@@ -512,8 +514,9 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
const sourcePath = theme.sourcePath;
|
||||
theme.sourceInfo = sourcePath
|
||||
? (this.findSourceInfoForPath(sourcePath, this.extensionThemeSourceInfos, metadataByPath) ??
|
||||
theme.sourceInfo ??
|
||||
this.getDefaultSourceInfoForPath(sourcePath))
|
||||
: undefined;
|
||||
: theme.sourceInfo;
|
||||
return theme;
|
||||
});
|
||||
this.themeDiagnostics = resolvedThemes.diagnostics;
|
||||
@@ -527,6 +530,9 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
for (const command of extension.commands.values()) {
|
||||
command.sourceInfo = extension.sourceInfo;
|
||||
}
|
||||
for (const tool of extension.tools.values()) {
|
||||
tool.sourceInfo = extension.sourceInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,11 +582,7 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getDefaultSourceInfoForPath(filePath: string): SourceInfo | undefined {
|
||||
if (!filePath) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private getDefaultSourceInfoForPath(filePath: string): SourceInfo {
|
||||
if (filePath.startsWith("<") && filePath.endsWith(">")) {
|
||||
return {
|
||||
path: filePath,
|
||||
@@ -606,17 +608,23 @@ export class DefaultResourceLoader implements ResourceLoader {
|
||||
|
||||
for (const root of agentRoots) {
|
||||
if (this.isUnderPath(normalizedPath, root)) {
|
||||
return { path: filePath, source: "local", scope: "user", origin: "top-level" };
|
||||
return { path: filePath, source: "local", scope: "user", origin: "top-level", baseDir: root };
|
||||
}
|
||||
}
|
||||
|
||||
for (const root of projectRoots) {
|
||||
if (this.isUnderPath(normalizedPath, root)) {
|
||||
return { path: filePath, source: "local", scope: "project", origin: "top-level" };
|
||||
return { path: filePath, source: "local", scope: "project", origin: "top-level", baseDir: root };
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return {
|
||||
path: filePath,
|
||||
source: "local",
|
||||
scope: "temporary",
|
||||
origin: "top-level",
|
||||
baseDir: statSync(normalizedPath).isDirectory() ? normalizedPath : resolve(normalizedPath, ".."),
|
||||
};
|
||||
}
|
||||
|
||||
private mergePaths(primary: string[], additional: string[]): string[] {
|
||||
|
||||
@@ -90,7 +90,6 @@ export type {
|
||||
ExtensionContext,
|
||||
ExtensionFactory,
|
||||
SlashCommandInfo,
|
||||
SlashCommandLocation,
|
||||
SlashCommandSource,
|
||||
ToolDefinition,
|
||||
} from "./extensions/index.js";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "pat
|
||||
import { CONFIG_DIR_NAME, getAgentDir } from "../config.js";
|
||||
import { parseFrontmatter } from "../utils/frontmatter.js";
|
||||
import type { ResourceDiagnostic } from "./diagnostics.js";
|
||||
import type { SourceInfo } from "./source-info.js";
|
||||
import { createSyntheticSourceInfo, type SourceInfo } from "./source-info.js";
|
||||
|
||||
/** Max name length per spec */
|
||||
const MAX_NAME_LENGTH = 64;
|
||||
@@ -76,8 +76,7 @@ export interface Skill {
|
||||
description: string;
|
||||
filePath: string;
|
||||
baseDir: string;
|
||||
source: string;
|
||||
sourceInfo?: SourceInfo;
|
||||
sourceInfo: SourceInfo;
|
||||
disableModelInvocation: boolean;
|
||||
}
|
||||
|
||||
@@ -138,6 +137,30 @@ export interface LoadSkillsFromDirOptions {
|
||||
source: string;
|
||||
}
|
||||
|
||||
function createSkillSourceInfo(filePath: string, baseDir: string, source: string): SourceInfo {
|
||||
switch (source) {
|
||||
case "user":
|
||||
return createSyntheticSourceInfo(filePath, {
|
||||
source: "local",
|
||||
scope: "user",
|
||||
baseDir,
|
||||
});
|
||||
case "project":
|
||||
return createSyntheticSourceInfo(filePath, {
|
||||
source: "local",
|
||||
scope: "project",
|
||||
baseDir,
|
||||
});
|
||||
case "path":
|
||||
return createSyntheticSourceInfo(filePath, {
|
||||
source: "local",
|
||||
baseDir,
|
||||
});
|
||||
default:
|
||||
return createSyntheticSourceInfo(filePath, { source, baseDir });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load skills from a directory.
|
||||
*
|
||||
@@ -293,7 +316,7 @@ function loadSkillFromFile(
|
||||
description: frontmatter.description,
|
||||
filePath,
|
||||
baseDir: skillDir,
|
||||
source,
|
||||
sourceInfo: createSkillSourceInfo(filePath, skillDir, source),
|
||||
disableModelInvocation: frontmatter["disable-model-invocation"] === true,
|
||||
},
|
||||
diagnostics,
|
||||
|
||||
@@ -2,15 +2,11 @@ import type { SourceInfo } from "./source-info.js";
|
||||
|
||||
export type SlashCommandSource = "extension" | "prompt" | "skill";
|
||||
|
||||
export type SlashCommandLocation = "user" | "project" | "path";
|
||||
|
||||
export interface SlashCommandInfo {
|
||||
name: string;
|
||||
description?: string;
|
||||
source: SlashCommandSource;
|
||||
sourceInfo?: SourceInfo;
|
||||
location?: SlashCommandLocation;
|
||||
path?: string;
|
||||
sourceInfo: SourceInfo;
|
||||
}
|
||||
|
||||
export interface BuiltinSlashCommand {
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import type { PathMetadata } from "./package-manager.js";
|
||||
|
||||
export type SourceScope = "user" | "project" | "temporary";
|
||||
export type SourceOrigin = "package" | "top-level";
|
||||
|
||||
export interface SourceInfo {
|
||||
path?: string;
|
||||
path: string;
|
||||
source: string;
|
||||
scope: "user" | "project" | "temporary";
|
||||
origin: "package" | "top-level";
|
||||
scope: SourceScope;
|
||||
origin: SourceOrigin;
|
||||
baseDir?: string;
|
||||
}
|
||||
|
||||
export function createSourceInfo(path: string | undefined, metadata: PathMetadata): SourceInfo {
|
||||
export function createSourceInfo(path: string, metadata: PathMetadata): SourceInfo {
|
||||
return {
|
||||
path,
|
||||
source: metadata.source,
|
||||
@@ -17,3 +20,21 @@ export function createSourceInfo(path: string | undefined, metadata: PathMetadat
|
||||
baseDir: metadata.baseDir,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSyntheticSourceInfo(
|
||||
path: string,
|
||||
options: {
|
||||
source: string;
|
||||
scope?: SourceScope;
|
||||
origin?: SourceOrigin;
|
||||
baseDir?: string;
|
||||
},
|
||||
): SourceInfo {
|
||||
return {
|
||||
path,
|
||||
source: options.source,
|
||||
scope: options.scope ?? "temporary",
|
||||
origin: options.origin ?? "top-level",
|
||||
baseDir: options.baseDir,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user