feat(coding-agent): enabled docs:// protocol and custom tool discovery from .omp/tools/
- Added docs:// internal URL protocol handler for accessing embedded markdown documentation files with path traversal validation. - Added support for executable tool files (.ts, .js, .sh, .bash, .py) in custom tools discovery from .omp/tools/ and .claude/tools/ directories. - Added generate-docs-index build script to automatically scan and embed markdown documentation files at build time. - Updated system prompt to document docs:// protocol usage and identify agent as operating within Oh My Pi harness.
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
"!**/node_modules/**/*",
|
||||
"!**/test-sessions.ts",
|
||||
"!**/template.generated.ts",
|
||||
"!**/docs-index.generated.ts",
|
||||
"!**/gen/agent_pb.ts",
|
||||
"!.worktrees/**/*"
|
||||
]
|
||||
|
||||
+3
-2
@@ -20,7 +20,7 @@
|
||||
"fmt:ts": "biome format --write .",
|
||||
"fmt:rs": "cargo fmt --all",
|
||||
"fix": "bun run fix:ts && bun run fix:rs",
|
||||
"fix:ts": "biome check --write --unsafe . && bun --cwd=packages/coding-agent run format-prompts",
|
||||
"fix:ts": "biome check --write --unsafe . && bun --cwd=packages/coding-agent run format-prompts && bun --cwd=packages/coding-agent run generate-docs-index",
|
||||
"fix:rs": "cargo clippy --fix --allow-dirty --all-targets --no-deps --allow-staged --broken-code --allow-no-vcs && cargo fmt --all",
|
||||
"build:native": "bun --cwd=packages/natives run build:native",
|
||||
"dev:native": "bun --cwd=packages/natives run dev:native",
|
||||
@@ -34,7 +34,8 @@
|
||||
"publish": "bun run prepublishOnly && npm publish -ws --access public",
|
||||
"publish:dry": "bun run prepublishOnly && npm publish -ws --access public --dry-run",
|
||||
"release": "bun scripts/release.ts",
|
||||
"generate-models": "bun --cwd=packages/ai scripts/generate-models.ts"
|
||||
"generate-models": "bun --cwd=packages/ai scripts/generate-models.ts",
|
||||
"generate-docs-index": "bun --cwd=packages/coding-agent run generate-docs-index",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.2",
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Support for `docs://` internal URL protocol to access embedded documentation files (e.g., `docs://sdk.md`)
|
||||
- Added `generate-docs-index` npm script to automatically index and embed documentation files at build time
|
||||
- Support for executable tool files (.ts, .js, .sh, .bash, .py) in custom tools discovery alongside markdown files
|
||||
- Display streamed tool intent in working message during agent execution
|
||||
- Added `tools.intentTracing` setting to enable intent tracing, which asks the agent to describe the intent of each tool call before executing it
|
||||
- Support for file deletion in hashline edit mode via `delete: true` parameter
|
||||
@@ -12,6 +16,9 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- System prompt now identifies agent as operating inside Oh My Pi harness and instructs reading docs:// URLs for omp/pi topics
|
||||
- Tool discovery now accepts executable script extensions (.ts, .js, .sh, .bash, .py) in addition to .json and .md files
|
||||
- Updated bash and read tool documentation to reference `docs://` URL support
|
||||
- Hashline format separator changed from pipe (`|`) to colon (`:`) for improved readability (e.g., `LINE#ID:content` instead of `LINE#ID|content`)
|
||||
- Hashline hash representation changed from 4-character base36 to 2-character hexadecimal for more compact line references
|
||||
- Hashline edit API: renamed `delete` parameter to `rm` for consistency with standard file operations
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
"scripts": {
|
||||
"check": "tsgo -p tsconfig.json",
|
||||
"format-prompts": "bun scripts/format-prompts.ts",
|
||||
"generate-docs-index": "bun scripts/generate-docs-index.ts",
|
||||
"build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
|
||||
"generate-template": "bun scripts/generate-template.ts",
|
||||
"test": "bun test"
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import { Glob } from "bun";
|
||||
import * as path from "node:path";
|
||||
|
||||
const docsDir = new URL("../../../docs/", import.meta.url).pathname;
|
||||
const outputPath = new URL("../src/internal-urls/docs-index.generated.ts", import.meta.url).pathname;
|
||||
const importBase = "../../../../docs";
|
||||
|
||||
function toIdentifier(relativePath: string): string {
|
||||
const withoutExt = relativePath.replace(/\.md$/i, "");
|
||||
const parts = withoutExt
|
||||
.split(/[^a-zA-Z0-9]+/)
|
||||
.filter(Boolean)
|
||||
.map((part, index) => {
|
||||
if (index === 0) {
|
||||
return part.toLowerCase();
|
||||
}
|
||||
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
|
||||
});
|
||||
|
||||
const base = parts.length > 0 ? parts.join("") : "doc";
|
||||
const safeBase = /^[0-9]/.test(base) ? `doc${base}` : base;
|
||||
return `${safeBase}Md`;
|
||||
}
|
||||
|
||||
const glob = new Glob("**/*.md");
|
||||
const entries: string[] = [];
|
||||
for await (const relativePath of glob.scan(docsDir)) {
|
||||
entries.push(relativePath.split(path.sep).join("/"));
|
||||
}
|
||||
entries.sort();
|
||||
|
||||
const usedIdentifiers = new Set<string>();
|
||||
const docs = entries.map((relativePath) => {
|
||||
let identifier = toIdentifier(relativePath);
|
||||
let suffix = 2;
|
||||
while (usedIdentifiers.has(identifier)) {
|
||||
identifier = `${toIdentifier(relativePath)}${suffix}`;
|
||||
suffix++;
|
||||
}
|
||||
usedIdentifiers.add(identifier);
|
||||
return { relativePath, identifier };
|
||||
});
|
||||
docs.sort((a, b) => a.identifier.localeCompare(b.identifier) || a.relativePath.localeCompare(b.relativePath));
|
||||
|
||||
const imports = docs
|
||||
.map(({ relativePath, identifier }) => `import ${identifier} from "${importBase}/${relativePath}" with { type: "text" };`)
|
||||
.join("\n");
|
||||
|
||||
const mapEntries = docs.map(({ relativePath, identifier }) => `\t"${relativePath}": ${identifier},`).join("\n");
|
||||
|
||||
const output = `// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT\n\n${imports}\n\nexport const EMBEDDED_DOCS: Readonly<Record<string, string>> = {\n${mapEntries}\n};\n\nexport const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();\n`;
|
||||
|
||||
await Bun.write(outputPath, output);
|
||||
console.log(`Generated ${path.relative(process.cwd(), outputPath)} (${docs.length} docs)`);
|
||||
@@ -656,7 +656,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
||||
|
||||
fileLoadPromises.push(
|
||||
loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, level, {
|
||||
extensions: ["json", "md"],
|
||||
extensions: ["json", "md", "ts", "js", "sh", "bash", "py"],
|
||||
transform: (name, content, path, source) => {
|
||||
if (name.endsWith(".json")) {
|
||||
const data = parseJSON<{ name?: string; description?: string }>(content);
|
||||
@@ -668,11 +668,21 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
||||
_source: source,
|
||||
};
|
||||
}
|
||||
const { frontmatter } = parseFrontmatter(content, { source: path });
|
||||
if (name.endsWith(".md")) {
|
||||
const { frontmatter } = parseFrontmatter(content, { source: path });
|
||||
return {
|
||||
name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
|
||||
path,
|
||||
description: frontmatter.description as string | undefined,
|
||||
level,
|
||||
_source: source,
|
||||
};
|
||||
}
|
||||
// Executable tool files (.ts, .js, .sh, .bash, .py)
|
||||
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
||||
return {
|
||||
name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
|
||||
name: toolName,
|
||||
path,
|
||||
description: frontmatter.description as string | undefined,
|
||||
level,
|
||||
_source: source,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT
|
||||
|
||||
import bashToolRuntimeMd from "../../../../docs/bash-tool-runtime.md" with { type: "text" };
|
||||
import blobArtifactArchitectureMd from "../../../../docs/blob-artifact-architecture.md" with { type: "text" };
|
||||
import compactionMd from "../../../../docs/compaction.md" with { type: "text" };
|
||||
import configUsageMd from "../../../../docs/config-usage.md" with { type: "text" };
|
||||
import customToolsMd from "../../../../docs/custom-tools.md" with { type: "text" };
|
||||
import environmentVariablesMd from "../../../../docs/environment-variables.md" with { type: "text" };
|
||||
import extensionLoadingMd from "../../../../docs/extension-loading.md" with { type: "text" };
|
||||
import extensionsMd from "../../../../docs/extensions.md" with { type: "text" };
|
||||
import fsScanCacheArchitectureMd from "../../../../docs/fs-scan-cache-architecture.md" with { type: "text" };
|
||||
import geminiManifestExtensionsMd from "../../../../docs/gemini-manifest-extensions.md" with { type: "text" };
|
||||
import handoffGenerationPipelineMd from "../../../../docs/handoff-generation-pipeline.md" with { type: "text" };
|
||||
import hooksMd from "../../../../docs/hooks.md" with { type: "text" };
|
||||
import mcpProtocolTransportsMd from "../../../../docs/mcp-protocol-transports.md" with { type: "text" };
|
||||
import mcpRuntimeLifecycleMd from "../../../../docs/mcp-runtime-lifecycle.md" with { type: "text" };
|
||||
import mcpServerToolAuthoringMd from "../../../../docs/mcp-server-tool-authoring.md" with { type: "text" };
|
||||
import modelsMd from "../../../../docs/models.md" with { type: "text" };
|
||||
import nativesAddonLoaderRuntimeMd from "../../../../docs/natives-addon-loader-runtime.md" with { type: "text" };
|
||||
import nativesArchitectureMd from "../../../../docs/natives-architecture.md" with { type: "text" };
|
||||
import nativesBindingContractMd from "../../../../docs/natives-binding-contract.md" with { type: "text" };
|
||||
import nativesBuildReleaseDebuggingMd from "../../../../docs/natives-build-release-debugging.md" with { type: "text" };
|
||||
import nativesMediaSystemUtilsMd from "../../../../docs/natives-media-system-utils.md" with { type: "text" };
|
||||
import nativesRustTaskCancellationMd from "../../../../docs/natives-rust-task-cancellation.md" with { type: "text" };
|
||||
import nativesShellPtyProcessMd from "../../../../docs/natives-shell-pty-process.md" with { type: "text" };
|
||||
import nativesTextSearchPipelineMd from "../../../../docs/natives-text-search-pipeline.md" with { type: "text" };
|
||||
import nonCompactionRetryPolicyMd from "../../../../docs/non-compaction-retry-policy.md" with { type: "text" };
|
||||
import notebookToolRuntimeMd from "../../../../docs/notebook-tool-runtime.md" with { type: "text" };
|
||||
import pluginManagerInstallerPlumbingMd from "../../../../docs/plugin-manager-installer-plumbing.md" with { type: "text" };
|
||||
import portingFromPiMonoMd from "../../../../docs/porting-from-pi-mono.md" with { type: "text" };
|
||||
import portingToNativesMd from "../../../../docs/porting-to-natives.md" with { type: "text" };
|
||||
import providerStreamingInternalsMd from "../../../../docs/provider-streaming-internals.md" with { type: "text" };
|
||||
import pythonReplMd from "../../../../docs/python-repl.md" with { type: "text" };
|
||||
import rpcMd from "../../../../docs/rpc.md" with { type: "text" };
|
||||
import rulebookMatchingPipelineMd from "../../../../docs/rulebook-matching-pipeline.md" with { type: "text" };
|
||||
import sdkMd from "../../../../docs/sdk.md" with { type: "text" };
|
||||
import secretsMd from "../../../../docs/secrets.md" with { type: "text" };
|
||||
import sessionMd from "../../../../docs/session.md" with { type: "text" };
|
||||
import sessionOperationsExportShareForkResumeMd from "../../../../docs/session-operations-export-share-fork-resume.md" with { type: "text" };
|
||||
import sessionSwitchingAndRecentListingMd from "../../../../docs/session-switching-and-recent-listing.md" with { type: "text" };
|
||||
import sessionTreePlanMd from "../../../../docs/session-tree-plan.md" with { type: "text" };
|
||||
import skillsMd from "../../../../docs/skills.md" with { type: "text" };
|
||||
import slashCommandInternalsMd from "../../../../docs/slash-command-internals.md" with { type: "text" };
|
||||
import taskAgentDiscoveryMd from "../../../../docs/task-agent-discovery.md" with { type: "text" };
|
||||
import themeMd from "../../../../docs/theme.md" with { type: "text" };
|
||||
import treeMd from "../../../../docs/tree.md" with { type: "text" };
|
||||
import ttsrInjectionLifecycleMd from "../../../../docs/ttsr-injection-lifecycle.md" with { type: "text" };
|
||||
import tuiMd from "../../../../docs/tui.md" with { type: "text" };
|
||||
import tuiRuntimeInternalsMd from "../../../../docs/tui-runtime-internals.md" with { type: "text" };
|
||||
|
||||
export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
||||
"bash-tool-runtime.md": bashToolRuntimeMd,
|
||||
"blob-artifact-architecture.md": blobArtifactArchitectureMd,
|
||||
"compaction.md": compactionMd,
|
||||
"config-usage.md": configUsageMd,
|
||||
"custom-tools.md": customToolsMd,
|
||||
"environment-variables.md": environmentVariablesMd,
|
||||
"extension-loading.md": extensionLoadingMd,
|
||||
"extensions.md": extensionsMd,
|
||||
"fs-scan-cache-architecture.md": fsScanCacheArchitectureMd,
|
||||
"gemini-manifest-extensions.md": geminiManifestExtensionsMd,
|
||||
"handoff-generation-pipeline.md": handoffGenerationPipelineMd,
|
||||
"hooks.md": hooksMd,
|
||||
"mcp-protocol-transports.md": mcpProtocolTransportsMd,
|
||||
"mcp-runtime-lifecycle.md": mcpRuntimeLifecycleMd,
|
||||
"mcp-server-tool-authoring.md": mcpServerToolAuthoringMd,
|
||||
"models.md": modelsMd,
|
||||
"natives-addon-loader-runtime.md": nativesAddonLoaderRuntimeMd,
|
||||
"natives-architecture.md": nativesArchitectureMd,
|
||||
"natives-binding-contract.md": nativesBindingContractMd,
|
||||
"natives-build-release-debugging.md": nativesBuildReleaseDebuggingMd,
|
||||
"natives-media-system-utils.md": nativesMediaSystemUtilsMd,
|
||||
"natives-rust-task-cancellation.md": nativesRustTaskCancellationMd,
|
||||
"natives-shell-pty-process.md": nativesShellPtyProcessMd,
|
||||
"natives-text-search-pipeline.md": nativesTextSearchPipelineMd,
|
||||
"non-compaction-retry-policy.md": nonCompactionRetryPolicyMd,
|
||||
"notebook-tool-runtime.md": notebookToolRuntimeMd,
|
||||
"plugin-manager-installer-plumbing.md": pluginManagerInstallerPlumbingMd,
|
||||
"porting-from-pi-mono.md": portingFromPiMonoMd,
|
||||
"porting-to-natives.md": portingToNativesMd,
|
||||
"provider-streaming-internals.md": providerStreamingInternalsMd,
|
||||
"python-repl.md": pythonReplMd,
|
||||
"rpc.md": rpcMd,
|
||||
"rulebook-matching-pipeline.md": rulebookMatchingPipelineMd,
|
||||
"sdk.md": sdkMd,
|
||||
"secrets.md": secretsMd,
|
||||
"session.md": sessionMd,
|
||||
"session-operations-export-share-fork-resume.md": sessionOperationsExportShareForkResumeMd,
|
||||
"session-switching-and-recent-listing.md": sessionSwitchingAndRecentListingMd,
|
||||
"session-tree-plan.md": sessionTreePlanMd,
|
||||
"skills.md": skillsMd,
|
||||
"slash-command-internals.md": slashCommandInternalsMd,
|
||||
"task-agent-discovery.md": taskAgentDiscoveryMd,
|
||||
"theme.md": themeMd,
|
||||
"tree.md": treeMd,
|
||||
"ttsr-injection-lifecycle.md": ttsrInjectionLifecycleMd,
|
||||
"tui.md": tuiMd,
|
||||
"tui-runtime-internals.md": tuiRuntimeInternalsMd,
|
||||
};
|
||||
|
||||
export const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Protocol handler for docs:// URLs.
|
||||
*
|
||||
* Serves statically embedded documentation files bundled at build time.
|
||||
*
|
||||
* URL forms:
|
||||
* - docs:// - Lists all available documentation files
|
||||
* - docs://<file>.md - Reads a specific documentation file
|
||||
*/
|
||||
import * as path from "node:path";
|
||||
import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
|
||||
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
||||
|
||||
/**
|
||||
* Handler for docs:// URLs.
|
||||
*
|
||||
* Resolves documentation file names to their content, or lists available docs.
|
||||
*/
|
||||
export class DocsProtocolHandler implements ProtocolHandler {
|
||||
readonly scheme = "docs";
|
||||
|
||||
async resolve(url: InternalUrl): Promise<InternalResource> {
|
||||
// Extract filename from host + path
|
||||
const host = url.rawHost || url.hostname;
|
||||
const pathname = url.rawPathname ?? url.pathname;
|
||||
const filename = host ? (pathname && pathname !== "/" ? host + pathname : host) : "";
|
||||
|
||||
if (!filename) {
|
||||
return this.#listDocs(url);
|
||||
}
|
||||
|
||||
return this.#readDoc(filename, url);
|
||||
}
|
||||
|
||||
async #listDocs(url: InternalUrl): Promise<InternalResource> {
|
||||
if (EMBEDDED_DOC_FILENAMES.length === 0) {
|
||||
throw new Error("No documentation files found");
|
||||
}
|
||||
|
||||
const listing = EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](docs://${f})`).join("\n");
|
||||
const content = `# Documentation\n\n${EMBEDDED_DOC_FILENAMES.length} files available:\n\n${listing}\n`;
|
||||
|
||||
return {
|
||||
url: url.href,
|
||||
content,
|
||||
contentType: "text/markdown",
|
||||
size: Buffer.byteLength(content, "utf-8"),
|
||||
sourcePath: "docs://",
|
||||
};
|
||||
}
|
||||
|
||||
async #readDoc(filename: string, url: InternalUrl): Promise<InternalResource> {
|
||||
// Validate: no traversal, no absolute paths
|
||||
if (path.isAbsolute(filename)) {
|
||||
throw new Error("Absolute paths are not allowed in docs:// URLs");
|
||||
}
|
||||
|
||||
const normalized = path.posix.normalize(filename.replaceAll("\\", "/"));
|
||||
if (normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
||||
throw new Error("Path traversal (..) is not allowed in docs:// URLs");
|
||||
}
|
||||
|
||||
const content = EMBEDDED_DOCS[normalized];
|
||||
if (content === undefined) {
|
||||
const lookup = normalized.replace(/\.md$/, "");
|
||||
const suggestions = EMBEDDED_DOC_FILENAMES.filter(
|
||||
f => f.includes(lookup) || lookup.includes(f.replace(/\.md$/, "")),
|
||||
).slice(0, 5);
|
||||
const suffix =
|
||||
suggestions.length > 0
|
||||
? `\nDid you mean: ${suggestions.join(", ")}`
|
||||
: "\nUse docs:// to list available files.";
|
||||
throw new Error(`Documentation file not found: ${filename}${suffix}`);
|
||||
}
|
||||
|
||||
return {
|
||||
url: url.href,
|
||||
content,
|
||||
contentType: "text/markdown",
|
||||
size: Buffer.byteLength(content, "utf-8"),
|
||||
sourcePath: `docs://${normalized}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
export { AgentProtocolHandler, type AgentProtocolOptions } from "./agent-protocol";
|
||||
export { ArtifactProtocolHandler, type ArtifactProtocolOptions } from "./artifact-protocol";
|
||||
export { DocsProtocolHandler } from "./docs-protocol";
|
||||
export { applyQuery, parseQuery, pathToQuery } from "./json-query";
|
||||
export { MemoryProtocolHandler, type MemoryProtocolOptions, resolveMemoryUrlToPath } from "./memory-protocol";
|
||||
export { PlanProtocolHandler, type PlanProtocolOptions, resolvePlanUrlToPath } from "./plan-protocol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://).
|
||||
* Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://, docs://).
|
||||
*/
|
||||
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<identity>
|
||||
Distinguished Staff Engineer.
|
||||
You are a distinguished staff engineer operating inside Oh My Pi, a Pi-based coding harness.
|
||||
|
||||
High-agency. Principled. Decisive.
|
||||
Expertise: debugging, refactoring, system design.
|
||||
@@ -172,6 +172,17 @@ Main branch: {{git.mainBranch}}
|
||||
{{/if}}
|
||||
</project>
|
||||
|
||||
<harness>
|
||||
Oh My Pi ships internal documentation accessible via `docs://` URLs (resolved by tools like read/grep).
|
||||
- Read `docs://` to list all available documentation files
|
||||
- Read `docs://<file>.md` to read a specific doc
|
||||
|
||||
<critical>
|
||||
- **ONLY** read docs when the user asks about omp/pi itself: its SDK, extensions, themes, skills, TUI, keybindings, or configuration.
|
||||
- When working on omp/pi topics, read the relevant docs and follow .md cross-references before implementing.
|
||||
</critical>
|
||||
</harness>
|
||||
|
||||
{{#if skills.length}}
|
||||
<skills>
|
||||
Scan descriptions vs task domain. Skill covers output? Read `skill://<name>` first.
|
||||
@@ -221,7 +232,7 @@ Notice the sequential habit:
|
||||
- Comfort in doing one thing at a time
|
||||
- Illusion that order = correctness
|
||||
- Assumption that B depends on A
|
||||
**Use Task tool when:**
|
||||
**Use Task tool when:**
|
||||
- Editing 4+ files with no dependencies between edits
|
||||
- Investigating 2+ independent subsystems
|
||||
- Work decomposes into pieces not needing each other's results
|
||||
|
||||
@@ -8,7 +8,7 @@ Executes bash command in shell session for terminal operations like git, bun, ca
|
||||
- `skill://` URIs are auto-resolved to filesystem paths before execution
|
||||
- `python skill://my-skill/scripts/init.py` runs the script from the skill directory
|
||||
- `skill://<name>/<relative-path>` resolves within the skill's base directory
|
||||
- `agent://`, `artifact://`, `plan://`, `memory://`, and `rule://` URIs are also auto-resolved to filesystem paths before execution
|
||||
- `agent://`, `artifact://`, `plan://`, `memory://`, `rule://`, and `docs://` URIs are also auto-resolved to filesystem paths before execution
|
||||
</instruction>
|
||||
|
||||
<output>
|
||||
|
||||
@@ -23,6 +23,8 @@ Reads files from local filesystem or internal URLs.
|
||||
- `memory://root/<path>` - read relative path within project memory root
|
||||
- `agent://<id>` - read agent output artifact
|
||||
- `agent://<id>/<path>` or `agent://<id>?q=<query>` - extract JSON from agent output
|
||||
- `docs://` - list available pi documentation files
|
||||
- `docs://<file>.md` - read a specific pi documentation file
|
||||
</instruction>
|
||||
|
||||
<output>
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
type CustomCommandsLoadResult,
|
||||
loadCustomCommands as loadCustomCommandsInternal,
|
||||
} from "./extensibility/custom-commands";
|
||||
import { discoverAndLoadCustomTools } from "./extensibility/custom-tools";
|
||||
import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "./extensibility/custom-tools/types";
|
||||
import { CustomToolAdapter } from "./extensibility/custom-tools/wrapper";
|
||||
import {
|
||||
@@ -39,6 +40,7 @@ import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal }
|
||||
import {
|
||||
AgentProtocolHandler,
|
||||
ArtifactProtocolHandler,
|
||||
DocsProtocolHandler,
|
||||
InternalUrlRouter,
|
||||
MemoryProtocolHandler,
|
||||
PlanProtocolHandler,
|
||||
@@ -770,6 +772,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
getRules: () => rulebookRules,
|
||||
}),
|
||||
);
|
||||
internalRouter.register(new DocsProtocolHandler());
|
||||
toolSession.internalRouter = internalRouter;
|
||||
toolSession.getArtifactsDir = getArtifactsDir;
|
||||
toolSession.agentOutputManager = new AgentOutputManager(
|
||||
@@ -848,6 +851,19 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
time("getSearchTools");
|
||||
}
|
||||
|
||||
debugStartup("sdk:discoverCustomTools:start");
|
||||
// Discover and load custom tools from .omp/tools/, .claude/tools/, etc.
|
||||
const builtInToolNames = builtinTools.map(t => t.name);
|
||||
const discoveredCustomTools = await discoverAndLoadCustomTools([], cwd, builtInToolNames);
|
||||
for (const { path, error } of discoveredCustomTools.errors) {
|
||||
logger.error("Custom tool load failed", { path, error });
|
||||
}
|
||||
if (discoveredCustomTools.tools.length > 0) {
|
||||
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
|
||||
}
|
||||
time("discoverAndLoadCustomTools");
|
||||
debugStartup("sdk:discoverCustomTools:done");
|
||||
|
||||
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
|
||||
if (customTools.length > 0) {
|
||||
inlineExtensions.push(createCustomToolsExtension(customTools));
|
||||
|
||||
@@ -966,11 +966,12 @@ function countNonEmptyLines(text: string): number {
|
||||
|
||||
/** Render fetch call (URL preview) */
|
||||
export function renderFetchCall(
|
||||
args: { url: string; timeout?: number; raw?: boolean },
|
||||
args: { url?: string; timeout?: number; raw?: boolean },
|
||||
uiTheme: Theme = theme,
|
||||
): Component {
|
||||
const domain = getDomain(args.url);
|
||||
const path = truncate(args.url.replace(/^https?:\/\/[^/]+/, ""), 50, "…");
|
||||
const url = args.url ?? "";
|
||||
const domain = getDomain(url);
|
||||
const path = truncate(url.replace(/^https?:\/\/[^/]+/, ""), 50, "\u2026");
|
||||
const description = `${domain}${path ? ` ${path}` : ""}`.trim();
|
||||
const meta: string[] = [];
|
||||
if (args.raw) meta.push("raw");
|
||||
|
||||
@@ -282,11 +282,11 @@ export function renderSearchResult(
|
||||
|
||||
/** Render web search call (query preview) */
|
||||
export function renderSearchCall(
|
||||
args: { query: string; provider?: string; [key: string]: unknown },
|
||||
args: { query?: string; provider?: string; [key: string]: unknown },
|
||||
theme: Theme,
|
||||
): Component {
|
||||
const provider = args.provider ?? "auto";
|
||||
const query = truncateToWidth(args.query, 80);
|
||||
const query = truncateToWidth(args.query ?? "", 80);
|
||||
const text = renderStatusLine({ icon: "pending", title: "Web Search", description: query, meta: [provider] }, theme);
|
||||
return new Text(text, 0, 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user