@oh-my-pi/pi-coding-agent 12.12.3 → 12.14.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 +92 -0
- package/package.json +8 -7
- package/scripts/generate-docs-index.ts +56 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli.ts +1 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commit/prompts/analysis-system.md +1 -3
- package/src/config/prompt-templates.ts +20 -5
- package/src/config/settings-schema.ts +10 -1
- package/src/discovery/builtin.ts +14 -4
- package/src/discovery/ssh.ts +26 -19
- package/src/extensibility/extensions/types.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +101 -0
- package/src/internal-urls/docs-protocol.ts +84 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +1 -1
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/ssh-command-controller.ts +452 -0
- package/src/modes/interactive-mode.ts +6 -0
- package/src/modes/types.ts +1 -0
- package/src/patch/diff.ts +1 -1
- package/src/patch/hashline.ts +274 -303
- package/src/patch/index.ts +324 -103
- package/src/patch/shared.ts +25 -28
- package/src/prompts/system/system-prompt.md +14 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/grep.md +12 -8
- package/src/prompts/tools/hashline.md +207 -60
- package/src/prompts/tools/read.md +3 -3
- package/src/sdk.ts +17 -0
- package/src/session/agent-session.ts +1 -0
- package/src/session/auth-storage.ts +6 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/tools/bash-interactive.ts +47 -7
- package/src/tools/fetch.ts +4 -3
- package/src/tools/grep.ts +14 -4
- package/src/tools/read.ts +2 -2
- package/src/tools/ssh.ts +1 -1
- package/src/web/search/render.ts +2 -2
package/src/discovery/builtin.ts
CHANGED
|
@@ -656,7 +656,7 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
656
656
|
|
|
657
657
|
fileLoadPromises.push(
|
|
658
658
|
loadFilesFromDir<CustomTool>(ctx, toolsDir, PROVIDER_ID, level, {
|
|
659
|
-
extensions: ["json", "md"],
|
|
659
|
+
extensions: ["json", "md", "ts", "js", "sh", "bash", "py"],
|
|
660
660
|
transform: (name, content, path, source) => {
|
|
661
661
|
if (name.endsWith(".json")) {
|
|
662
662
|
const data = parseJSON<{ name?: string; description?: string }>(content);
|
|
@@ -668,11 +668,21 @@ async function loadTools(ctx: LoadContext): Promise<LoadResult<CustomTool>> {
|
|
|
668
668
|
_source: source,
|
|
669
669
|
};
|
|
670
670
|
}
|
|
671
|
-
|
|
671
|
+
if (name.endsWith(".md")) {
|
|
672
|
+
const { frontmatter } = parseFrontmatter(content, { source: path });
|
|
673
|
+
return {
|
|
674
|
+
name: (frontmatter.name as string) || name.replace(/\.md$/, ""),
|
|
675
|
+
path,
|
|
676
|
+
description: frontmatter.description as string | undefined,
|
|
677
|
+
level,
|
|
678
|
+
_source: source,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
// Executable tool files (.ts, .js, .sh, .bash, .py)
|
|
682
|
+
const toolName = name.replace(/\.(ts|js|sh|bash|py)$/, "");
|
|
672
683
|
return {
|
|
673
|
-
name:
|
|
684
|
+
name: toolName,
|
|
674
685
|
path,
|
|
675
|
-
description: frontmatter.description as string | undefined,
|
|
676
686
|
level,
|
|
677
687
|
_source: source,
|
|
678
688
|
};
|
package/src/discovery/ssh.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SSH JSON Provider
|
|
3
3
|
*
|
|
4
|
-
* Discovers SSH hosts from
|
|
5
|
-
* Priority: 5 (low, project
|
|
4
|
+
* Discovers SSH hosts from managed omp config paths and legacy root ssh.json files.
|
|
5
|
+
* Priority: 5 (low, project/user config discovery)
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
+
import { getSSHConfigPath } from "@oh-my-pi/pi-utils/dirs";
|
|
8
9
|
import { registerProvider } from "../capability";
|
|
9
10
|
import { readFile } from "../capability/fs";
|
|
10
11
|
import { type SSHHost, sshCapability } from "../capability/ssh";
|
|
@@ -90,38 +91,39 @@ function normalizeHost(
|
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
async function loadSshJsonFile(
|
|
94
|
+
async function loadSshJsonFile(
|
|
95
|
+
ctx: LoadContext,
|
|
96
|
+
filePath: string,
|
|
97
|
+
level: "user" | "project",
|
|
98
|
+
): Promise<LoadResult<SSHHost>> {
|
|
94
99
|
const items: SSHHost[] = [];
|
|
95
100
|
const warnings: string[] = [];
|
|
96
|
-
|
|
97
|
-
const content = await readFile(path);
|
|
101
|
+
const content = await readFile(filePath);
|
|
98
102
|
if (content === null) {
|
|
99
103
|
return { items, warnings };
|
|
100
104
|
}
|
|
101
|
-
|
|
102
105
|
const parsed = parseJSON<SSHConfigFile>(content);
|
|
103
106
|
if (!parsed) {
|
|
104
|
-
warnings.push(`Failed to parse JSON in ${
|
|
107
|
+
warnings.push(`Failed to parse JSON in ${filePath}`);
|
|
105
108
|
return { items, warnings };
|
|
106
109
|
}
|
|
107
|
-
|
|
108
110
|
const config = expandEnvVarsDeep(parsed);
|
|
109
111
|
if (!config.hosts || typeof config.hosts !== "object") {
|
|
110
|
-
warnings.push(`Missing hosts in ${
|
|
112
|
+
warnings.push(`Missing hosts in ${filePath}`);
|
|
111
113
|
return { items, warnings };
|
|
112
114
|
}
|
|
113
115
|
|
|
114
|
-
const source = createSourceMeta(PROVIDER_ID,
|
|
116
|
+
const source = createSourceMeta(PROVIDER_ID, filePath, level);
|
|
115
117
|
for (const [name, rawHost] of Object.entries(config.hosts)) {
|
|
116
118
|
if (!name.trim()) {
|
|
117
|
-
warnings.push(`Invalid SSH host name in ${
|
|
119
|
+
warnings.push(`Invalid SSH host name in ${filePath}`);
|
|
118
120
|
continue;
|
|
119
121
|
}
|
|
120
122
|
if (!rawHost || typeof rawHost !== "object") {
|
|
121
|
-
warnings.push(`Invalid host entry in ${
|
|
123
|
+
warnings.push(`Invalid host entry in ${filePath}: ${name}`);
|
|
122
124
|
continue;
|
|
123
125
|
}
|
|
124
|
-
const host = normalizeHost(name, rawHost, source,
|
|
126
|
+
const host = normalizeHost(name, rawHost, source, ctx.home, warnings);
|
|
125
127
|
if (host) items.push(host);
|
|
126
128
|
}
|
|
127
129
|
|
|
@@ -130,14 +132,19 @@ async function loadSshJsonFile(_ctx: LoadContext, path: string): Promise<LoadRes
|
|
|
130
132
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
131
133
|
};
|
|
132
134
|
}
|
|
133
|
-
|
|
134
135
|
async function load(ctx: LoadContext): Promise<LoadResult<SSHHost>> {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
136
|
+
const candidateSources: Array<{ path: string; level: "user" | "project" }> = [
|
|
137
|
+
{ path: getSSHConfigPath("project", ctx.cwd), level: "project" },
|
|
138
|
+
{ path: getSSHConfigPath("user", ctx.cwd), level: "user" },
|
|
139
|
+
{ path: path.join(ctx.cwd, "ssh.json"), level: "project" },
|
|
140
|
+
{ path: path.join(ctx.cwd, ".ssh.json"), level: "project" },
|
|
141
|
+
];
|
|
142
|
+
const uniqueSources = candidateSources.filter(
|
|
143
|
+
(source, index, arr) => arr.findIndex(candidate => candidate.path === source.path) === index,
|
|
144
|
+
);
|
|
145
|
+
const results = await Promise.all(uniqueSources.map(source => loadSshJsonFile(ctx, source.path, source.level)));
|
|
138
146
|
const allItems = results.flatMap(r => r.items);
|
|
139
147
|
const allWarnings = results.flatMap(r => r.warnings ?? []);
|
|
140
|
-
|
|
141
148
|
return {
|
|
142
149
|
items: allItems,
|
|
143
150
|
warnings: allWarnings.length > 0 ? allWarnings : undefined,
|
|
@@ -147,7 +154,7 @@ async function load(ctx: LoadContext): Promise<LoadResult<SSHHost>> {
|
|
|
147
154
|
registerProvider(sshCapability.id, {
|
|
148
155
|
id: PROVIDER_ID,
|
|
149
156
|
displayName: DISPLAY_NAME,
|
|
150
|
-
description: "Load SSH hosts from ssh.json
|
|
157
|
+
description: "Load SSH hosts from managed omp paths and legacy ssh.json/.ssh.json files",
|
|
151
158
|
priority: 5,
|
|
152
159
|
load,
|
|
153
160
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT
|
|
2
|
+
|
|
3
|
+
import bashToolRuntimeMd from "../../../../docs/bash-tool-runtime.md" with { type: "text" };
|
|
4
|
+
import blobArtifactArchitectureMd from "../../../../docs/blob-artifact-architecture.md" with { type: "text" };
|
|
5
|
+
import compactionMd from "../../../../docs/compaction.md" with { type: "text" };
|
|
6
|
+
import configUsageMd from "../../../../docs/config-usage.md" with { type: "text" };
|
|
7
|
+
import customToolsMd from "../../../../docs/custom-tools.md" with { type: "text" };
|
|
8
|
+
import environmentVariablesMd from "../../../../docs/environment-variables.md" with { type: "text" };
|
|
9
|
+
import extensionLoadingMd from "../../../../docs/extension-loading.md" with { type: "text" };
|
|
10
|
+
import extensionsMd from "../../../../docs/extensions.md" with { type: "text" };
|
|
11
|
+
import fsScanCacheArchitectureMd from "../../../../docs/fs-scan-cache-architecture.md" with { type: "text" };
|
|
12
|
+
import geminiManifestExtensionsMd from "../../../../docs/gemini-manifest-extensions.md" with { type: "text" };
|
|
13
|
+
import handoffGenerationPipelineMd from "../../../../docs/handoff-generation-pipeline.md" with { type: "text" };
|
|
14
|
+
import hooksMd from "../../../../docs/hooks.md" with { type: "text" };
|
|
15
|
+
import mcpProtocolTransportsMd from "../../../../docs/mcp-protocol-transports.md" with { type: "text" };
|
|
16
|
+
import mcpRuntimeLifecycleMd from "../../../../docs/mcp-runtime-lifecycle.md" with { type: "text" };
|
|
17
|
+
import mcpServerToolAuthoringMd from "../../../../docs/mcp-server-tool-authoring.md" with { type: "text" };
|
|
18
|
+
import modelsMd from "../../../../docs/models.md" with { type: "text" };
|
|
19
|
+
import nativesAddonLoaderRuntimeMd from "../../../../docs/natives-addon-loader-runtime.md" with { type: "text" };
|
|
20
|
+
import nativesArchitectureMd from "../../../../docs/natives-architecture.md" with { type: "text" };
|
|
21
|
+
import nativesBindingContractMd from "../../../../docs/natives-binding-contract.md" with { type: "text" };
|
|
22
|
+
import nativesBuildReleaseDebuggingMd from "../../../../docs/natives-build-release-debugging.md" with { type: "text" };
|
|
23
|
+
import nativesMediaSystemUtilsMd from "../../../../docs/natives-media-system-utils.md" with { type: "text" };
|
|
24
|
+
import nativesRustTaskCancellationMd from "../../../../docs/natives-rust-task-cancellation.md" with { type: "text" };
|
|
25
|
+
import nativesShellPtyProcessMd from "../../../../docs/natives-shell-pty-process.md" with { type: "text" };
|
|
26
|
+
import nativesTextSearchPipelineMd from "../../../../docs/natives-text-search-pipeline.md" with { type: "text" };
|
|
27
|
+
import nonCompactionRetryPolicyMd from "../../../../docs/non-compaction-retry-policy.md" with { type: "text" };
|
|
28
|
+
import notebookToolRuntimeMd from "../../../../docs/notebook-tool-runtime.md" with { type: "text" };
|
|
29
|
+
import pluginManagerInstallerPlumbingMd from "../../../../docs/plugin-manager-installer-plumbing.md" with { type: "text" };
|
|
30
|
+
import portingFromPiMonoMd from "../../../../docs/porting-from-pi-mono.md" with { type: "text" };
|
|
31
|
+
import portingToNativesMd from "../../../../docs/porting-to-natives.md" with { type: "text" };
|
|
32
|
+
import providerStreamingInternalsMd from "../../../../docs/provider-streaming-internals.md" with { type: "text" };
|
|
33
|
+
import pythonReplMd from "../../../../docs/python-repl.md" with { type: "text" };
|
|
34
|
+
import rpcMd from "../../../../docs/rpc.md" with { type: "text" };
|
|
35
|
+
import rulebookMatchingPipelineMd from "../../../../docs/rulebook-matching-pipeline.md" with { type: "text" };
|
|
36
|
+
import sdkMd from "../../../../docs/sdk.md" with { type: "text" };
|
|
37
|
+
import secretsMd from "../../../../docs/secrets.md" with { type: "text" };
|
|
38
|
+
import sessionMd from "../../../../docs/session.md" with { type: "text" };
|
|
39
|
+
import sessionOperationsExportShareForkResumeMd from "../../../../docs/session-operations-export-share-fork-resume.md" with { type: "text" };
|
|
40
|
+
import sessionSwitchingAndRecentListingMd from "../../../../docs/session-switching-and-recent-listing.md" with { type: "text" };
|
|
41
|
+
import sessionTreePlanMd from "../../../../docs/session-tree-plan.md" with { type: "text" };
|
|
42
|
+
import skillsMd from "../../../../docs/skills.md" with { type: "text" };
|
|
43
|
+
import slashCommandInternalsMd from "../../../../docs/slash-command-internals.md" with { type: "text" };
|
|
44
|
+
import taskAgentDiscoveryMd from "../../../../docs/task-agent-discovery.md" with { type: "text" };
|
|
45
|
+
import themeMd from "../../../../docs/theme.md" with { type: "text" };
|
|
46
|
+
import treeMd from "../../../../docs/tree.md" with { type: "text" };
|
|
47
|
+
import ttsrInjectionLifecycleMd from "../../../../docs/ttsr-injection-lifecycle.md" with { type: "text" };
|
|
48
|
+
import tuiMd from "../../../../docs/tui.md" with { type: "text" };
|
|
49
|
+
import tuiRuntimeInternalsMd from "../../../../docs/tui-runtime-internals.md" with { type: "text" };
|
|
50
|
+
|
|
51
|
+
export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
52
|
+
"bash-tool-runtime.md": bashToolRuntimeMd,
|
|
53
|
+
"blob-artifact-architecture.md": blobArtifactArchitectureMd,
|
|
54
|
+
"compaction.md": compactionMd,
|
|
55
|
+
"config-usage.md": configUsageMd,
|
|
56
|
+
"custom-tools.md": customToolsMd,
|
|
57
|
+
"environment-variables.md": environmentVariablesMd,
|
|
58
|
+
"extension-loading.md": extensionLoadingMd,
|
|
59
|
+
"extensions.md": extensionsMd,
|
|
60
|
+
"fs-scan-cache-architecture.md": fsScanCacheArchitectureMd,
|
|
61
|
+
"gemini-manifest-extensions.md": geminiManifestExtensionsMd,
|
|
62
|
+
"handoff-generation-pipeline.md": handoffGenerationPipelineMd,
|
|
63
|
+
"hooks.md": hooksMd,
|
|
64
|
+
"mcp-protocol-transports.md": mcpProtocolTransportsMd,
|
|
65
|
+
"mcp-runtime-lifecycle.md": mcpRuntimeLifecycleMd,
|
|
66
|
+
"mcp-server-tool-authoring.md": mcpServerToolAuthoringMd,
|
|
67
|
+
"models.md": modelsMd,
|
|
68
|
+
"natives-addon-loader-runtime.md": nativesAddonLoaderRuntimeMd,
|
|
69
|
+
"natives-architecture.md": nativesArchitectureMd,
|
|
70
|
+
"natives-binding-contract.md": nativesBindingContractMd,
|
|
71
|
+
"natives-build-release-debugging.md": nativesBuildReleaseDebuggingMd,
|
|
72
|
+
"natives-media-system-utils.md": nativesMediaSystemUtilsMd,
|
|
73
|
+
"natives-rust-task-cancellation.md": nativesRustTaskCancellationMd,
|
|
74
|
+
"natives-shell-pty-process.md": nativesShellPtyProcessMd,
|
|
75
|
+
"natives-text-search-pipeline.md": nativesTextSearchPipelineMd,
|
|
76
|
+
"non-compaction-retry-policy.md": nonCompactionRetryPolicyMd,
|
|
77
|
+
"notebook-tool-runtime.md": notebookToolRuntimeMd,
|
|
78
|
+
"plugin-manager-installer-plumbing.md": pluginManagerInstallerPlumbingMd,
|
|
79
|
+
"porting-from-pi-mono.md": portingFromPiMonoMd,
|
|
80
|
+
"porting-to-natives.md": portingToNativesMd,
|
|
81
|
+
"provider-streaming-internals.md": providerStreamingInternalsMd,
|
|
82
|
+
"python-repl.md": pythonReplMd,
|
|
83
|
+
"rpc.md": rpcMd,
|
|
84
|
+
"rulebook-matching-pipeline.md": rulebookMatchingPipelineMd,
|
|
85
|
+
"sdk.md": sdkMd,
|
|
86
|
+
"secrets.md": secretsMd,
|
|
87
|
+
"session.md": sessionMd,
|
|
88
|
+
"session-operations-export-share-fork-resume.md": sessionOperationsExportShareForkResumeMd,
|
|
89
|
+
"session-switching-and-recent-listing.md": sessionSwitchingAndRecentListingMd,
|
|
90
|
+
"session-tree-plan.md": sessionTreePlanMd,
|
|
91
|
+
"skills.md": skillsMd,
|
|
92
|
+
"slash-command-internals.md": slashCommandInternalsMd,
|
|
93
|
+
"task-agent-discovery.md": taskAgentDiscoveryMd,
|
|
94
|
+
"theme.md": themeMd,
|
|
95
|
+
"tree.md": treeMd,
|
|
96
|
+
"ttsr-injection-lifecycle.md": ttsrInjectionLifecycleMd,
|
|
97
|
+
"tui.md": tuiMd,
|
|
98
|
+
"tui-runtime-internals.md": tuiRuntimeInternalsMd,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const EMBEDDED_DOC_FILENAMES = Object.keys(EMBEDDED_DOCS).sort();
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol handler for docs:// URLs.
|
|
3
|
+
*
|
|
4
|
+
* Serves statically embedded documentation files bundled at build time.
|
|
5
|
+
*
|
|
6
|
+
* URL forms:
|
|
7
|
+
* - docs:// - Lists all available documentation files
|
|
8
|
+
* - docs://<file>.md - Reads a specific documentation file
|
|
9
|
+
*/
|
|
10
|
+
import * as path from "node:path";
|
|
11
|
+
import { EMBEDDED_DOC_FILENAMES, EMBEDDED_DOCS } from "./docs-index.generated";
|
|
12
|
+
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Handler for docs:// URLs.
|
|
16
|
+
*
|
|
17
|
+
* Resolves documentation file names to their content, or lists available docs.
|
|
18
|
+
*/
|
|
19
|
+
export class DocsProtocolHandler implements ProtocolHandler {
|
|
20
|
+
readonly scheme = "docs";
|
|
21
|
+
|
|
22
|
+
async resolve(url: InternalUrl): Promise<InternalResource> {
|
|
23
|
+
// Extract filename from host + path
|
|
24
|
+
const host = url.rawHost || url.hostname;
|
|
25
|
+
const pathname = url.rawPathname ?? url.pathname;
|
|
26
|
+
const filename = host ? (pathname && pathname !== "/" ? host + pathname : host) : "";
|
|
27
|
+
|
|
28
|
+
if (!filename) {
|
|
29
|
+
return this.#listDocs(url);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return this.#readDoc(filename, url);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async #listDocs(url: InternalUrl): Promise<InternalResource> {
|
|
36
|
+
if (EMBEDDED_DOC_FILENAMES.length === 0) {
|
|
37
|
+
throw new Error("No documentation files found");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const listing = EMBEDDED_DOC_FILENAMES.map(f => `- [${f}](docs://${f})`).join("\n");
|
|
41
|
+
const content = `# Documentation\n\n${EMBEDDED_DOC_FILENAMES.length} files available:\n\n${listing}\n`;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
url: url.href,
|
|
45
|
+
content,
|
|
46
|
+
contentType: "text/markdown",
|
|
47
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
48
|
+
sourcePath: "docs://",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async #readDoc(filename: string, url: InternalUrl): Promise<InternalResource> {
|
|
53
|
+
// Validate: no traversal, no absolute paths
|
|
54
|
+
if (path.isAbsolute(filename)) {
|
|
55
|
+
throw new Error("Absolute paths are not allowed in docs:// URLs");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const normalized = path.posix.normalize(filename.replaceAll("\\", "/"));
|
|
59
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.includes("/../")) {
|
|
60
|
+
throw new Error("Path traversal (..) is not allowed in docs:// URLs");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const content = EMBEDDED_DOCS[normalized];
|
|
64
|
+
if (content === undefined) {
|
|
65
|
+
const lookup = normalized.replace(/\.md$/, "");
|
|
66
|
+
const suggestions = EMBEDDED_DOC_FILENAMES.filter(
|
|
67
|
+
f => f.includes(lookup) || lookup.includes(f.replace(/\.md$/, "")),
|
|
68
|
+
).slice(0, 5);
|
|
69
|
+
const suffix =
|
|
70
|
+
suggestions.length > 0
|
|
71
|
+
? `\nDid you mean: ${suggestions.join(", ")}`
|
|
72
|
+
: "\nUse docs:// to list available files.";
|
|
73
|
+
throw new Error(`Documentation file not found: ${filename}${suffix}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
url: url.href,
|
|
78
|
+
content,
|
|
79
|
+
contentType: "text/markdown",
|
|
80
|
+
size: Buffer.byteLength(content, "utf-8"),
|
|
81
|
+
sourcePath: `docs://${normalized}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
|
|
23
23
|
export { AgentProtocolHandler, type AgentProtocolOptions } from "./agent-protocol";
|
|
24
24
|
export { ArtifactProtocolHandler, type ArtifactProtocolOptions } from "./artifact-protocol";
|
|
25
|
+
export { DocsProtocolHandler } from "./docs-protocol";
|
|
25
26
|
export { applyQuery, parseQuery, pathToQuery } from "./json-query";
|
|
26
27
|
export { MemoryProtocolHandler, type MemoryProtocolOptions, resolveMemoryUrlToPath } from "./memory-protocol";
|
|
27
28
|
export { PlanProtocolHandler, type PlanProtocolOptions, resolvePlanUrlToPath } from "./plan-protocol";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://).
|
|
2
|
+
* Internal URL router for internal protocols (agent://, artifact://, plan://, memory://, skill://, rule://, docs://).
|
|
3
3
|
*/
|
|
4
4
|
import type { InternalResource, InternalUrl, ProtocolHandler } from "./types";
|
|
5
5
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
|
|
1
2
|
import { Loader, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
2
3
|
import { settings } from "../../config/settings";
|
|
3
4
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
@@ -14,6 +15,7 @@ export class EventController {
|
|
|
14
15
|
#lastReadGroup: ReadToolGroupComponent | undefined = undefined;
|
|
15
16
|
#lastThinkingCount = 0;
|
|
16
17
|
#renderedCustomMessages = new Set<string>();
|
|
18
|
+
#lastIntent: string | undefined = undefined;
|
|
17
19
|
|
|
18
20
|
constructor(private ctx: InteractiveModeContext) {}
|
|
19
21
|
|
|
@@ -32,6 +34,13 @@ export class EventController {
|
|
|
32
34
|
return this.#lastReadGroup;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
#updateWorkingMessageFromIntent(intent: string | undefined): void {
|
|
38
|
+
const trimmed = intent?.trim();
|
|
39
|
+
if (!trimmed || trimmed === this.#lastIntent) return;
|
|
40
|
+
this.#lastIntent = trimmed;
|
|
41
|
+
this.ctx.setWorkingMessage(`${trimmed} (esc to interrupt)`);
|
|
42
|
+
}
|
|
43
|
+
|
|
35
44
|
subscribeToAgent(): void {
|
|
36
45
|
this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
|
|
37
46
|
await this.handleEvent(event);
|
|
@@ -48,6 +57,7 @@ export class EventController {
|
|
|
48
57
|
|
|
49
58
|
switch (event.type) {
|
|
50
59
|
case "agent_start":
|
|
60
|
+
this.#lastIntent = undefined;
|
|
51
61
|
if (this.ctx.retryEscapeHandler) {
|
|
52
62
|
this.ctx.editor.onEscape = this.ctx.retryEscapeHandler;
|
|
53
63
|
this.ctx.retryEscapeHandler = undefined;
|
|
@@ -155,6 +165,15 @@ export class EventController {
|
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
}
|
|
168
|
+
|
|
169
|
+
// Update working message with intent from streamed tool arguments
|
|
170
|
+
for (const content of this.ctx.streamingMessage.content) {
|
|
171
|
+
if (content.type !== "toolCall") continue;
|
|
172
|
+
const args = content.arguments;
|
|
173
|
+
if (!args || typeof args !== "object" || !(INTENT_FIELD in args)) continue;
|
|
174
|
+
this.#updateWorkingMessageFromIntent(args[INTENT_FIELD] as string | undefined);
|
|
175
|
+
}
|
|
176
|
+
|
|
158
177
|
this.ctx.ui.requestRender();
|
|
159
178
|
}
|
|
160
179
|
break;
|
|
@@ -196,6 +215,7 @@ export class EventController {
|
|
|
196
215
|
break;
|
|
197
216
|
|
|
198
217
|
case "tool_execution_start": {
|
|
218
|
+
this.#updateWorkingMessageFromIntent(event.intent);
|
|
199
219
|
if (!this.ctx.pendingTools.has(event.toolCallId)) {
|
|
200
220
|
if (event.toolName === "read") {
|
|
201
221
|
const group = this.#getReadGroup();
|