@mariozechner/pi-coding-agent 0.23.4 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/README.md +20 -13
- package/dist/core/custom-tools/loader.d.ts.map +1 -1
- package/dist/core/custom-tools/loader.js +56 -6
- package/dist/core/custom-tools/loader.js.map +1 -1
- package/dist/core/custom-tools/types.d.ts +9 -1
- package/dist/core/custom-tools/types.d.ts.map +1 -1
- package/dist/core/custom-tools/types.js.map +1 -1
- package/dist/core/hooks/index.d.ts +2 -1
- package/dist/core/hooks/index.d.ts.map +1 -1
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/hooks/index.js.map +1 -1
- package/dist/core/hooks/runner.d.ts.map +1 -1
- package/dist/core/hooks/runner.js +44 -4
- package/dist/core/hooks/runner.js.map +1 -1
- package/dist/core/hooks/tool-wrapper.d.ts.map +1 -1
- package/dist/core/hooks/tool-wrapper.js +5 -9
- package/dist/core/hooks/tool-wrapper.js.map +1 -1
- package/dist/core/hooks/types.d.ts +73 -11
- package/dist/core/hooks/types.d.ts.map +1 -1
- package/dist/core/hooks/types.js +22 -1
- package/dist/core/hooks/types.js.map +1 -1
- package/dist/core/skills.d.ts +18 -5
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +183 -72
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +2 -2
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +2 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/find.d.ts +5 -0
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts +6 -0
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +6 -5
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts +5 -0
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/read.d.ts +4 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +16 -7
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/hook-input.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-input.js +2 -2
- package/dist/modes/interactive/components/hook-input.js.map +1 -1
- package/dist/modes/interactive/components/hook-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-selector.js +2 -2
- package/dist/modes/interactive/components/hook-selector.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +2 -2
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/oauth-selector.js +2 -2
- package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +3 -3
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message-selector.js +3 -3
- package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +63 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +10 -0
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/custom-tools.md +43 -4
- package/docs/hooks.md +104 -5
- package/docs/skills.md +65 -24
- package/examples/custom-tools/README.md +18 -7
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +157 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +772 -0
- package/package.json +4 -4
- /package/examples/custom-tools/{hello.ts → hello/index.ts} +0 -0
- /package/examples/custom-tools/{question.ts → question/index.ts} +0 -0
- /package/examples/custom-tools/{todo.ts → todo/index.ts} +0 -0
|
@@ -8,12 +8,42 @@ import { spawn } from "node:child_process";
|
|
|
8
8
|
const DEFAULT_TIMEOUT = 30000;
|
|
9
9
|
/**
|
|
10
10
|
* Execute a command and return stdout/stderr/code.
|
|
11
|
+
* Supports cancellation via AbortSignal and timeout.
|
|
11
12
|
*/
|
|
12
|
-
async function exec(command, args, cwd) {
|
|
13
|
+
async function exec(command, args, cwd, options) {
|
|
13
14
|
return new Promise((resolve) => {
|
|
14
15
|
const proc = spawn(command, args, { cwd, shell: false });
|
|
15
16
|
let stdout = "";
|
|
16
17
|
let stderr = "";
|
|
18
|
+
let killed = false;
|
|
19
|
+
let timeoutId;
|
|
20
|
+
const killProcess = () => {
|
|
21
|
+
if (!killed) {
|
|
22
|
+
killed = true;
|
|
23
|
+
proc.kill("SIGTERM");
|
|
24
|
+
// Force kill after 5 seconds if SIGTERM doesn't work
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
if (!proc.killed) {
|
|
27
|
+
proc.kill("SIGKILL");
|
|
28
|
+
}
|
|
29
|
+
}, 5000);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
// Handle abort signal
|
|
33
|
+
if (options?.signal) {
|
|
34
|
+
if (options.signal.aborted) {
|
|
35
|
+
killProcess();
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
options.signal.addEventListener("abort", killProcess, { once: true });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Handle timeout
|
|
42
|
+
if (options?.timeout && options.timeout > 0) {
|
|
43
|
+
timeoutId = setTimeout(() => {
|
|
44
|
+
killProcess();
|
|
45
|
+
}, options.timeout);
|
|
46
|
+
}
|
|
17
47
|
proc.stdout?.on("data", (data) => {
|
|
18
48
|
stdout += data.toString();
|
|
19
49
|
});
|
|
@@ -21,10 +51,20 @@ async function exec(command, args, cwd) {
|
|
|
21
51
|
stderr += data.toString();
|
|
22
52
|
});
|
|
23
53
|
proc.on("close", (code) => {
|
|
24
|
-
|
|
54
|
+
if (timeoutId)
|
|
55
|
+
clearTimeout(timeoutId);
|
|
56
|
+
if (options?.signal) {
|
|
57
|
+
options.signal.removeEventListener("abort", killProcess);
|
|
58
|
+
}
|
|
59
|
+
resolve({ stdout, stderr, code: code ?? 0, killed });
|
|
25
60
|
});
|
|
26
61
|
proc.on("error", (_err) => {
|
|
27
|
-
|
|
62
|
+
if (timeoutId)
|
|
63
|
+
clearTimeout(timeoutId);
|
|
64
|
+
if (options?.signal) {
|
|
65
|
+
options.signal.removeEventListener("abort", killProcess);
|
|
66
|
+
}
|
|
67
|
+
resolve({ stdout, stderr, code: 1, killed });
|
|
28
68
|
});
|
|
29
69
|
});
|
|
30
70
|
}
|
|
@@ -129,7 +169,7 @@ export class HookRunner {
|
|
|
129
169
|
*/
|
|
130
170
|
createContext() {
|
|
131
171
|
return {
|
|
132
|
-
exec: (command, args) => exec(command, args, this.cwd),
|
|
172
|
+
exec: (command, args, options) => exec(command, args, this.cwd, options),
|
|
133
173
|
ui: this.uiContext,
|
|
134
174
|
hasUI: this.hasUI,
|
|
135
175
|
cwd: this.cwd,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook runner - executes hooks and manages their lifecycle.\n */\n\nimport { spawn } from \"node:child_process\";\nimport type { LoadedHook, SendHandler } from \"./loader.js\";\nimport type {\n\tBranchEventResult,\n\tExecResult,\n\tHookError,\n\tHookEvent,\n\tHookEventContext,\n\tHookUIContext,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEventResult,\n} from \"./types.js\";\n\n/**\n * Default timeout for hook execution (30 seconds).\n */\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * Listener for hook errors.\n */\nexport type HookErrorListener = (error: HookError) => void;\n\n/**\n * Execute a command and return stdout/stderr/code.\n */\nasync function exec(command: string, args: string[], cwd: string): Promise<ExecResult> {\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(command, args, { cwd, shell: false });\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\n\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tproc.on(\"close\", (code) => {\n\t\t\tresolve({ stdout, stderr, code: code ?? 0 });\n\t\t});\n\n\t\tproc.on(\"error\", (_err) => {\n\t\t\tresolve({ stdout, stderr, code: 1 });\n\t\t});\n\t});\n}\n\n/**\n * Create a promise that rejects after a timeout.\n */\nfunction createTimeout(ms: number): { promise: Promise<never>; clear: () => void } {\n\tlet timeoutId: NodeJS.Timeout;\n\tconst promise = new Promise<never>((_, reject) => {\n\t\ttimeoutId = setTimeout(() => reject(new Error(`Hook timed out after ${ms}ms`)), ms);\n\t});\n\treturn {\n\t\tpromise,\n\t\tclear: () => clearTimeout(timeoutId),\n\t};\n}\n\n/** No-op UI context used when no UI is available */\nconst noOpUIContext: HookUIContext = {\n\tselect: async () => null,\n\tconfirm: async () => false,\n\tinput: async () => null,\n\tnotify: () => {},\n};\n\n/**\n * HookRunner executes hooks and manages event emission.\n */\nexport class HookRunner {\n\tprivate hooks: LoadedHook[];\n\tprivate uiContext: HookUIContext;\n\tprivate hasUI: boolean;\n\tprivate cwd: string;\n\tprivate sessionFile: string | null;\n\tprivate timeout: number;\n\tprivate errorListeners: Set<HookErrorListener> = new Set();\n\n\tconstructor(hooks: LoadedHook[], cwd: string, timeout: number = DEFAULT_TIMEOUT) {\n\t\tthis.hooks = hooks;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.hasUI = false;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionFile = null;\n\t\tthis.timeout = timeout;\n\t}\n\n\t/**\n\t * Set the UI context for hooks.\n\t * Call this when the mode initializes and UI is available.\n\t */\n\tsetUIContext(uiContext: HookUIContext, hasUI: boolean): void {\n\t\tthis.uiContext = uiContext;\n\t\tthis.hasUI = hasUI;\n\t}\n\n\t/**\n\t * Get the paths of all loaded hooks.\n\t */\n\tgetHookPaths(): string[] {\n\t\treturn this.hooks.map((h) => h.path);\n\t}\n\n\t/**\n\t * Set the session file path.\n\t */\n\tsetSessionFile(sessionFile: string | null): void {\n\t\tthis.sessionFile = sessionFile;\n\t}\n\n\t/**\n\t * Set the send handler for all hooks' pi.send().\n\t * Call this when the mode initializes.\n\t */\n\tsetSendHandler(handler: SendHandler): void {\n\t\tfor (const hook of this.hooks) {\n\t\t\thook.setSendHandler(handler);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to hook errors.\n\t * @returns Unsubscribe function\n\t */\n\tonError(listener: HookErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\t/**\n\t * Emit an error to all listeners.\n\t */\n\tprivate emitError(error: HookError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\t/**\n\t * Check if any hooks have handlers for the given event type.\n\t */\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Create the event context for handlers.\n\t */\n\tprivate createContext(): HookEventContext {\n\t\treturn {\n\t\t\texec: (command: string, args: string[]) => exec(command, args, this.cwd),\n\t\t\tui: this.uiContext,\n\t\t\thasUI: this.hasUI,\n\t\t\tcwd: this.cwd,\n\t\t\tsessionFile: this.sessionFile,\n\t\t};\n\t}\n\n\t/**\n\t * Emit an event to all hooks.\n\t * Returns the result from branch/tool_result events (if any handler returns one).\n\t */\n\tasync emit(event: HookEvent): Promise<BranchEventResult | ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: BranchEventResult | ToolResultEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst timeout = createTimeout(this.timeout);\n\t\t\t\t\tconst handlerResult = await Promise.race([handler(event, ctx), timeout.promise]);\n\t\t\t\t\ttimeout.clear();\n\n\t\t\t\t\t// For branch events, capture the result\n\t\t\t\t\tif (event.type === \"branch\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as BranchEventResult;\n\t\t\t\t\t}\n\n\t\t\t\t\t// For tool_result events, capture the result\n\t\t\t\t\tif (event.type === \"tool_result\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as ToolResultEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a tool_call event to all hooks.\n\t * No timeout - user prompts can take as long as needed.\n\t * Errors are thrown (not swallowed) so caller can block on failure.\n\t */\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\t// No timeout - let user take their time\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\t// If blocked, stop processing further hooks\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../../src/core/hooks/runner.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook runner - executes hooks and manages their lifecycle.\n */\n\nimport { spawn } from \"node:child_process\";\nimport type { LoadedHook, SendHandler } from \"./loader.js\";\nimport type {\n\tBranchEventResult,\n\tExecOptions,\n\tExecResult,\n\tHookError,\n\tHookEvent,\n\tHookEventContext,\n\tHookUIContext,\n\tToolCallEvent,\n\tToolCallEventResult,\n\tToolResultEventResult,\n} from \"./types.js\";\n\n/**\n * Default timeout for hook execution (30 seconds).\n */\nconst DEFAULT_TIMEOUT = 30000;\n\n/**\n * Listener for hook errors.\n */\nexport type HookErrorListener = (error: HookError) => void;\n\n/**\n * Execute a command and return stdout/stderr/code.\n * Supports cancellation via AbortSignal and timeout.\n */\nasync function exec(command: string, args: string[], cwd: string, options?: ExecOptions): Promise<ExecResult> {\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(command, args, { cwd, shell: false });\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tlet killed = false;\n\t\tlet timeoutId: NodeJS.Timeout | undefined;\n\n\t\tconst killProcess = () => {\n\t\t\tif (!killed) {\n\t\t\t\tkilled = true;\n\t\t\t\tproc.kill(\"SIGTERM\");\n\t\t\t\t// Force kill after 5 seconds if SIGTERM doesn't work\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tif (!proc.killed) {\n\t\t\t\t\t\tproc.kill(\"SIGKILL\");\n\t\t\t\t\t}\n\t\t\t\t}, 5000);\n\t\t\t}\n\t\t};\n\n\t\t// Handle abort signal\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\tkillProcess();\n\t\t\t} else {\n\t\t\t\toptions.signal.addEventListener(\"abort\", killProcess, { once: true });\n\t\t\t}\n\t\t}\n\n\t\t// Handle timeout\n\t\tif (options?.timeout && options.timeout > 0) {\n\t\t\ttimeoutId = setTimeout(() => {\n\t\t\t\tkillProcess();\n\t\t\t}, options.timeout);\n\t\t}\n\n\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tproc.on(\"close\", (code) => {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", killProcess);\n\t\t\t}\n\t\t\tresolve({ stdout, stderr, code: code ?? 0, killed });\n\t\t});\n\n\t\tproc.on(\"error\", (_err) => {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", killProcess);\n\t\t\t}\n\t\t\tresolve({ stdout, stderr, code: 1, killed });\n\t\t});\n\t});\n}\n\n/**\n * Create a promise that rejects after a timeout.\n */\nfunction createTimeout(ms: number): { promise: Promise<never>; clear: () => void } {\n\tlet timeoutId: NodeJS.Timeout;\n\tconst promise = new Promise<never>((_, reject) => {\n\t\ttimeoutId = setTimeout(() => reject(new Error(`Hook timed out after ${ms}ms`)), ms);\n\t});\n\treturn {\n\t\tpromise,\n\t\tclear: () => clearTimeout(timeoutId),\n\t};\n}\n\n/** No-op UI context used when no UI is available */\nconst noOpUIContext: HookUIContext = {\n\tselect: async () => null,\n\tconfirm: async () => false,\n\tinput: async () => null,\n\tnotify: () => {},\n};\n\n/**\n * HookRunner executes hooks and manages event emission.\n */\nexport class HookRunner {\n\tprivate hooks: LoadedHook[];\n\tprivate uiContext: HookUIContext;\n\tprivate hasUI: boolean;\n\tprivate cwd: string;\n\tprivate sessionFile: string | null;\n\tprivate timeout: number;\n\tprivate errorListeners: Set<HookErrorListener> = new Set();\n\n\tconstructor(hooks: LoadedHook[], cwd: string, timeout: number = DEFAULT_TIMEOUT) {\n\t\tthis.hooks = hooks;\n\t\tthis.uiContext = noOpUIContext;\n\t\tthis.hasUI = false;\n\t\tthis.cwd = cwd;\n\t\tthis.sessionFile = null;\n\t\tthis.timeout = timeout;\n\t}\n\n\t/**\n\t * Set the UI context for hooks.\n\t * Call this when the mode initializes and UI is available.\n\t */\n\tsetUIContext(uiContext: HookUIContext, hasUI: boolean): void {\n\t\tthis.uiContext = uiContext;\n\t\tthis.hasUI = hasUI;\n\t}\n\n\t/**\n\t * Get the paths of all loaded hooks.\n\t */\n\tgetHookPaths(): string[] {\n\t\treturn this.hooks.map((h) => h.path);\n\t}\n\n\t/**\n\t * Set the session file path.\n\t */\n\tsetSessionFile(sessionFile: string | null): void {\n\t\tthis.sessionFile = sessionFile;\n\t}\n\n\t/**\n\t * Set the send handler for all hooks' pi.send().\n\t * Call this when the mode initializes.\n\t */\n\tsetSendHandler(handler: SendHandler): void {\n\t\tfor (const hook of this.hooks) {\n\t\t\thook.setSendHandler(handler);\n\t\t}\n\t}\n\n\t/**\n\t * Subscribe to hook errors.\n\t * @returns Unsubscribe function\n\t */\n\tonError(listener: HookErrorListener): () => void {\n\t\tthis.errorListeners.add(listener);\n\t\treturn () => this.errorListeners.delete(listener);\n\t}\n\n\t/**\n\t * Emit an error to all listeners.\n\t */\n\tprivate emitError(error: HookError): void {\n\t\tfor (const listener of this.errorListeners) {\n\t\t\tlistener(error);\n\t\t}\n\t}\n\n\t/**\n\t * Check if any hooks have handlers for the given event type.\n\t */\n\thasHandlers(eventType: string): boolean {\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(eventType);\n\t\t\tif (handlers && handlers.length > 0) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Create the event context for handlers.\n\t */\n\tprivate createContext(): HookEventContext {\n\t\treturn {\n\t\t\texec: (command: string, args: string[], options?: ExecOptions) => exec(command, args, this.cwd, options),\n\t\t\tui: this.uiContext,\n\t\t\thasUI: this.hasUI,\n\t\t\tcwd: this.cwd,\n\t\t\tsessionFile: this.sessionFile,\n\t\t};\n\t}\n\n\t/**\n\t * Emit an event to all hooks.\n\t * Returns the result from branch/tool_result events (if any handler returns one).\n\t */\n\tasync emit(event: HookEvent): Promise<BranchEventResult | ToolResultEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: BranchEventResult | ToolResultEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(event.type);\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\ttry {\n\t\t\t\t\tconst timeout = createTimeout(this.timeout);\n\t\t\t\t\tconst handlerResult = await Promise.race([handler(event, ctx), timeout.promise]);\n\t\t\t\t\ttimeout.clear();\n\n\t\t\t\t\t// For branch events, capture the result\n\t\t\t\t\tif (event.type === \"branch\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as BranchEventResult;\n\t\t\t\t\t}\n\n\t\t\t\t\t// For tool_result events, capture the result\n\t\t\t\t\tif (event.type === \"tool_result\" && handlerResult) {\n\t\t\t\t\t\tresult = handlerResult as ToolResultEventResult;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tthis.emitError({\n\t\t\t\t\t\thookPath: hook.path,\n\t\t\t\t\t\tevent: event.type,\n\t\t\t\t\t\terror: message,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Emit a tool_call event to all hooks.\n\t * No timeout - user prompts can take as long as needed.\n\t * Errors are thrown (not swallowed) so caller can block on failure.\n\t */\n\tasync emitToolCall(event: ToolCallEvent): Promise<ToolCallEventResult | undefined> {\n\t\tconst ctx = this.createContext();\n\t\tlet result: ToolCallEventResult | undefined;\n\n\t\tfor (const hook of this.hooks) {\n\t\t\tconst handlers = hook.handlers.get(\"tool_call\");\n\t\t\tif (!handlers || handlers.length === 0) continue;\n\n\t\t\tfor (const handler of handlers) {\n\t\t\t\t// No timeout - let user take their time\n\t\t\t\tconst handlerResult = await handler(event, ctx);\n\n\t\t\t\tif (handlerResult) {\n\t\t\t\t\tresult = handlerResult as ToolCallEventResult;\n\t\t\t\t\t// If blocked, stop processing further hooks\n\t\t\t\t\tif (result.block) {\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-wrapper.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/tool-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"tool-wrapper.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/tool-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,CAuDvG;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAE7G","sourcesContent":["/**\n * Tool wrapper - wraps tools with hook callbacks for interception.\n */\n\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport type { HookRunner } from \"./runner.js\";\nimport type { ToolCallEventResult, ToolResultEventResult } from \"./types.js\";\n\n/**\n * Wrap a tool with hook callbacks.\n * - Emits tool_call event before execution (can block)\n * - Emits tool_result event after execution (can modify result)\n */\nexport function wrapToolWithHooks<T>(tool: AgentTool<any, T>, hookRunner: HookRunner): AgentTool<any, T> {\n\treturn {\n\t\t...tool,\n\t\texecute: async (toolCallId: string, params: Record<string, unknown>, signal?: AbortSignal) => {\n\t\t\t// Emit tool_call event - hooks can block execution\n\t\t\t// If hook errors/times out, block by default (fail-safe)\n\t\t\tif (hookRunner.hasHandlers(\"tool_call\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconst callResult = (await hookRunner.emitToolCall({\n\t\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\t\ttoolName: tool.name,\n\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\tinput: params,\n\t\t\t\t\t})) as ToolCallEventResult | undefined;\n\n\t\t\t\t\tif (callResult?.block) {\n\t\t\t\t\t\tconst reason = callResult.reason || \"Tool execution was blocked by a hook\";\n\t\t\t\t\t\tthrow new Error(reason);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Hook error or block - throw to mark as error\n\t\t\t\t\tif (err instanceof Error) {\n\t\t\t\t\t\tthrow err;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Hook failed, blocking execution: ${String(err)}`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Execute the actual tool\n\t\t\tconst result = await tool.execute(toolCallId, params, signal);\n\n\t\t\t// Emit tool_result event - hooks can modify the result\n\t\t\tif (hookRunner.hasHandlers(\"tool_result\")) {\n\t\t\t\tconst resultResult = (await hookRunner.emit({\n\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\ttoolName: tool.name,\n\t\t\t\t\ttoolCallId,\n\t\t\t\t\tinput: params,\n\t\t\t\t\tcontent: result.content,\n\t\t\t\t\tdetails: result.details,\n\t\t\t\t\tisError: false,\n\t\t\t\t})) as ToolResultEventResult | undefined;\n\n\t\t\t\t// Apply modifications if any\n\t\t\t\tif (resultResult) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: resultResult.content ?? result.content,\n\t\t\t\t\t\tdetails: (resultResult.details ?? result.details) as T,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\t};\n}\n\n/**\n * Wrap all tools with hook callbacks.\n */\nexport function wrapToolsWithHooks<T>(tools: AgentTool<any, T>[], hookRunner: HookRunner): AgentTool<any, T>[] {\n\treturn tools.map((tool) => wrapToolWithHooks(tool, hookRunner));\n}\n"]}
|
|
@@ -37,24 +37,20 @@ export function wrapToolWithHooks(tool, hookRunner) {
|
|
|
37
37
|
const result = await tool.execute(toolCallId, params, signal);
|
|
38
38
|
// Emit tool_result event - hooks can modify the result
|
|
39
39
|
if (hookRunner.hasHandlers("tool_result")) {
|
|
40
|
-
// Extract text from result for hooks
|
|
41
|
-
const resultText = result.content
|
|
42
|
-
.filter((c) => c.type === "text")
|
|
43
|
-
.map((c) => c.text)
|
|
44
|
-
.join("\n");
|
|
45
40
|
const resultResult = (await hookRunner.emit({
|
|
46
41
|
type: "tool_result",
|
|
47
42
|
toolName: tool.name,
|
|
48
43
|
toolCallId,
|
|
49
44
|
input: params,
|
|
50
|
-
|
|
45
|
+
content: result.content,
|
|
46
|
+
details: result.details,
|
|
51
47
|
isError: false,
|
|
52
48
|
}));
|
|
53
49
|
// Apply modifications if any
|
|
54
|
-
if (resultResult
|
|
50
|
+
if (resultResult) {
|
|
55
51
|
return {
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
content: resultResult.content ?? result.content,
|
|
53
|
+
details: (resultResult.details ?? result.details),
|
|
58
54
|
};
|
|
59
55
|
}
|
|
60
56
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-wrapper.js","sourceRoot":"","sources":["../../../src/core/hooks/tool-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAI,IAAuB,EAAE,UAAsB,EAAqB;IACxG,OAAO;QACN,GAAG,IAAI;QACP,OAAO,EAAE,KAAK,EAAE,UAAkB,EAAE,MAA+B,EAAE,MAAoB,EAAE,EAAE,CAAC;YAC7F,mDAAmD;YACnD,yDAAyD;YACzD,IAAI,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;gBACzC,IAAI,CAAC;oBACJ,MAAM,UAAU,GAAG,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC;wBACjD,IAAI,EAAE,WAAW;wBACjB,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,UAAU;wBACV,KAAK,EAAE,MAAM;qBACb,CAAC,CAAoC,CAAC;oBAEvC,IAAI,UAAU,EAAE,KAAK,EAAE,CAAC;wBACvB,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,sCAAsC,CAAC;wBAC3E,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;oBACzB,CAAC;gBACF,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,+CAA+C;oBAC/C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;wBAC1B,MAAM,GAAG,CAAC;oBACX,CAAC;oBACD,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACpE,CAAC;YACF,CAAC;YAED,0BAA0B;YAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YAE9D,uDAAuD;YACvD,IAAI,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC3C,
|
|
1
|
+
{"version":3,"file":"tool-wrapper.js","sourceRoot":"","sources":["../../../src/core/hooks/tool-wrapper.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAI,IAAuB,EAAE,UAAsB,EAAqB;IACxG,OAAO;QACN,GAAG,IAAI;QACP,OAAO,EAAE,KAAK,EAAE,UAAkB,EAAE,MAA+B,EAAE,MAAoB,EAAE,EAAE,CAAC;YAC7F,mDAAmD;YACnD,yDAAyD;YACzD,IAAI,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,EAAE,CAAC;gBACzC,IAAI,CAAC;oBACJ,MAAM,UAAU,GAAG,CAAC,MAAM,UAAU,CAAC,YAAY,CAAC;wBACjD,IAAI,EAAE,WAAW;wBACjB,QAAQ,EAAE,IAAI,CAAC,IAAI;wBACnB,UAAU;wBACV,KAAK,EAAE,MAAM;qBACb,CAAC,CAAoC,CAAC;oBAEvC,IAAI,UAAU,EAAE,KAAK,EAAE,CAAC;wBACvB,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,IAAI,sCAAsC,CAAC;wBAC3E,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;oBACzB,CAAC;gBACF,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACd,+CAA+C;oBAC/C,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;wBAC1B,MAAM,GAAG,CAAC;oBACX,CAAC;oBACD,MAAM,IAAI,KAAK,CAAC,oCAAoC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACpE,CAAC;YACF,CAAC;YAED,0BAA0B;YAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;YAE9D,uDAAuD;YACvD,IAAI,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC3C,MAAM,YAAY,GAAG,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC;oBAC3C,IAAI,EAAE,aAAa;oBACnB,QAAQ,EAAE,IAAI,CAAC,IAAI;oBACnB,UAAU;oBACV,KAAK,EAAE,MAAM;oBACb,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,OAAO,EAAE,MAAM,CAAC,OAAO;oBACvB,OAAO,EAAE,KAAK;iBACd,CAAC,CAAsC,CAAC;gBAEzC,6BAA6B;gBAC7B,IAAI,YAAY,EAAE,CAAC;oBAClB,OAAO;wBACN,OAAO,EAAE,YAAY,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO;wBAC/C,OAAO,EAAE,CAAC,YAAY,CAAC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAM;qBACtD,CAAC;gBACH,CAAC;YACF,CAAC;YAED,OAAO,MAAM,CAAC;QAAA,CACd;KACD,CAAC;AAAA,CACF;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAI,KAA0B,EAAE,UAAsB,EAAuB;IAC9G,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;AAAA,CAChE","sourcesContent":["/**\n * Tool wrapper - wraps tools with hook callbacks for interception.\n */\n\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport type { HookRunner } from \"./runner.js\";\nimport type { ToolCallEventResult, ToolResultEventResult } from \"./types.js\";\n\n/**\n * Wrap a tool with hook callbacks.\n * - Emits tool_call event before execution (can block)\n * - Emits tool_result event after execution (can modify result)\n */\nexport function wrapToolWithHooks<T>(tool: AgentTool<any, T>, hookRunner: HookRunner): AgentTool<any, T> {\n\treturn {\n\t\t...tool,\n\t\texecute: async (toolCallId: string, params: Record<string, unknown>, signal?: AbortSignal) => {\n\t\t\t// Emit tool_call event - hooks can block execution\n\t\t\t// If hook errors/times out, block by default (fail-safe)\n\t\t\tif (hookRunner.hasHandlers(\"tool_call\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconst callResult = (await hookRunner.emitToolCall({\n\t\t\t\t\t\ttype: \"tool_call\",\n\t\t\t\t\t\ttoolName: tool.name,\n\t\t\t\t\t\ttoolCallId,\n\t\t\t\t\t\tinput: params,\n\t\t\t\t\t})) as ToolCallEventResult | undefined;\n\n\t\t\t\t\tif (callResult?.block) {\n\t\t\t\t\t\tconst reason = callResult.reason || \"Tool execution was blocked by a hook\";\n\t\t\t\t\t\tthrow new Error(reason);\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Hook error or block - throw to mark as error\n\t\t\t\t\tif (err instanceof Error) {\n\t\t\t\t\t\tthrow err;\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(`Hook failed, blocking execution: ${String(err)}`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Execute the actual tool\n\t\t\tconst result = await tool.execute(toolCallId, params, signal);\n\n\t\t\t// Emit tool_result event - hooks can modify the result\n\t\t\tif (hookRunner.hasHandlers(\"tool_result\")) {\n\t\t\t\tconst resultResult = (await hookRunner.emit({\n\t\t\t\t\ttype: \"tool_result\",\n\t\t\t\t\ttoolName: tool.name,\n\t\t\t\t\ttoolCallId,\n\t\t\t\t\tinput: params,\n\t\t\t\t\tcontent: result.content,\n\t\t\t\t\tdetails: result.details,\n\t\t\t\t\tisError: false,\n\t\t\t\t})) as ToolResultEventResult | undefined;\n\n\t\t\t\t// Apply modifications if any\n\t\t\t\tif (resultResult) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: resultResult.content ?? result.content,\n\t\t\t\t\t\tdetails: (resultResult.details ?? result.details) as T,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn result;\n\t\t},\n\t};\n}\n\n/**\n * Wrap all tools with hook callbacks.\n */\nexport function wrapToolsWithHooks<T>(tools: AgentTool<any, T>[], hookRunner: HookRunner): AgentTool<any, T>[] {\n\treturn tools.map((tool) => wrapToolWithHooks(tool, hookRunner));\n}\n"]}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* and interact with the user via UI primitives.
|
|
6
6
|
*/
|
|
7
7
|
import type { AppMessage, Attachment } from "@mariozechner/pi-agent-core";
|
|
8
|
-
import type { ToolResultMessage } from "@mariozechner/pi-ai";
|
|
8
|
+
import type { ImageContent, TextContent, ToolResultMessage } from "@mariozechner/pi-ai";
|
|
9
9
|
import type { SessionEntry } from "../session-manager.js";
|
|
10
|
+
import type { BashToolDetails, FindToolDetails, GrepToolDetails, LsToolDetails, ReadToolDetails } from "../tools/index.js";
|
|
10
11
|
/**
|
|
11
12
|
* Result of executing a command via ctx.exec()
|
|
12
13
|
*/
|
|
@@ -14,6 +15,14 @@ export interface ExecResult {
|
|
|
14
15
|
stdout: string;
|
|
15
16
|
stderr: string;
|
|
16
17
|
code: number;
|
|
18
|
+
/** True if the process was killed due to signal or timeout */
|
|
19
|
+
killed?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface ExecOptions {
|
|
22
|
+
/** AbortSignal to cancel the process */
|
|
23
|
+
signal?: AbortSignal;
|
|
24
|
+
/** Timeout in milliseconds */
|
|
25
|
+
timeout?: number;
|
|
17
26
|
}
|
|
18
27
|
/**
|
|
19
28
|
* UI context for hooks to request interactive UI from the harness.
|
|
@@ -47,7 +56,7 @@ export interface HookUIContext {
|
|
|
47
56
|
*/
|
|
48
57
|
export interface HookEventContext {
|
|
49
58
|
/** Execute a command and return stdout/stderr/code */
|
|
50
|
-
exec(command: string, args: string[]): Promise<ExecResult>;
|
|
59
|
+
exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
|
|
51
60
|
/** UI methods for user interaction */
|
|
52
61
|
ui: HookUIContext;
|
|
53
62
|
/** Whether UI is available (false in print mode) */
|
|
@@ -118,22 +127,72 @@ export interface ToolCallEvent {
|
|
|
118
127
|
input: Record<string, unknown>;
|
|
119
128
|
}
|
|
120
129
|
/**
|
|
121
|
-
*
|
|
122
|
-
* Fired after a tool is executed. Hooks can modify the result.
|
|
130
|
+
* Base interface for tool_result events.
|
|
123
131
|
*/
|
|
124
|
-
|
|
132
|
+
interface ToolResultEventBase {
|
|
125
133
|
type: "tool_result";
|
|
126
|
-
/** Tool name (e.g., "bash", "edit", "write") */
|
|
127
|
-
toolName: string;
|
|
128
134
|
/** Tool call ID */
|
|
129
135
|
toolCallId: string;
|
|
130
136
|
/** Tool input parameters */
|
|
131
137
|
input: Record<string, unknown>;
|
|
132
|
-
/**
|
|
133
|
-
|
|
138
|
+
/** Full content array (text and images) */
|
|
139
|
+
content: (TextContent | ImageContent)[];
|
|
134
140
|
/** Whether the tool execution was an error */
|
|
135
141
|
isError: boolean;
|
|
136
142
|
}
|
|
143
|
+
/** Tool result event for bash tool */
|
|
144
|
+
export interface BashToolResultEvent extends ToolResultEventBase {
|
|
145
|
+
toolName: "bash";
|
|
146
|
+
details: BashToolDetails | undefined;
|
|
147
|
+
}
|
|
148
|
+
/** Tool result event for read tool */
|
|
149
|
+
export interface ReadToolResultEvent extends ToolResultEventBase {
|
|
150
|
+
toolName: "read";
|
|
151
|
+
details: ReadToolDetails | undefined;
|
|
152
|
+
}
|
|
153
|
+
/** Tool result event for edit tool */
|
|
154
|
+
export interface EditToolResultEvent extends ToolResultEventBase {
|
|
155
|
+
toolName: "edit";
|
|
156
|
+
details: undefined;
|
|
157
|
+
}
|
|
158
|
+
/** Tool result event for write tool */
|
|
159
|
+
export interface WriteToolResultEvent extends ToolResultEventBase {
|
|
160
|
+
toolName: "write";
|
|
161
|
+
details: undefined;
|
|
162
|
+
}
|
|
163
|
+
/** Tool result event for grep tool */
|
|
164
|
+
export interface GrepToolResultEvent extends ToolResultEventBase {
|
|
165
|
+
toolName: "grep";
|
|
166
|
+
details: GrepToolDetails | undefined;
|
|
167
|
+
}
|
|
168
|
+
/** Tool result event for find tool */
|
|
169
|
+
export interface FindToolResultEvent extends ToolResultEventBase {
|
|
170
|
+
toolName: "find";
|
|
171
|
+
details: FindToolDetails | undefined;
|
|
172
|
+
}
|
|
173
|
+
/** Tool result event for ls tool */
|
|
174
|
+
export interface LsToolResultEvent extends ToolResultEventBase {
|
|
175
|
+
toolName: "ls";
|
|
176
|
+
details: LsToolDetails | undefined;
|
|
177
|
+
}
|
|
178
|
+
/** Tool result event for custom/unknown tools */
|
|
179
|
+
export interface CustomToolResultEvent extends ToolResultEventBase {
|
|
180
|
+
toolName: string;
|
|
181
|
+
details: unknown;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Event data for tool_result event.
|
|
185
|
+
* Fired after a tool is executed. Hooks can modify the result.
|
|
186
|
+
* Use toolName to discriminate and get typed details.
|
|
187
|
+
*/
|
|
188
|
+
export type ToolResultEvent = BashToolResultEvent | ReadToolResultEvent | EditToolResultEvent | WriteToolResultEvent | GrepToolResultEvent | FindToolResultEvent | LsToolResultEvent | CustomToolResultEvent;
|
|
189
|
+
export declare function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent;
|
|
190
|
+
export declare function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent;
|
|
191
|
+
export declare function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent;
|
|
192
|
+
export declare function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent;
|
|
193
|
+
export declare function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent;
|
|
194
|
+
export declare function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent;
|
|
195
|
+
export declare function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent;
|
|
137
196
|
/**
|
|
138
197
|
* Event data for branch event.
|
|
139
198
|
*/
|
|
@@ -163,8 +222,10 @@ export interface ToolCallEventResult {
|
|
|
163
222
|
* Allows hooks to modify tool results.
|
|
164
223
|
*/
|
|
165
224
|
export interface ToolResultEventResult {
|
|
166
|
-
/**
|
|
167
|
-
|
|
225
|
+
/** Replacement content array (text and images) */
|
|
226
|
+
content?: (TextContent | ImageContent)[];
|
|
227
|
+
/** Replacement details */
|
|
228
|
+
details?: unknown;
|
|
168
229
|
/** Override isError flag */
|
|
169
230
|
isError?: boolean;
|
|
170
231
|
}
|
|
@@ -213,4 +274,5 @@ export interface HookError {
|
|
|
213
274
|
event: string;
|
|
214
275
|
error: string;
|
|
215
276
|
}
|
|
277
|
+
export {};
|
|
216
278
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AppMessage, Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { SessionEntry } from \"../session-manager.js\";\n\n// ============================================================================\n// Execution Context\n// ============================================================================\n\n/**\n * Result of executing a command via ctx.exec()\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n}\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | null>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or null if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | null>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n}\n\n/**\n * Context passed to hook event handlers.\n */\nexport interface HookEventContext {\n\t/** Execute a command and return stdout/stderr/code */\n\texec(command: string, args: string[]): Promise<ExecResult>;\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Path to session file, or null if --no-session */\n\tsessionFile: string | null;\n}\n\n// ============================================================================\n// Events\n// ============================================================================\n\n/**\n * Event data for session event.\n * Fired on startup and when session changes (switch or clear).\n * Note: branch has its own event that fires BEFORE the branch happens.\n */\nexport interface SessionEvent {\n\ttype: \"session\";\n\t/** All session entries (including pre-compaction history) */\n\tentries: SessionEntry[];\n\t/** Current session file path, or null in --no-session mode */\n\tsessionFile: string | null;\n\t/** Previous session file path, or null for \"start\" and \"clear\" */\n\tpreviousSessionFile: string | null;\n\t/** Reason for the session event */\n\treason: \"start\" | \"switch\" | \"clear\";\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AppMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AppMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n */\nexport interface ToolResultEvent {\n\ttype: \"tool_result\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Tool result content (text) */\n\tresult: string;\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/**\n * Event data for branch event.\n */\nexport interface BranchEvent {\n\ttype: \"branch\";\n\t/** Index of the turn to branch from */\n\ttargetTurnIndex: number;\n\t/** Full session history */\n\tentries: SessionEntry[];\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent\n\t| BranchEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Modified result text (if not set, original result is used) */\n\tresult?: string;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for branch event handlers.\n * Allows hooks to control branch behavior.\n */\nexport interface BranchEventResult {\n\t/** If true, skip restoring the conversation (only restore code) */\n\tskipConversationRestore?: boolean;\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n */\nexport type HookHandler<E, R = void> = (event: E, ctx: HookEventContext) => Promise<R>;\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.send() to inject messages.\n */\nexport interface HookAPI {\n\ton(event: \"session\", handler: HookHandler<SessionEvent>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult | undefined>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult | undefined>): void;\n\ton(event: \"branch\", handler: HookHandler<BranchEvent, BranchEventResult | undefined>): void;\n\n\t/**\n\t * Send a message to the agent.\n\t * If the agent is streaming, the message is queued.\n\t * If the agent is idle, a new agent loop is started.\n\t */\n\tsend(text: string, attachments?: Attachment[]): void;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AppMessage, Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, TextContent, ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { SessionEntry } from \"../session-manager.js\";\nimport type {\n\tBashToolDetails,\n\tFindToolDetails,\n\tGrepToolDetails,\n\tLsToolDetails,\n\tReadToolDetails,\n} from \"../tools/index.js\";\n\n// ============================================================================\n// Execution Context\n// ============================================================================\n\n/**\n * Result of executing a command via ctx.exec()\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n\t/** True if the process was killed due to signal or timeout */\n\tkilled?: boolean;\n}\n\nexport interface ExecOptions {\n\t/** AbortSignal to cancel the process */\n\tsignal?: AbortSignal;\n\t/** Timeout in milliseconds */\n\ttimeout?: number;\n}\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | null>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or null if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | null>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n}\n\n/**\n * Context passed to hook event handlers.\n */\nexport interface HookEventContext {\n\t/** Execute a command and return stdout/stderr/code */\n\texec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Path to session file, or null if --no-session */\n\tsessionFile: string | null;\n}\n\n// ============================================================================\n// Events\n// ============================================================================\n\n/**\n * Event data for session event.\n * Fired on startup and when session changes (switch or clear).\n * Note: branch has its own event that fires BEFORE the branch happens.\n */\nexport interface SessionEvent {\n\ttype: \"session\";\n\t/** All session entries (including pre-compaction history) */\n\tentries: SessionEntry[];\n\t/** Current session file path, or null in --no-session mode */\n\tsessionFile: string | null;\n\t/** Previous session file path, or null for \"start\" and \"clear\" */\n\tpreviousSessionFile: string | null;\n\t/** Reason for the session event */\n\treason: \"start\" | \"switch\" | \"clear\";\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AppMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AppMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Base interface for tool_result events.\n */\ninterface ToolResultEventBase {\n\ttype: \"tool_result\";\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Full content array (text and images) */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/** Tool result event for bash tool */\nexport interface BashToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"bash\";\n\tdetails: BashToolDetails | undefined;\n}\n\n/** Tool result event for read tool */\nexport interface ReadToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"read\";\n\tdetails: ReadToolDetails | undefined;\n}\n\n/** Tool result event for edit tool */\nexport interface EditToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"edit\";\n\tdetails: undefined;\n}\n\n/** Tool result event for write tool */\nexport interface WriteToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"write\";\n\tdetails: undefined;\n}\n\n/** Tool result event for grep tool */\nexport interface GrepToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"grep\";\n\tdetails: GrepToolDetails | undefined;\n}\n\n/** Tool result event for find tool */\nexport interface FindToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"find\";\n\tdetails: FindToolDetails | undefined;\n}\n\n/** Tool result event for ls tool */\nexport interface LsToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"ls\";\n\tdetails: LsToolDetails | undefined;\n}\n\n/** Tool result event for custom/unknown tools */\nexport interface CustomToolResultEvent extends ToolResultEventBase {\n\ttoolName: string;\n\tdetails: unknown;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n * Use toolName to discriminate and get typed details.\n */\nexport type ToolResultEvent =\n\t| BashToolResultEvent\n\t| ReadToolResultEvent\n\t| EditToolResultEvent\n\t| WriteToolResultEvent\n\t| GrepToolResultEvent\n\t| FindToolResultEvent\n\t| LsToolResultEvent\n\t| CustomToolResultEvent;\n\n// Type guards for narrowing ToolResultEvent to specific tool types\nexport function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {\n\treturn e.toolName === \"bash\";\n}\nexport function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {\n\treturn e.toolName === \"read\";\n}\nexport function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {\n\treturn e.toolName === \"edit\";\n}\nexport function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {\n\treturn e.toolName === \"write\";\n}\nexport function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {\n\treturn e.toolName === \"grep\";\n}\nexport function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {\n\treturn e.toolName === \"find\";\n}\nexport function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {\n\treturn e.toolName === \"ls\";\n}\n\n/**\n * Event data for branch event.\n */\nexport interface BranchEvent {\n\ttype: \"branch\";\n\t/** Index of the turn to branch from */\n\ttargetTurnIndex: number;\n\t/** Full session history */\n\tentries: SessionEntry[];\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent\n\t| BranchEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Replacement content array (text and images) */\n\tcontent?: (TextContent | ImageContent)[];\n\t/** Replacement details */\n\tdetails?: unknown;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for branch event handlers.\n * Allows hooks to control branch behavior.\n */\nexport interface BranchEventResult {\n\t/** If true, skip restoring the conversation (only restore code) */\n\tskipConversationRestore?: boolean;\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n */\nexport type HookHandler<E, R = void> = (event: E, ctx: HookEventContext) => Promise<R>;\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.send() to inject messages.\n */\nexport interface HookAPI {\n\ton(event: \"session\", handler: HookHandler<SessionEvent>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult | undefined>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult | undefined>): void;\n\ton(event: \"branch\", handler: HookHandler<BranchEvent, BranchEventResult | undefined>): void;\n\n\t/**\n\t * Send a message to the agent.\n\t * If the agent is streaming, the message is queued.\n\t * If the agent is idle, a new agent loop is started.\n\t */\n\tsend(text: string, attachments?: Attachment[]): void;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
|
package/dist/core/hooks/types.js
CHANGED
|
@@ -4,5 +4,26 @@
|
|
|
4
4
|
* Hooks are TypeScript modules that can subscribe to agent lifecycle events
|
|
5
5
|
* and interact with the user via UI primitives.
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
// Type guards for narrowing ToolResultEvent to specific tool types
|
|
8
|
+
export function isBashToolResult(e) {
|
|
9
|
+
return e.toolName === "bash";
|
|
10
|
+
}
|
|
11
|
+
export function isReadToolResult(e) {
|
|
12
|
+
return e.toolName === "read";
|
|
13
|
+
}
|
|
14
|
+
export function isEditToolResult(e) {
|
|
15
|
+
return e.toolName === "edit";
|
|
16
|
+
}
|
|
17
|
+
export function isWriteToolResult(e) {
|
|
18
|
+
return e.toolName === "write";
|
|
19
|
+
}
|
|
20
|
+
export function isGrepToolResult(e) {
|
|
21
|
+
return e.toolName === "grep";
|
|
22
|
+
}
|
|
23
|
+
export function isFindToolResult(e) {
|
|
24
|
+
return e.toolName === "find";
|
|
25
|
+
}
|
|
26
|
+
export function isLsToolResult(e) {
|
|
27
|
+
return e.toolName === "ls";
|
|
28
|
+
}
|
|
8
29
|
//# sourceMappingURL=types.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AppMessage, Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { SessionEntry } from \"../session-manager.js\";\n\n// ============================================================================\n// Execution Context\n// ============================================================================\n\n/**\n * Result of executing a command via ctx.exec()\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n}\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | null>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or null if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | null>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n}\n\n/**\n * Context passed to hook event handlers.\n */\nexport interface HookEventContext {\n\t/** Execute a command and return stdout/stderr/code */\n\texec(command: string, args: string[]): Promise<ExecResult>;\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Path to session file, or null if --no-session */\n\tsessionFile: string | null;\n}\n\n// ============================================================================\n// Events\n// ============================================================================\n\n/**\n * Event data for session event.\n * Fired on startup and when session changes (switch or clear).\n * Note: branch has its own event that fires BEFORE the branch happens.\n */\nexport interface SessionEvent {\n\ttype: \"session\";\n\t/** All session entries (including pre-compaction history) */\n\tentries: SessionEntry[];\n\t/** Current session file path, or null in --no-session mode */\n\tsessionFile: string | null;\n\t/** Previous session file path, or null for \"start\" and \"clear\" */\n\tpreviousSessionFile: string | null;\n\t/** Reason for the session event */\n\treason: \"start\" | \"switch\" | \"clear\";\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AppMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AppMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n */\nexport interface ToolResultEvent {\n\ttype: \"tool_result\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Tool result content (text) */\n\tresult: string;\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/**\n * Event data for branch event.\n */\nexport interface BranchEvent {\n\ttype: \"branch\";\n\t/** Index of the turn to branch from */\n\ttargetTurnIndex: number;\n\t/** Full session history */\n\tentries: SessionEntry[];\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent\n\t| BranchEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Modified result text (if not set, original result is used) */\n\tresult?: string;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for branch event handlers.\n * Allows hooks to control branch behavior.\n */\nexport interface BranchEventResult {\n\t/** If true, skip restoring the conversation (only restore code) */\n\tskipConversationRestore?: boolean;\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n */\nexport type HookHandler<E, R = void> = (event: E, ctx: HookEventContext) => Promise<R>;\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.send() to inject messages.\n */\nexport interface HookAPI {\n\ton(event: \"session\", handler: HookHandler<SessionEvent>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult | undefined>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult | undefined>): void;\n\ton(event: \"branch\", handler: HookHandler<BranchEvent, BranchEventResult | undefined>): void;\n\n\t/**\n\t * Send a message to the agent.\n\t * If the agent is streaming, the message is queued.\n\t * If the agent is idle, a new agent loop is started.\n\t */\n\tsend(text: string, attachments?: Attachment[]): void;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/hooks/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsOH,mEAAmE;AACnE,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,iBAAiB,CAAC,CAAkB,EAA6B;IAChF,OAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC;AAAA,CAC9B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,gBAAgB,CAAC,CAAkB,EAA4B;IAC9E,OAAO,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC;AAAA,CAC7B;AACD,MAAM,UAAU,cAAc,CAAC,CAAkB,EAA0B;IAC1E,OAAO,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC;AAAA,CAC3B","sourcesContent":["/**\n * Hook system types.\n *\n * Hooks are TypeScript modules that can subscribe to agent lifecycle events\n * and interact with the user via UI primitives.\n */\n\nimport type { AppMessage, Attachment } from \"@mariozechner/pi-agent-core\";\nimport type { ImageContent, TextContent, ToolResultMessage } from \"@mariozechner/pi-ai\";\nimport type { SessionEntry } from \"../session-manager.js\";\nimport type {\n\tBashToolDetails,\n\tFindToolDetails,\n\tGrepToolDetails,\n\tLsToolDetails,\n\tReadToolDetails,\n} from \"../tools/index.js\";\n\n// ============================================================================\n// Execution Context\n// ============================================================================\n\n/**\n * Result of executing a command via ctx.exec()\n */\nexport interface ExecResult {\n\tstdout: string;\n\tstderr: string;\n\tcode: number;\n\t/** True if the process was killed due to signal or timeout */\n\tkilled?: boolean;\n}\n\nexport interface ExecOptions {\n\t/** AbortSignal to cancel the process */\n\tsignal?: AbortSignal;\n\t/** Timeout in milliseconds */\n\ttimeout?: number;\n}\n\n/**\n * UI context for hooks to request interactive UI from the harness.\n * Each mode (interactive, RPC, print) provides its own implementation.\n */\nexport interface HookUIContext {\n\t/**\n\t * Show a selector and return the user's choice.\n\t * @param title - Title to display\n\t * @param options - Array of string options\n\t * @returns Selected option string, or null if cancelled\n\t */\n\tselect(title: string, options: string[]): Promise<string | null>;\n\n\t/**\n\t * Show a confirmation dialog.\n\t * @returns true if confirmed, false if cancelled\n\t */\n\tconfirm(title: string, message: string): Promise<boolean>;\n\n\t/**\n\t * Show a text input dialog.\n\t * @returns User input, or null if cancelled\n\t */\n\tinput(title: string, placeholder?: string): Promise<string | null>;\n\n\t/**\n\t * Show a notification to the user.\n\t */\n\tnotify(message: string, type?: \"info\" | \"warning\" | \"error\"): void;\n}\n\n/**\n * Context passed to hook event handlers.\n */\nexport interface HookEventContext {\n\t/** Execute a command and return stdout/stderr/code */\n\texec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;\n\t/** UI methods for user interaction */\n\tui: HookUIContext;\n\t/** Whether UI is available (false in print mode) */\n\thasUI: boolean;\n\t/** Current working directory */\n\tcwd: string;\n\t/** Path to session file, or null if --no-session */\n\tsessionFile: string | null;\n}\n\n// ============================================================================\n// Events\n// ============================================================================\n\n/**\n * Event data for session event.\n * Fired on startup and when session changes (switch or clear).\n * Note: branch has its own event that fires BEFORE the branch happens.\n */\nexport interface SessionEvent {\n\ttype: \"session\";\n\t/** All session entries (including pre-compaction history) */\n\tentries: SessionEntry[];\n\t/** Current session file path, or null in --no-session mode */\n\tsessionFile: string | null;\n\t/** Previous session file path, or null for \"start\" and \"clear\" */\n\tpreviousSessionFile: string | null;\n\t/** Reason for the session event */\n\treason: \"start\" | \"switch\" | \"clear\";\n}\n\n/**\n * Event data for agent_start event.\n * Fired when an agent loop starts (once per user prompt).\n */\nexport interface AgentStartEvent {\n\ttype: \"agent_start\";\n}\n\n/**\n * Event data for agent_end event.\n */\nexport interface AgentEndEvent {\n\ttype: \"agent_end\";\n\tmessages: AppMessage[];\n}\n\n/**\n * Event data for turn_start event.\n */\nexport interface TurnStartEvent {\n\ttype: \"turn_start\";\n\tturnIndex: number;\n\ttimestamp: number;\n}\n\n/**\n * Event data for turn_end event.\n */\nexport interface TurnEndEvent {\n\ttype: \"turn_end\";\n\tturnIndex: number;\n\tmessage: AppMessage;\n\ttoolResults: ToolResultMessage[];\n}\n\n/**\n * Event data for tool_call event.\n * Fired before a tool is executed. Hooks can block execution.\n */\nexport interface ToolCallEvent {\n\ttype: \"tool_call\";\n\t/** Tool name (e.g., \"bash\", \"edit\", \"write\") */\n\ttoolName: string;\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n}\n\n/**\n * Base interface for tool_result events.\n */\ninterface ToolResultEventBase {\n\ttype: \"tool_result\";\n\t/** Tool call ID */\n\ttoolCallId: string;\n\t/** Tool input parameters */\n\tinput: Record<string, unknown>;\n\t/** Full content array (text and images) */\n\tcontent: (TextContent | ImageContent)[];\n\t/** Whether the tool execution was an error */\n\tisError: boolean;\n}\n\n/** Tool result event for bash tool */\nexport interface BashToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"bash\";\n\tdetails: BashToolDetails | undefined;\n}\n\n/** Tool result event for read tool */\nexport interface ReadToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"read\";\n\tdetails: ReadToolDetails | undefined;\n}\n\n/** Tool result event for edit tool */\nexport interface EditToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"edit\";\n\tdetails: undefined;\n}\n\n/** Tool result event for write tool */\nexport interface WriteToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"write\";\n\tdetails: undefined;\n}\n\n/** Tool result event for grep tool */\nexport interface GrepToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"grep\";\n\tdetails: GrepToolDetails | undefined;\n}\n\n/** Tool result event for find tool */\nexport interface FindToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"find\";\n\tdetails: FindToolDetails | undefined;\n}\n\n/** Tool result event for ls tool */\nexport interface LsToolResultEvent extends ToolResultEventBase {\n\ttoolName: \"ls\";\n\tdetails: LsToolDetails | undefined;\n}\n\n/** Tool result event for custom/unknown tools */\nexport interface CustomToolResultEvent extends ToolResultEventBase {\n\ttoolName: string;\n\tdetails: unknown;\n}\n\n/**\n * Event data for tool_result event.\n * Fired after a tool is executed. Hooks can modify the result.\n * Use toolName to discriminate and get typed details.\n */\nexport type ToolResultEvent =\n\t| BashToolResultEvent\n\t| ReadToolResultEvent\n\t| EditToolResultEvent\n\t| WriteToolResultEvent\n\t| GrepToolResultEvent\n\t| FindToolResultEvent\n\t| LsToolResultEvent\n\t| CustomToolResultEvent;\n\n// Type guards for narrowing ToolResultEvent to specific tool types\nexport function isBashToolResult(e: ToolResultEvent): e is BashToolResultEvent {\n\treturn e.toolName === \"bash\";\n}\nexport function isReadToolResult(e: ToolResultEvent): e is ReadToolResultEvent {\n\treturn e.toolName === \"read\";\n}\nexport function isEditToolResult(e: ToolResultEvent): e is EditToolResultEvent {\n\treturn e.toolName === \"edit\";\n}\nexport function isWriteToolResult(e: ToolResultEvent): e is WriteToolResultEvent {\n\treturn e.toolName === \"write\";\n}\nexport function isGrepToolResult(e: ToolResultEvent): e is GrepToolResultEvent {\n\treturn e.toolName === \"grep\";\n}\nexport function isFindToolResult(e: ToolResultEvent): e is FindToolResultEvent {\n\treturn e.toolName === \"find\";\n}\nexport function isLsToolResult(e: ToolResultEvent): e is LsToolResultEvent {\n\treturn e.toolName === \"ls\";\n}\n\n/**\n * Event data for branch event.\n */\nexport interface BranchEvent {\n\ttype: \"branch\";\n\t/** Index of the turn to branch from */\n\ttargetTurnIndex: number;\n\t/** Full session history */\n\tentries: SessionEntry[];\n}\n\n/**\n * Union of all hook event types.\n */\nexport type HookEvent =\n\t| SessionEvent\n\t| AgentStartEvent\n\t| AgentEndEvent\n\t| TurnStartEvent\n\t| TurnEndEvent\n\t| ToolCallEvent\n\t| ToolResultEvent\n\t| BranchEvent;\n\n// ============================================================================\n// Event Results\n// ============================================================================\n\n/**\n * Return type for tool_call event handlers.\n * Allows hooks to block tool execution.\n */\nexport interface ToolCallEventResult {\n\t/** If true, block the tool from executing */\n\tblock?: boolean;\n\t/** Reason for blocking (returned to LLM as error) */\n\treason?: string;\n}\n\n/**\n * Return type for tool_result event handlers.\n * Allows hooks to modify tool results.\n */\nexport interface ToolResultEventResult {\n\t/** Replacement content array (text and images) */\n\tcontent?: (TextContent | ImageContent)[];\n\t/** Replacement details */\n\tdetails?: unknown;\n\t/** Override isError flag */\n\tisError?: boolean;\n}\n\n/**\n * Return type for branch event handlers.\n * Allows hooks to control branch behavior.\n */\nexport interface BranchEventResult {\n\t/** If true, skip restoring the conversation (only restore code) */\n\tskipConversationRestore?: boolean;\n}\n\n// ============================================================================\n// Hook API\n// ============================================================================\n\n/**\n * Handler function type for each event.\n */\nexport type HookHandler<E, R = void> = (event: E, ctx: HookEventContext) => Promise<R>;\n\n/**\n * HookAPI passed to hook factory functions.\n * Hooks use pi.on() to subscribe to events and pi.send() to inject messages.\n */\nexport interface HookAPI {\n\ton(event: \"session\", handler: HookHandler<SessionEvent>): void;\n\ton(event: \"agent_start\", handler: HookHandler<AgentStartEvent>): void;\n\ton(event: \"agent_end\", handler: HookHandler<AgentEndEvent>): void;\n\ton(event: \"turn_start\", handler: HookHandler<TurnStartEvent>): void;\n\ton(event: \"turn_end\", handler: HookHandler<TurnEndEvent>): void;\n\ton(event: \"tool_call\", handler: HookHandler<ToolCallEvent, ToolCallEventResult | undefined>): void;\n\ton(event: \"tool_result\", handler: HookHandler<ToolResultEvent, ToolResultEventResult | undefined>): void;\n\ton(event: \"branch\", handler: HookHandler<BranchEvent, BranchEventResult | undefined>): void;\n\n\t/**\n\t * Send a message to the agent.\n\t * If the agent is streaming, the message is queued.\n\t * If the agent is idle, a new agent loop is started.\n\t */\n\tsend(text: string, attachments?: Attachment[]): void;\n}\n\n/**\n * Hook factory function type.\n * Hooks export a default function that receives the HookAPI.\n */\nexport type HookFactory = (pi: HookAPI) => void;\n\n// ============================================================================\n// Errors\n// ============================================================================\n\n/**\n * Error emitted when a hook fails.\n */\nexport interface HookError {\n\thookPath: string;\n\tevent: string;\n\terror: string;\n}\n"]}
|
package/dist/core/skills.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface SkillFrontmatter {
|
|
2
2
|
name?: string;
|
|
3
|
-
description
|
|
3
|
+
description?: string;
|
|
4
|
+
[key: string]: unknown;
|
|
4
5
|
}
|
|
5
6
|
export interface Skill {
|
|
6
7
|
name: string;
|
|
@@ -9,22 +10,34 @@ export interface Skill {
|
|
|
9
10
|
baseDir: string;
|
|
10
11
|
source: string;
|
|
11
12
|
}
|
|
13
|
+
export interface SkillWarning {
|
|
14
|
+
skillPath: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
export interface LoadSkillsResult {
|
|
18
|
+
skills: Skill[];
|
|
19
|
+
warnings: SkillWarning[];
|
|
20
|
+
}
|
|
12
21
|
export interface LoadSkillsFromDirOptions {
|
|
13
22
|
/** Directory to scan for skills */
|
|
14
23
|
dir: string;
|
|
15
24
|
/** Source identifier for these skills */
|
|
16
25
|
source: string;
|
|
17
|
-
/** Use colon-separated path names (e.g., db:migrate) instead of simple directory name */
|
|
18
|
-
useColonPath?: boolean;
|
|
19
26
|
}
|
|
20
27
|
/**
|
|
21
28
|
* Load skills from a directory recursively.
|
|
22
29
|
* Skills are directories containing a SKILL.md file with frontmatter including a description.
|
|
23
30
|
*/
|
|
24
|
-
export declare function loadSkillsFromDir(options: LoadSkillsFromDirOptions
|
|
31
|
+
export declare function loadSkillsFromDir(options: LoadSkillsFromDirOptions): LoadSkillsResult;
|
|
25
32
|
/**
|
|
26
33
|
* Format skills for inclusion in a system prompt.
|
|
34
|
+
* Uses XML format per Agent Skills standard.
|
|
35
|
+
* See: https://agentskills.io/integrate-skills
|
|
27
36
|
*/
|
|
28
37
|
export declare function formatSkillsForPrompt(skills: Skill[]): string;
|
|
29
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Load skills from all configured locations.
|
|
40
|
+
* Returns skills and any validation warnings.
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadSkills(): LoadSkillsResult;
|
|
30
43
|
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf;AA4CD,MAAM,WAAW,wBAAwB;IACxC,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,EAAE,MAAM,GAAE,MAAW,GAAG,KAAK,EAAE,CAGjG;AA+FD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAqB7D;AAED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAgCpC","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription: string;\n}\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: string;\n}\n\ntype SkillFormat = \"recursive\" | \"claude\";\n\nfunction stripQuotes(value: string): string {\n\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string } {\n\tconst frontmatter: SkillFrontmatter = { description: \"\" };\n\n\tconst normalizedContent = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\tif (!normalizedContent.startsWith(\"---\")) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst endIndex = normalizedContent.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, body: normalizedContent };\n\t}\n\n\tconst frontmatterBlock = normalizedContent.slice(4, endIndex);\n\tconst body = normalizedContent.slice(endIndex + 4).trim();\n\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tconst key = match[1];\n\t\t\tconst value = stripQuotes(match[2].trim());\n\t\t\tif (key === \"name\") {\n\t\t\t\tfrontmatter.name = value;\n\t\t\t} else if (key === \"description\") {\n\t\t\t\tfrontmatter.description = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { frontmatter, body };\n}\n\nexport interface LoadSkillsFromDirOptions {\n\t/** Directory to scan for skills */\n\tdir: string;\n\t/** Source identifier for these skills */\n\tsource: string;\n\t/** Use colon-separated path names (e.g., db:migrate) instead of simple directory name */\n\tuseColonPath?: boolean;\n}\n\n/**\n * Load skills from a directory recursively.\n * Skills are directories containing a SKILL.md file with frontmatter including a description.\n */\nexport function loadSkillsFromDir(options: LoadSkillsFromDirOptions, subdir: string = \"\"): Skill[] {\n\tconst { dir, source, useColonPath = false } = options;\n\treturn loadSkillsFromDirInternal(dir, source, \"recursive\", useColonPath, subdir);\n}\n\nfunction loadSkillsFromDirInternal(\n\tdir: string,\n\tsource: string,\n\tformat: SkillFormat,\n\tuseColonPath: boolean = false,\n\tsubdir: string = \"\",\n): Skill[] {\n\tconst skills: Skill[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn skills;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (format === \"recursive\") {\n\t\t\t\t// Recursive format: scan directories, look for SKILL.md files\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\t\tskills.push(...loadSkillsFromDirInternal(fullPath, source, format, useColonPath, newSubdir));\n\t\t\t\t} else if (entry.isFile() && entry.name === \"SKILL.md\") {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst skillDir = dirname(fullPath);\n\t\t\t\t\t\t// useColonPath: db:migrate (pi), otherwise just: migrate (codex)\n\t\t\t\t\t\tconst nameFromPath = useColonPath ? subdir || basename(skillDir) : basename(skillDir);\n\t\t\t\t\t\tconst name = frontmatter.name || nameFromPath;\n\n\t\t\t\t\t\tskills.push({\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\t\tfilePath: fullPath,\n\t\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\t\tsource,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t} else if (format === \"claude\") {\n\t\t\t\t// Claude format: only one level deep, each directory must contain SKILL.md\n\t\t\t\tif (!entry.isDirectory()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst skillDir = fullPath;\n\t\t\t\tconst skillFile = join(skillDir, \"SKILL.md\");\n\n\t\t\t\tif (!existsSync(skillFile)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(skillFile, \"utf-8\");\n\t\t\t\t\tconst { frontmatter } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tif (!frontmatter.description) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst name = frontmatter.name || entry.name;\n\n\t\t\t\t\tskills.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription: frontmatter.description,\n\t\t\t\t\t\tfilePath: skillFile,\n\t\t\t\t\t\tbaseDir: skillDir,\n\t\t\t\t\t\tsource,\n\t\t\t\t\t});\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\t} catch {}\n\n\treturn skills;\n}\n\n/**\n * Format skills for inclusion in a system prompt.\n */\nexport function formatSkillsForPrompt(skills: Skill[]): string {\n\tif (skills.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\n<available_skills>\",\n\t\t\"The following skills provide specialized instructions for specific tasks.\",\n\t\t\"Use the read tool to load a skill's file when the task matches its description.\",\n\t\t\"Skills may contain {baseDir} placeholders - replace them with the skill's base directory path.\\n\",\n\t];\n\n\tfor (const skill of skills) {\n\t\tlines.push(`- ${skill.name}: ${skill.description}`);\n\t\tlines.push(` File: ${skill.filePath}`);\n\t\tlines.push(` Base directory: ${skill.baseDir}`);\n\t}\n\n\tlines.push(\"</available_skills>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function loadSkills(): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\n\t// Codex: recursive, simple directory name\n\tconst codexUserDir = join(homedir(), \".codex\", \"skills\");\n\tfor (const skill of loadSkillsFromDirInternal(codexUserDir, \"codex-user\", \"recursive\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Claude: single level only\n\tconst claudeUserDir = join(homedir(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDirInternal(claudeUserDir, \"claude-user\", \"claude\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst claudeProjectDir = resolve(process.cwd(), \".claude\", \"skills\");\n\tfor (const skill of loadSkillsFromDirInternal(claudeProjectDir, \"claude-project\", \"claude\", false)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Pi: recursive, colon-separated path names\n\tconst globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, \"agent\", \"skills\");\n\tfor (const skill of loadSkillsFromDirInternal(globalSkillsDir, \"user\", \"recursive\", true)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\tconst projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"skills\");\n\tfor (const skill of loadSkillsFromDirInternal(projectSkillsDir, \"project\", \"recursive\", true)) {\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n"]}
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/core/skills.ts"],"names":[],"mappings":"AAwBA,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAChC,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,YAAY,EAAE,CAAC;CACzB;AAwGD,MAAM,WAAW,wBAAwB;IACxC,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,gBAAgB,CAGrF;AA8GD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAuB7D;AAWD;;;GAGG;AACH,wBAAgB,UAAU,IAAI,gBAAgB,CA0C7C","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { homedir } from \"os\";\nimport { basename, dirname, join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.js\";\n\n/**\n * Standard frontmatter fields per Agent Skills spec.\n * See: https://agentskills.io/specification#frontmatter-required\n */\nconst ALLOWED_FRONTMATTER_FIELDS = new Set([\n\t\"name\",\n\t\"description\",\n\t\"license\",\n\t\"compatibility\",\n\t\"metadata\",\n\t\"allowed-tools\",\n]);\n\n/** Max name length per spec */\nconst MAX_NAME_LENGTH = 64;\n\n/** Max description length per spec */\nconst MAX_DESCRIPTION_LENGTH = 1024;\n\nexport interface SkillFrontmatter {\n\tname?: string;\n\tdescription?: string;\n\t[key: string]: unknown;\n}\n\nexport interface Skill {\n\tname: string;\n\tdescription: string;\n\tfilePath: string;\n\tbaseDir: string;\n\tsource: string;\n}\n\nexport interface SkillWarning {\n\tskillPath: string;\n\tmessage: string;\n}\n\nexport interface LoadSkillsResult {\n\tskills: Skill[];\n\twarnings: SkillWarning[];\n}\n\ntype SkillFormat = \"recursive\" | \"claude\";\n\nfunction stripQuotes(value: string): string {\n\tif ((value.startsWith('\"') && value.endsWith('\"')) || (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n\t\treturn value.slice(1, -1);\n\t}\n\treturn value;\n}\n\nfunction parseFrontmatter(content: string): { frontmatter: SkillFrontmatter; body: string; allKeys: string[] } {\n\tconst frontmatter: SkillFrontmatter = {};\n\tconst allKeys: string[] = [];\n\n\tconst normalizedContent = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n\n\tif (!normalizedContent.startsWith(\"---\")) {\n\t\treturn { frontmatter, body: normalizedContent, allKeys };\n\t}\n\n\tconst endIndex = normalizedContent.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, body: normalizedContent, allKeys };\n\t}\n\n\tconst frontmatterBlock = normalizedContent.slice(4, endIndex);\n\tconst body = normalizedContent.slice(endIndex + 4).trim();\n\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w[\\w-]*):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tconst key = match[1];\n\t\t\tconst value = stripQuotes(match[2].trim());\n\t\t\tallKeys.push(key);\n\t\t\tif (key === \"name\") {\n\t\t\t\tfrontmatter.name = value;\n\t\t\t} else if (key === \"description\") {\n\t\t\t\tfrontmatter.description = value;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { frontmatter, body, allKeys };\n}\n\n/**\n * Validate skill name per Agent Skills spec.\n * Returns array of validation error messages (empty if valid).\n */\nfunction validateName(name: string, parentDirName: string): string[] {\n\tconst errors: string[] = [];\n\n\tif (name !== parentDirName) {\n\t\terrors.push(`name \"${name}\" does not match parent directory \"${parentDirName}\"`);\n\t}\n\n\tif (name.length > MAX_NAME_LENGTH) {\n\t\terrors.push(`name exceeds ${MAX_NAME_LENGTH} characters (${name.length})`);\n\t}\n\n\tif (!/^[a-z0-9-]+$/.test(name)) {\n\t\terrors.push(`name contains invalid characters (must be lowercase a-z, 0-9, hyphens only)`);\n\t}\n\n\tif (name.startsWith(\"-\") || name.endsWith(\"-\")) {\n\t\terrors.push(`name must not start or end with a hyphen`);\n\t}\n\n\tif (name.includes(\"--\")) {\n\t\terrors.push(`name must not contain consecutive hyphens`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Validate description per Agent Skills spec.\n */\nfunction validateDescription(description: string | undefined): string[] {\n\tconst errors: string[] = [];\n\n\tif (!description || description.trim() === \"\") {\n\t\terrors.push(`description is required`);\n\t} else if (description.length > MAX_DESCRIPTION_LENGTH) {\n\t\terrors.push(`description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${description.length})`);\n\t}\n\n\treturn errors;\n}\n\n/**\n * Check for unknown frontmatter fields.\n */\nfunction validateFrontmatterFields(keys: string[]): string[] {\n\tconst errors: string[] = [];\n\tfor (const key of keys) {\n\t\tif (!ALLOWED_FRONTMATTER_FIELDS.has(key)) {\n\t\t\terrors.push(`unknown frontmatter field \"${key}\"`);\n\t\t}\n\t}\n\treturn errors;\n}\n\nexport interface LoadSkillsFromDirOptions {\n\t/** Directory to scan for skills */\n\tdir: string;\n\t/** Source identifier for these skills */\n\tsource: string;\n}\n\n/**\n * Load skills from a directory recursively.\n * Skills are directories containing a SKILL.md file with frontmatter including a description.\n */\nexport function loadSkillsFromDir(options: LoadSkillsFromDirOptions): LoadSkillsResult {\n\tconst { dir, source } = options;\n\treturn loadSkillsFromDirInternal(dir, source, \"recursive\");\n}\n\nfunction loadSkillsFromDirInternal(dir: string, source: string, format: SkillFormat): LoadSkillsResult {\n\tconst skills: Skill[] = [];\n\tconst warnings: SkillWarning[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn { skills, warnings };\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.name.startsWith(\".\")) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (format === \"recursive\") {\n\t\t\t\t// Recursive format: scan directories, look for SKILL.md files\n\t\t\t\tif (entry.isDirectory()) {\n\t\t\t\t\tconst subResult = loadSkillsFromDirInternal(fullPath, source, format);\n\t\t\t\t\tskills.push(...subResult.skills);\n\t\t\t\t\twarnings.push(...subResult.warnings);\n\t\t\t\t} else if (entry.isFile() && entry.name === \"SKILL.md\") {\n\t\t\t\t\tconst result = loadSkillFromFile(fullPath, source);\n\t\t\t\t\tif (result.skill) {\n\t\t\t\t\t\tskills.push(result.skill);\n\t\t\t\t\t}\n\t\t\t\t\twarnings.push(...result.warnings);\n\t\t\t\t}\n\t\t\t} else if (format === \"claude\") {\n\t\t\t\t// Claude format: only one level deep, each directory must contain SKILL.md\n\t\t\t\tif (!entry.isDirectory()) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst skillFile = join(fullPath, \"SKILL.md\");\n\t\t\t\tif (!existsSync(skillFile)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst result = loadSkillFromFile(skillFile, source);\n\t\t\t\tif (result.skill) {\n\t\t\t\t\tskills.push(result.skill);\n\t\t\t\t}\n\t\t\t\twarnings.push(...result.warnings);\n\t\t\t}\n\t\t}\n\t} catch {}\n\n\treturn { skills, warnings };\n}\n\nfunction loadSkillFromFile(filePath: string, source: string): { skill: Skill | null; warnings: SkillWarning[] } {\n\tconst warnings: SkillWarning[] = [];\n\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, allKeys } = parseFrontmatter(rawContent);\n\t\tconst skillDir = dirname(filePath);\n\t\tconst parentDirName = basename(skillDir);\n\n\t\t// Validate frontmatter fields\n\t\tconst fieldErrors = validateFrontmatterFields(allKeys);\n\t\tfor (const error of fieldErrors) {\n\t\t\twarnings.push({ skillPath: filePath, message: error });\n\t\t}\n\n\t\t// Validate description\n\t\tconst descErrors = validateDescription(frontmatter.description);\n\t\tfor (const error of descErrors) {\n\t\t\twarnings.push({ skillPath: filePath, message: error });\n\t\t}\n\n\t\t// Use name from frontmatter, or fall back to parent directory name\n\t\tconst name = frontmatter.name || parentDirName;\n\n\t\t// Validate name\n\t\tconst nameErrors = validateName(name, parentDirName);\n\t\tfor (const error of nameErrors) {\n\t\t\twarnings.push({ skillPath: filePath, message: error });\n\t\t}\n\n\t\t// Still load the skill even with warnings (unless description is completely missing)\n\t\tif (!frontmatter.description || frontmatter.description.trim() === \"\") {\n\t\t\treturn { skill: null, warnings };\n\t\t}\n\n\t\treturn {\n\t\t\tskill: {\n\t\t\t\tname,\n\t\t\t\tdescription: frontmatter.description,\n\t\t\t\tfilePath,\n\t\t\t\tbaseDir: skillDir,\n\t\t\t\tsource,\n\t\t\t},\n\t\t\twarnings,\n\t\t};\n\t} catch {\n\t\treturn { skill: null, warnings };\n\t}\n}\n\n/**\n * Format skills for inclusion in a system prompt.\n * Uses XML format per Agent Skills standard.\n * See: https://agentskills.io/integrate-skills\n */\nexport function formatSkillsForPrompt(skills: Skill[]): string {\n\tif (skills.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines = [\n\t\t\"\\n\\nThe following skills provide specialized instructions for specific tasks.\",\n\t\t\"Use the read tool to load a skill's file when the task matches its description.\",\n\t\t\"\",\n\t\t\"<available_skills>\",\n\t];\n\n\tfor (const skill of skills) {\n\t\tlines.push(\" <skill>\");\n\t\tlines.push(` <name>${escapeXml(skill.name)}</name>`);\n\t\tlines.push(` <description>${escapeXml(skill.description)}</description>`);\n\t\tlines.push(` <location>${escapeXml(skill.filePath)}</location>`);\n\t\tlines.push(\" </skill>\");\n\t}\n\n\tlines.push(\"</available_skills>\");\n\n\treturn lines.join(\"\\n\");\n}\n\nfunction escapeXml(str: string): string {\n\treturn str\n\t\t.replace(/&/g, \"&\")\n\t\t.replace(/</g, \"<\")\n\t\t.replace(/>/g, \">\")\n\t\t.replace(/\"/g, \""\")\n\t\t.replace(/'/g, \"'\");\n}\n\n/**\n * Load skills from all configured locations.\n * Returns skills and any validation warnings.\n */\nexport function loadSkills(): LoadSkillsResult {\n\tconst skillMap = new Map<string, Skill>();\n\tconst allWarnings: SkillWarning[] = [];\n\tconst collisionWarnings: SkillWarning[] = [];\n\n\tfunction addSkills(result: LoadSkillsResult) {\n\t\tallWarnings.push(...result.warnings);\n\t\tfor (const skill of result.skills) {\n\t\t\tconst existing = skillMap.get(skill.name);\n\t\t\tif (existing) {\n\t\t\t\tcollisionWarnings.push({\n\t\t\t\t\tskillPath: skill.filePath,\n\t\t\t\t\tmessage: `name collision: \"${skill.name}\" already loaded from ${existing.filePath}, skipping this one`,\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tskillMap.set(skill.name, skill);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Codex: recursive\n\tconst codexUserDir = join(homedir(), \".codex\", \"skills\");\n\taddSkills(loadSkillsFromDirInternal(codexUserDir, \"codex-user\", \"recursive\"));\n\n\t// Claude: single level only\n\tconst claudeUserDir = join(homedir(), \".claude\", \"skills\");\n\taddSkills(loadSkillsFromDirInternal(claudeUserDir, \"claude-user\", \"claude\"));\n\n\tconst claudeProjectDir = resolve(process.cwd(), \".claude\", \"skills\");\n\taddSkills(loadSkillsFromDirInternal(claudeProjectDir, \"claude-project\", \"claude\"));\n\n\t// Pi: recursive\n\tconst globalSkillsDir = join(homedir(), CONFIG_DIR_NAME, \"agent\", \"skills\");\n\taddSkills(loadSkillsFromDirInternal(globalSkillsDir, \"user\", \"recursive\"));\n\n\tconst projectSkillsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"skills\");\n\taddSkills(loadSkillsFromDirInternal(projectSkillsDir, \"project\", \"recursive\"));\n\n\treturn {\n\t\tskills: Array.from(skillMap.values()),\n\t\twarnings: [...allWarnings, ...collisionWarnings],\n\t};\n}\n"]}
|