Merge pull request #2 from CTOTools-skills-code-agent/1.0.0.0/IIAA-XYZ-skill-metadata-and-macos-portability-001
Fix skill origin metadata and macOS source-importer portability
This commit is contained in:
@@ -6,8 +6,8 @@ metadata:
|
||||
author: workspace-swiss-knife
|
||||
version: "2.0"
|
||||
spec: agentskills.io/specification
|
||||
origin-repository: git@github.ibm.com:CTOTools-skills-code-agent/drawio-main.git
|
||||
origin-path: $HOME/projects-ibm/cognitive-architect/workspace-skills-code-agent/drawio-main
|
||||
origin-repository: git@github.ibm.com:CTOTools-skills-code-agent/diagrams-drawio.git
|
||||
origin-path: $HOME/projects-skills-code-agent/ws-skills-code-agent/diagrams-drawio
|
||||
repository: https://gitea.lego-cloud.eu/home-v1-skills-code-agent/diagrams-drawio
|
||||
compatibility: Designed for Cline, Claude Code, GitHub Copilot, OpenAI Codex, and other compatible agent environments
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { constants } from "node:fs";
|
||||
import { open, readdir, realpath } from "node:fs/promises";
|
||||
import { open, readdir, realpath, stat } from "node:fs/promises";
|
||||
import type { FileHandle } from "node:fs/promises";
|
||||
import { isAbsolute, relative, resolve } from "node:path";
|
||||
|
||||
@@ -643,7 +643,7 @@ async function openConfined(candidate: string, root: string): Promise<{ handle:
|
||||
throw error;
|
||||
}
|
||||
try {
|
||||
const canonical = await realpath(`/proc/self/fd/${handle.fd}`);
|
||||
const canonical = await canonicalPathOfOpenHandle(handle, candidate);
|
||||
if (!isConfined(root, canonical)) throw new Error("Opened source escapes outside declared root");
|
||||
return { handle, canonical };
|
||||
} catch (error) {
|
||||
@@ -652,6 +652,35 @@ async function openConfined(candidate: string, root: string): Promise<{ handle:
|
||||
}
|
||||
}
|
||||
|
||||
let procFdAvailable: boolean | undefined;
|
||||
|
||||
/** Path to address an open directory by: its `/proc/self/fd/N` entry on Linux, else its verified canonical path. */
|
||||
async function descriptorPath(handle: FileHandle, canonical: string): Promise<string> {
|
||||
if (procFdAvailable === undefined) {
|
||||
try { await realpath(`/proc/self/fd/${handle.fd}`); procFdAvailable = true; } catch { procFdAvailable = false; }
|
||||
}
|
||||
return procFdAvailable ? `/proc/self/fd/${handle.fd}` : canonical;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the canonical path of an already-opened file. On Linux `/proc/self/fd/N` answers for the
|
||||
* exact descriptor; on macOS/BSD (no procfs) fall back to resolving the candidate path and proving it
|
||||
* names the same inode as the open handle, so a swap between open() and realpath() is still rejected.
|
||||
*/
|
||||
async function canonicalPathOfOpenHandle(handle: FileHandle, candidate: string): Promise<string> {
|
||||
try {
|
||||
return await realpath(`/proc/self/fd/${handle.fd}`);
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
||||
}
|
||||
const canonical = await realpath(candidate);
|
||||
const [opened, resolved] = await Promise.all([handle.stat(), stat(canonical)]);
|
||||
if (opened.dev !== resolved.dev || opened.ino !== resolved.ino) {
|
||||
throw new Error("Opened source does not match its canonical path");
|
||||
}
|
||||
return canonical;
|
||||
}
|
||||
|
||||
async function readBounded(handle: FileHandle, limit: number, directory: boolean): Promise<Buffer> {
|
||||
const initial = await handle.stat();
|
||||
if (!initial.isFile()) throw new Error("Source entry must be a regular file");
|
||||
@@ -711,12 +740,13 @@ export async function importSource(request: SourceImportRequest): Promise<Source
|
||||
|
||||
const results: SourceImportResult[] = [];
|
||||
let consumed = 0;
|
||||
const walk = async (directory: FileHandle): Promise<void> => {
|
||||
for (const name of (await readdir(`/proc/self/fd/${directory.fd}`)).sort(codePointCompare)) {
|
||||
const opened = await openConfined(resolve(`/proc/self/fd/${directory.fd}`, name), rootPath);
|
||||
const walk = async (directory: { handle: FileHandle; canonical: string }): Promise<void> => {
|
||||
const base = await descriptorPath(directory.handle, directory.canonical);
|
||||
for (const name of (await readdir(base)).sort(codePointCompare)) {
|
||||
const opened = await openConfined(resolve(base, name), rootPath);
|
||||
try {
|
||||
const entryInfo = await opened.handle.stat();
|
||||
if (entryInfo.isDirectory()) await walk(opened.handle);
|
||||
if (entryInfo.isDirectory()) await walk(opened);
|
||||
else if (entryInfo.isFile()) {
|
||||
const dot = name.lastIndexOf("."); const extension = dot >= 0 ? name.slice(dot).toLowerCase() : "";
|
||||
if (extensions[request.sourceKind].has(extension)) {
|
||||
@@ -729,7 +759,7 @@ export async function importSource(request: SourceImportRequest): Promise<Source
|
||||
} finally { await opened.handle.close(); }
|
||||
}
|
||||
};
|
||||
try { await walk(source.handle); }
|
||||
try { await walk(source); }
|
||||
finally { await source.handle.close(); }
|
||||
if (results.length === 0) throw new Error(`No supported ${request.sourceKind} source files found`);
|
||||
const nodeById = new Map<string, DiagramNode>(); const edgeById = new Map<string, DiagramEdge>(); const diagnostics: ImportDiagnostic[] = [];
|
||||
|
||||
Reference in New Issue
Block a user