@pi-harness/plugin-api 0.1.27
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/README.md +56 -0
- package/dist/atomic-write.d.ts +9 -0
- package/dist/atomic-write.d.ts.map +1 -0
- package/dist/atomic-write.js +85 -0
- package/dist/atomic-write.js.map +1 -0
- package/dist/bounded-file.d.ts +7 -0
- package/dist/bounded-file.d.ts.map +1 -0
- package/dist/bounded-file.js +51 -0
- package/dist/bounded-file.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +29 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/services.d.ts +118 -0
- package/dist/services.d.ts.map +1 -0
- package/dist/services.js +115 -0
- package/dist/services.js.map +1 -0
- package/dist/workspace-path.d.ts +13 -0
- package/dist/workspace-path.d.ts.map +1 -0
- package/dist/workspace-path.js +71 -0
- package/dist/workspace-path.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @pi-harness/plugin-api
|
|
2
|
+
|
|
3
|
+
The contract a [Pi Harness](https://github.com/pi-harness/pi-harness) plugin is written against. A plugin is an ordinary [Cordis](https://github.com/deepseek-ai/cordis) plugin; this package supplies the service types the harness puts on the Cordis `Context`, the helpers for declaring plugin configuration, and the bounded file access the harness expects a plugin to use when it touches the workspace.
|
|
4
|
+
|
|
5
|
+
It carries no launcher, no HTTP server and no bundled runtime, so installing it does not pull the harness itself.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @pi-harness/plugin-api @deepseek-ai/cordis @deepseek-ai/schemastery @earendil-works/pi-ai @earendil-works/pi-coding-agent
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The four runtimes are `peerDependencies`. They appear in this package's published type surface, so the plugin and the harness have to resolve the same copy of each: a second copy of `@deepseek-ai/cordis` detaches the `declare module` augmentation and the plugin compiles against a `Context` that carries none of the harness services. Match the versions declared here and npm reports any conflict at install time.
|
|
14
|
+
|
|
15
|
+
## Use
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
19
|
+
import { Type } from "@earendil-works/pi-ai";
|
|
20
|
+
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
21
|
+
import type {} from "@pi-harness/plugin-api";
|
|
22
|
+
|
|
23
|
+
export const helloTool = defineTool({
|
|
24
|
+
name: "hello",
|
|
25
|
+
label: "Hello",
|
|
26
|
+
description: "Greet a person by name.",
|
|
27
|
+
parameters: Type.Object({ name: Type.String({ description: "The name to greet." }) }),
|
|
28
|
+
execute(_toolCallId, params) {
|
|
29
|
+
return Promise.resolve({ content: [{ type: "text", text: `Hello, ${params.name}!` }], details: undefined });
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
name: "pi-hello",
|
|
35
|
+
inject: ["piTools"],
|
|
36
|
+
apply(context: Context) {
|
|
37
|
+
context.effect(() => context.piTools.register(helloTool));
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The bare `import type {} from "@pi-harness/plugin-api"` is what applies the `Context` augmentation; without it `context.piTools` does not exist as far as TypeScript is concerned.
|
|
43
|
+
|
|
44
|
+
`examples/plugin-hello` in the repository is this plugin with its tests.
|
|
45
|
+
|
|
46
|
+
## Exports
|
|
47
|
+
|
|
48
|
+
- **Services** — the service interfaces the harness puts on the Cordis `Context` (`PiToolsSnapshot`, `PiSessionService`, `PiModelsService`, `PiRuntimeService`, `PiResourcesService`, `PiMcpService`, `PiTelemetryService`, `PiHarnessLaunch`), the `declare module` augmentation that attaches them, and the `PiToolRegistry` / `PiPluginUiRegistry` a test can construct directly.
|
|
49
|
+
- **Config** — `EmptyConfig` for a plugin that takes no options, and `assertKnownConfigKeys` for rejecting unknown keys with a message that lists the supported ones. Both are built on [Schemastery](https://github.com/shigma/schemastery).
|
|
50
|
+
- **Workspace paths** — `resolveExistingWorkspacePath`, `resolveWorkspaceFilePath`, `prepareWorkspaceFile` and `isPathInside`, which keep a plugin from escaping the workspace root through symlinks or `..`.
|
|
51
|
+
- **Bounded file access** — `readBoundedFile` and `readBoundedTextFile` with their `BoundedFileSizeError` / `BoundedFileTypeError`, which refuse oversized files, directories, FIFOs and symlinks rather than stranding the process on them.
|
|
52
|
+
- **Atomic writes** — `atomicWriteFile`, which serialises concurrent writes to the same path, preserves an existing target's permission bits and fsyncs the containing directory.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface AtomicWriteOptions {
|
|
2
|
+
encoding?: BufferEncoding | null;
|
|
3
|
+
/** Permission bits for the written file. Defaults to the existing regular target's own bits, or 0o600 for a new file. */
|
|
4
|
+
mode?: number;
|
|
5
|
+
overwrite?: boolean;
|
|
6
|
+
signal?: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export declare function atomicWriteFile(target: string, data: string | NodeJS.ArrayBufferView, options?: AtomicWriteOptions): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=atomic-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.d.ts","sourceRoot":"","sources":["../src/atomic-write.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,yHAAyH;IACzH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAsED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,eAAe,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAOtI"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { link, lstat, open, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
const writeQueues = new Map();
|
|
5
|
+
// Only operating-system failures are tolerated below; a programmer error (TypeError and friends) carries no errno and must still surface.
|
|
6
|
+
function isErrno(error) {
|
|
7
|
+
return error instanceof Error && typeof error.code === "string";
|
|
8
|
+
}
|
|
9
|
+
function throwIfAborted(signal) {
|
|
10
|
+
if (signal?.aborted !== true)
|
|
11
|
+
return;
|
|
12
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Atomic write was cancelled", { cause: signal.reason });
|
|
13
|
+
}
|
|
14
|
+
// Directory fsync makes the rename itself durable. Windows has no directory handles to sync, and some filesystems reject opening or fsyncing a directory descriptor; every such case degrades to the file-level guarantee instead of failing a write that already committed.
|
|
15
|
+
async function syncDirectory(path) {
|
|
16
|
+
if (process.platform === "win32")
|
|
17
|
+
return;
|
|
18
|
+
let handle;
|
|
19
|
+
try {
|
|
20
|
+
handle = await open(path, "r");
|
|
21
|
+
await handle.sync();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (!isErrno(error))
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await handle?.close().catch(() => undefined);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// The commit replaces the target inode, so the temporary file's permission bits become the target's. A caller that only replaces content must not silently downgrade an existing file, so its current bits are carried over unless the caller pins a mode explicitly.
|
|
32
|
+
async function targetFileMode(target) {
|
|
33
|
+
try {
|
|
34
|
+
const info = await lstat(target);
|
|
35
|
+
return info.isFile() ? info.mode & 0o777 : undefined;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function writeAtomic(target, data, options) {
|
|
42
|
+
throwIfAborted(options.signal);
|
|
43
|
+
const temporary = join(dirname(target), `.${basename(target)}.${randomUUID()}.tmp`);
|
|
44
|
+
const mode = options.mode ?? (await targetFileMode(target)) ?? 0o600;
|
|
45
|
+
let committed = false;
|
|
46
|
+
try {
|
|
47
|
+
// The temporary file must reach stable storage before it is renamed over the target, otherwise a power loss can commit the directory entry while the data blocks are still only in the page cache, leaving an empty or truncated target after the old contents are already gone.
|
|
48
|
+
const handle = await open(temporary, "wx", mode);
|
|
49
|
+
try {
|
|
50
|
+
// open() masks the requested bits with the process umask, so the mode is restated on the handle to land exactly the requested permissions.
|
|
51
|
+
await handle.chmod(mode);
|
|
52
|
+
const bytes = typeof data === "string" ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
53
|
+
await handle.writeFile(bytes, { encoding: options.encoding, signal: options.signal });
|
|
54
|
+
throwIfAborted(options.signal);
|
|
55
|
+
await handle.sync();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await handle.close();
|
|
59
|
+
}
|
|
60
|
+
throwIfAborted(options.signal);
|
|
61
|
+
if (options.overwrite === false) {
|
|
62
|
+
await link(temporary, target);
|
|
63
|
+
await rm(temporary);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
await rename(temporary, target);
|
|
67
|
+
}
|
|
68
|
+
committed = true;
|
|
69
|
+
await syncDirectory(dirname(target));
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
if (!committed)
|
|
73
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function atomicWriteFile(target, data, options = {}) {
|
|
77
|
+
const previous = writeQueues.get(target);
|
|
78
|
+
const operation = (previous === undefined ? Promise.resolve() : previous.catch(() => undefined)).then(async () => writeAtomic(target, data, options));
|
|
79
|
+
writeQueues.set(target, operation);
|
|
80
|
+
return operation.finally(() => {
|
|
81
|
+
if (writeQueues.get(target) === operation)
|
|
82
|
+
writeQueues.delete(target);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=atomic-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"atomic-write.js","sourceRoot":"","sources":["../src/atomic-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare class BoundedFileSizeError extends Error {
|
|
2
|
+
}
|
|
3
|
+
export declare class BoundedFileTypeError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export declare function readBoundedFile(path: string, maxBytes: number, label: string): Promise<Buffer>;
|
|
6
|
+
export declare function readBoundedTextFile(path: string, maxBytes: number, label: string): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=bounded-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-file.d.ts","sourceRoot":"","sources":["../src/bounded-file.ts"],"names":[],"mappings":"AAGA,qBAAa,oBAAqB,SAAQ,KAAK;CAAG;AAClD,qBAAa,oBAAqB,SAAQ,KAAK;CAAG;AAElD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0BpG;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOxG"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { open } from "node:fs/promises";
|
|
3
|
+
export class BoundedFileSizeError extends Error {
|
|
4
|
+
}
|
|
5
|
+
export class BoundedFileTypeError extends Error {
|
|
6
|
+
}
|
|
7
|
+
export async function readBoundedFile(path, maxBytes, label) {
|
|
8
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
9
|
+
throw new Error("Bounded file size must be a non-negative safe integer");
|
|
10
|
+
let handle;
|
|
11
|
+
try {
|
|
12
|
+
// O_NOFOLLOW only rejects symbolic links. Without O_NONBLOCK a FIFO or a blocking device node would suspend the open until a writer appears, so the regular-file check below would never run and the libuv thread serving the call would be lost for the life of the process.
|
|
13
|
+
handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
14
|
+
const metadata = await handle.stat();
|
|
15
|
+
if (!metadata.isFile())
|
|
16
|
+
throw new BoundedFileTypeError(`${label} must be a regular file`);
|
|
17
|
+
if (metadata.size > maxBytes)
|
|
18
|
+
throw new BoundedFileSizeError(`${label} exceeds the ${maxBytes}-byte limit`);
|
|
19
|
+
const chunks = [];
|
|
20
|
+
let total = 0;
|
|
21
|
+
while (total <= maxBytes) {
|
|
22
|
+
const buffer = Buffer.allocUnsafe(Math.min(64 * 1024, maxBytes + 1 - total));
|
|
23
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
24
|
+
if (bytesRead === 0)
|
|
25
|
+
break;
|
|
26
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
27
|
+
total += bytesRead;
|
|
28
|
+
}
|
|
29
|
+
if (total > maxBytes)
|
|
30
|
+
throw new BoundedFileSizeError(`${label} exceeds the ${maxBytes}-byte limit`);
|
|
31
|
+
return Buffer.concat(chunks, total);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code === "ELOOP")
|
|
35
|
+
throw new BoundedFileTypeError(`${label} cannot be a symbolic link`, { cause: error });
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
await handle?.close();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function readBoundedTextFile(path, maxBytes, label) {
|
|
43
|
+
const bytes = await readBoundedFile(path, maxBytes, label);
|
|
44
|
+
try {
|
|
45
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new Error(`${label} must contain valid UTF-8`, { cause: error });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=bounded-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-file.js","sourceRoot":"","sources":["../src/bounded-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG;AAClD,MAAM,OAAO,oBAAqB,SAAQ,KAAK;CAAG;AAElD,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa;IACjF,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IAC9H,IAAI,MAAoD,CAAC;IACzD,IAAI,CAAC;QACH,8QAA8Q;QAC9Q,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;QAC5F,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACrC,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;YAAE,MAAM,IAAI,oBAAoB,CAAC,GAAG,KAAK,yBAAyB,CAAC,CAAC;QAC1F,IAAI,QAAQ,CAAC,IAAI,GAAG,QAAQ;YAAE,MAAM,IAAI,oBAAoB,CAAC,GAAG,KAAK,gBAAgB,QAAQ,aAAa,CAAC,CAAC;QAC5G,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,OAAO,KAAK,IAAI,QAAQ,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,EAAE,QAAQ,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;YAC7E,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACxE,IAAI,SAAS,KAAK,CAAC;gBAAE,MAAM;YAC3B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;YAC3C,KAAK,IAAI,SAAS,CAAC;QACrB,CAAC;QACD,IAAI,KAAK,GAAG,QAAQ;YAAE,MAAM,IAAI,oBAAoB,CAAC,GAAG,KAAK,gBAAgB,QAAQ,aAAa,CAAC,CAAC;QACpG,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO;YAAE,MAAM,IAAI,oBAAoB,CAAC,GAAG,KAAK,4BAA4B,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9I,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,EAAE,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAY,EAAE,QAAgB,EAAE,KAAa;IACrF,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC3D,IAAI,CAAC;QACH,OAAO,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,2BAA2B,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACzE,CAAC;AACH,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,0BAA0B,CAAC;AAEzC,eAAO,MAAM,WAAW,YActB,CAAC;AAEH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAKrG"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import z from "@deepseek-ai/schemastery";
|
|
2
|
+
export const EmptyConfig = z.transform(z.any(), (value) => {
|
|
3
|
+
if (value === undefined || value === null)
|
|
4
|
+
return {};
|
|
5
|
+
if (typeof value !== "object" || Array.isArray(value))
|
|
6
|
+
throw new Error("expected an object");
|
|
7
|
+
let prototype;
|
|
8
|
+
let keys;
|
|
9
|
+
try {
|
|
10
|
+
prototype = Object.getPrototypeOf(value);
|
|
11
|
+
keys = Reflect.ownKeys(value);
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error("configuration could not be inspected safely", { cause: error });
|
|
15
|
+
}
|
|
16
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
17
|
+
throw new Error("configuration must be a plain object");
|
|
18
|
+
if (keys.length > 0)
|
|
19
|
+
throw new Error(`unknown config keys: ${keys.map((key) => String(key)).join(", ")}`);
|
|
20
|
+
return {};
|
|
21
|
+
});
|
|
22
|
+
export function assertKnownConfigKeys(plugin, config, known) {
|
|
23
|
+
if (config === undefined || config === null || typeof config !== "object")
|
|
24
|
+
return;
|
|
25
|
+
const unknown = Object.keys(config).filter((key) => !known.includes(key));
|
|
26
|
+
if (unknown.length > 0)
|
|
27
|
+
throw new Error(`Unknown ${plugin} config keys: ${unknown.join(", ")}; supported keys are ${known.length === 0 ? "(none)" : known.join(", ")}`);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,CAAC,MAAM,0BAA0B,CAAC;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAc,EAAE,EAAE;IACjE,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACrD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAC7F,IAAI,SAAkB,CAAC;IACvB,IAAI,IAA4B,CAAC;IACjC,IAAI,CAAC;QACH,SAAS,GAAG,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,6CAA6C,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,IAAI,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,SAAS,KAAK,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAClH,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1G,OAAO,EAAE,CAAC;AACZ,CAAC,CAAC,CAAC;AAEH,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,MAAe,EAAE,KAAwB;IAC7F,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ;QAAE,OAAO;IAClF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1E,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,WAAW,MAAM,iBAAiB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACpJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC"}
|
package/dist/context.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,mBAAmB,cAAc,CAAC;AAClC,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAE5B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import type { AgentSession, AgentSessionEvent, AgentSessionRuntime, AgentSessionServices, ExtensionError, SessionManager, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
4
|
+
import type { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
5
|
+
export interface PiHarnessLaunch {
|
|
6
|
+
readonly cwd: string;
|
|
7
|
+
readonly cwdUrl?: string;
|
|
8
|
+
readonly agentDir: string;
|
|
9
|
+
readonly configPath?: string;
|
|
10
|
+
readonly args: readonly string[];
|
|
11
|
+
requestExit(code: number): void;
|
|
12
|
+
}
|
|
13
|
+
export interface PiModelsService {
|
|
14
|
+
readonly runtime: ModelRuntime;
|
|
15
|
+
readonly model: Model<Api>;
|
|
16
|
+
}
|
|
17
|
+
export interface PiModelRuntimeService {
|
|
18
|
+
readonly runtime: ModelRuntime;
|
|
19
|
+
readonly provider: string;
|
|
20
|
+
readonly model: string;
|
|
21
|
+
}
|
|
22
|
+
export type PiResourcesService = AgentSessionServices & {
|
|
23
|
+
createForCwd(cwd: string): Promise<AgentSessionServices>;
|
|
24
|
+
};
|
|
25
|
+
export interface PiSessionService {
|
|
26
|
+
readonly manager: SessionManager;
|
|
27
|
+
}
|
|
28
|
+
export interface PiMcpServerSnapshot {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly command: readonly string[];
|
|
31
|
+
readonly status: string;
|
|
32
|
+
readonly startedAt: number;
|
|
33
|
+
}
|
|
34
|
+
export interface PiMcpService {
|
|
35
|
+
snapshot(): {
|
|
36
|
+
readonly servers: readonly PiMcpServerSnapshot[];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export interface PiRuntimeService {
|
|
40
|
+
readonly session: AgentSession;
|
|
41
|
+
readonly sessionRuntime: AgentSessionRuntime;
|
|
42
|
+
prompt(text: string): Promise<void>;
|
|
43
|
+
abort(): Promise<void>;
|
|
44
|
+
dispose(): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
export interface PiTelemetryEvent {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly properties?: Readonly<Record<string, unknown>>;
|
|
49
|
+
}
|
|
50
|
+
export interface PiTelemetryService {
|
|
51
|
+
readonly enabled: false;
|
|
52
|
+
send(event: PiTelemetryEvent): {
|
|
53
|
+
blocked: true;
|
|
54
|
+
name: string;
|
|
55
|
+
};
|
|
56
|
+
snapshot(): {
|
|
57
|
+
blocked: number;
|
|
58
|
+
names: readonly string[];
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface PiToolsSnapshot {
|
|
62
|
+
readonly names: string[];
|
|
63
|
+
readonly customTools: ToolDefinition[];
|
|
64
|
+
}
|
|
65
|
+
export interface PiToolsLease extends PiToolsSnapshot {
|
|
66
|
+
release(): void;
|
|
67
|
+
}
|
|
68
|
+
export declare class PiToolRegistry {
|
|
69
|
+
#private;
|
|
70
|
+
constructor(names?: readonly string[]);
|
|
71
|
+
register(tool: ToolDefinition): () => void;
|
|
72
|
+
snapshot(): PiToolsSnapshot;
|
|
73
|
+
acquire(): PiToolsLease;
|
|
74
|
+
}
|
|
75
|
+
export interface PiPluginPanel {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly pluginId: string;
|
|
78
|
+
readonly title: string;
|
|
79
|
+
readonly description?: string;
|
|
80
|
+
readonly icon?: string;
|
|
81
|
+
readonly visible?: () => boolean | Promise<boolean>;
|
|
82
|
+
readonly read: () => unknown;
|
|
83
|
+
}
|
|
84
|
+
export interface PiPluginPanelSnapshot {
|
|
85
|
+
readonly id: string;
|
|
86
|
+
readonly pluginId: string;
|
|
87
|
+
readonly title: string;
|
|
88
|
+
readonly description?: string;
|
|
89
|
+
readonly icon?: string;
|
|
90
|
+
readonly data?: unknown;
|
|
91
|
+
readonly error?: string;
|
|
92
|
+
}
|
|
93
|
+
export declare class PiPluginUiRegistry {
|
|
94
|
+
#private;
|
|
95
|
+
register(panel: PiPluginPanel): () => void;
|
|
96
|
+
snapshot(): Promise<readonly PiPluginPanelSnapshot[]>;
|
|
97
|
+
}
|
|
98
|
+
declare module "@deepseek-ai/cordis" {
|
|
99
|
+
interface Context {
|
|
100
|
+
piHarnessLaunch: PiHarnessLaunch;
|
|
101
|
+
piModelRuntime: PiModelRuntimeService;
|
|
102
|
+
piModels: PiModelsService;
|
|
103
|
+
piResources: PiResourcesService;
|
|
104
|
+
piSession: PiSessionService;
|
|
105
|
+
piMcp: PiMcpService;
|
|
106
|
+
piTools: PiToolRegistry;
|
|
107
|
+
piPluginUi: PiPluginUiRegistry;
|
|
108
|
+
piRuntime: PiRuntimeService;
|
|
109
|
+
piTelemetry: PiTelemetryService;
|
|
110
|
+
}
|
|
111
|
+
interface Events {
|
|
112
|
+
"pi/session-event"(event: AgentSessionEvent): void;
|
|
113
|
+
"pi/extension-error"(error: ExtensionError): void;
|
|
114
|
+
"pi/telemetry"(event: PiTelemetryEvent): void;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export declare function provideLaunchContext(context: Context, launch: PiHarnessLaunch): () => void;
|
|
118
|
+
//# sourceMappingURL=services.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"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"}
|
package/dist/services.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
const PI_BUILTIN_TOOL_NAMES = ["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"];
|
|
4
|
+
const MAX_PI_TOOL_NAMES = 256;
|
|
5
|
+
const MAX_PI_TOOL_NAME_LENGTH = 128;
|
|
6
|
+
const PI_TOOL_NAME_PATTERN = /^[^\s\p{Cc}]+$/u;
|
|
7
|
+
function assertPiToolName(name) {
|
|
8
|
+
if (typeof name !== "string" || name.length === 0 || name.length > MAX_PI_TOOL_NAME_LENGTH || !PI_TOOL_NAME_PATTERN.test(name))
|
|
9
|
+
throw new Error(`Pi tool name must contain 1-${MAX_PI_TOOL_NAME_LENGTH} non-whitespace, non-control characters`);
|
|
10
|
+
}
|
|
11
|
+
export class PiToolRegistry {
|
|
12
|
+
#names;
|
|
13
|
+
#customTools = new Map();
|
|
14
|
+
#leases = 0;
|
|
15
|
+
constructor(names = []) {
|
|
16
|
+
const uncheckedNames = names;
|
|
17
|
+
if (!Array.isArray(uncheckedNames) || uncheckedNames.length > MAX_PI_TOOL_NAMES)
|
|
18
|
+
throw new Error(`Pi tool names must contain at most ${MAX_PI_TOOL_NAMES} entries`);
|
|
19
|
+
const validatedNames = [];
|
|
20
|
+
for (const name of uncheckedNames) {
|
|
21
|
+
const uncheckedName = name;
|
|
22
|
+
assertPiToolName(uncheckedName);
|
|
23
|
+
validatedNames.push(uncheckedName);
|
|
24
|
+
}
|
|
25
|
+
if (new Set(validatedNames).size !== validatedNames.length)
|
|
26
|
+
throw new Error("Pi tool names must be unique");
|
|
27
|
+
this.#names = validatedNames;
|
|
28
|
+
}
|
|
29
|
+
register(tool) {
|
|
30
|
+
assertPiToolName(tool.name);
|
|
31
|
+
if (this.#leases > 0)
|
|
32
|
+
throw new Error(`Pi tool registry is leased by pi-runtime; declare a Cordis injection that activates ${tool.name} before pi-runtime`);
|
|
33
|
+
if (this.#names.includes(tool.name) || this.#customTools.has(tool.name))
|
|
34
|
+
throw new Error(`Pi tool is already registered: ${tool.name}`);
|
|
35
|
+
if (PI_BUILTIN_TOOL_NAMES.includes(tool.name))
|
|
36
|
+
throw new Error(`Pi tool name is reserved by a built-in tool: ${tool.name}`);
|
|
37
|
+
this.#customTools.set(tool.name, tool);
|
|
38
|
+
return () => {
|
|
39
|
+
if (this.#customTools.get(tool.name) === tool)
|
|
40
|
+
this.#customTools.delete(tool.name);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
snapshot() {
|
|
44
|
+
return { names: [...this.#names], customTools: [...this.#customTools.values()] };
|
|
45
|
+
}
|
|
46
|
+
acquire() {
|
|
47
|
+
this.#leases += 1;
|
|
48
|
+
let released = false;
|
|
49
|
+
return {
|
|
50
|
+
...this.snapshot(),
|
|
51
|
+
release: () => {
|
|
52
|
+
if (released)
|
|
53
|
+
return;
|
|
54
|
+
released = true;
|
|
55
|
+
this.#leases -= 1;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class PiPluginUiRegistry {
|
|
61
|
+
#panels = new Map();
|
|
62
|
+
register(panel) {
|
|
63
|
+
if (panel.id.trim() === "" || panel.pluginId.trim() === "" || panel.title.trim() === "")
|
|
64
|
+
throw new Error("Plugin UI panel id, pluginId, and title are required");
|
|
65
|
+
if (this.#panels.has(panel.id))
|
|
66
|
+
throw new Error(`Plugin UI panel is already registered: ${panel.id}`);
|
|
67
|
+
this.#panels.set(panel.id, panel);
|
|
68
|
+
let active = true;
|
|
69
|
+
return () => {
|
|
70
|
+
if (!active)
|
|
71
|
+
return;
|
|
72
|
+
active = false;
|
|
73
|
+
if (this.#panels.get(panel.id) === panel)
|
|
74
|
+
this.#panels.delete(panel.id);
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async snapshot() {
|
|
78
|
+
const snapshots = [];
|
|
79
|
+
for (const panel of this.#panels.values()) {
|
|
80
|
+
if (panel.visible !== undefined && !(await panel.visible()))
|
|
81
|
+
continue;
|
|
82
|
+
try {
|
|
83
|
+
const data = await panel.read();
|
|
84
|
+
snapshots.push({
|
|
85
|
+
id: panel.id,
|
|
86
|
+
pluginId: panel.pluginId,
|
|
87
|
+
title: panel.title,
|
|
88
|
+
...(panel.description === undefined ? {} : { description: panel.description }),
|
|
89
|
+
...(panel.icon === undefined ? {} : { icon: panel.icon }),
|
|
90
|
+
data: structuredClone(data),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
snapshots.push({
|
|
95
|
+
id: panel.id,
|
|
96
|
+
pluginId: panel.pluginId,
|
|
97
|
+
title: panel.title,
|
|
98
|
+
...(panel.description === undefined ? {} : { description: panel.description }),
|
|
99
|
+
...(panel.icon === undefined ? {} : { icon: panel.icon }),
|
|
100
|
+
error: error instanceof Error ? error.message : String(error),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return snapshots;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function provideLaunchContext(context, launch) {
|
|
108
|
+
if (!isAbsolute(launch.cwd))
|
|
109
|
+
throw new Error(`Pi Harness launch cwd must be an absolute path: ${launch.cwd}`);
|
|
110
|
+
if (!isAbsolute(launch.agentDir))
|
|
111
|
+
throw new Error(`Pi Harness agent directory must be an absolute path: ${launch.agentDir}`);
|
|
112
|
+
const value = Object.freeze({ ...launch, cwdUrl: pathToFileURL(launch.cwd).href, args: Object.freeze([...launch.args]) });
|
|
113
|
+
return context.provide("piHarnessLaunch", value);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.js","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAiFzC,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;AACpG,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAC9B,MAAM,uBAAuB,GAAG,GAAG,CAAC;AACpC,MAAM,oBAAoB,GAAG,iBAAiB,CAAC;AAE/C,SAAS,gBAAgB,CAAC,IAAa;IACrC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,uBAAuB,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;QAC5H,MAAM,IAAI,KAAK,CAAC,+BAA+B,uBAAuB,yCAAyC,CAAC,CAAC;AACrH,CAAC;AAED,MAAM,OAAO,cAAc;IAChB,MAAM,CAAW;IACjB,YAAY,GAAG,IAAI,GAAG,EAA0B,CAAC;IAC1D,OAAO,GAAG,CAAC,CAAC;IAEZ,YAAY,QAA2B,EAAE;QACvC,MAAM,cAAc,GAAY,KAAK,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,cAAc,CAAC,MAAM,GAAG,iBAAiB;YAC7E,MAAM,IAAI,KAAK,CAAC,sCAAsC,iBAAiB,UAAU,CAAC,CAAC;QACrF,MAAM,cAAc,GAAa,EAAE,CAAC;QACpC,KAAK,MAAM,IAAI,IAAI,cAAc,EAAE,CAAC;YAClC,MAAM,aAAa,GAAY,IAAI,CAAC;YACpC,gBAAgB,CAAC,aAAa,CAAC,CAAC;YAChC,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,cAAc,CAAC,CAAC,IAAI,KAAK,cAAc,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;QAC5G,IAAI,CAAC,MAAM,GAAG,cAAc,CAAC;IAC/B,CAAC;IAED,QAAQ,CAAC,IAAoB;QAC3B,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uFAAuF,IAAI,CAAC,IAAI,oBAAoB,CAAC,CAAC;QAC5J,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACxI,IAAI,qBAAqB,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gDAAgD,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAC5H,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACvC,OAAO,GAAG,EAAE;YACV,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI;gBAAE,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrF,CAAC,CAAC;IACJ,CAAC;IAED,QAAQ;QACN,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC;IACnF,CAAC;IAED,OAAO;QACL,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;QAClB,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,OAAO;YACL,GAAG,IAAI,CAAC,QAAQ,EAAE;YAClB,OAAO,EAAE,GAAG,EAAE;gBACZ,IAAI,QAAQ;oBAAE,OAAO;gBACrB,QAAQ,GAAG,IAAI,CAAC;gBAChB,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC;YACpB,CAAC;SACF,CAAC;IACJ,CAAC;CACF;AAsBD,MAAM,OAAO,kBAAkB;IACpB,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAEpD,QAAQ,CAAC,KAAoB;QAC3B,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YACrF,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,0CAA0C,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;QACtG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QAClC,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,MAAM;gBAAE,OAAO;YACpB,MAAM,GAAG,KAAK,CAAC;YACf,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,KAAK;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,SAAS,GAA4B,EAAE,CAAC;QAC9C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;YAC1C,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;gBAAE,SAAS;YACtE,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;gBAChC,SAAS,CAAC,IAAI,CAAC;oBACb,EAAE,EAAE,KAAK,CAAC,EAAE;oBACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;oBAC9E,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;oBACzD,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC;iBAC5B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,CAAC,IAAI,CAAC;oBACb,EAAE,EAAE,KAAK,CAAC,EAAE;oBACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;oBACxB,KAAK,EAAE,KAAK,CAAC,KAAK;oBAClB,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;oBAC9E,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;oBACzD,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;iBAC9D,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;CACF;AAuBD,MAAM,UAAU,oBAAoB,CAAC,OAAgB,EAAE,MAAuB;IAC5E,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mDAAmD,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9G,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wDAAwD,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7H,MAAM,KAAK,GAAoB,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3I,OAAO,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface WorkspacePath {
|
|
2
|
+
readonly root: string;
|
|
3
|
+
readonly target: string;
|
|
4
|
+
readonly relativePath: string;
|
|
5
|
+
}
|
|
6
|
+
export interface WorkspaceFilePath extends WorkspacePath {
|
|
7
|
+
readonly exists: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function isPathInside(root: string, target: string): boolean;
|
|
10
|
+
export declare function resolveExistingWorkspacePath(root: string, requested: string, message: string): Promise<WorkspacePath>;
|
|
11
|
+
export declare function resolveWorkspaceFilePath(root: string, requested: string, message: string): Promise<WorkspacePath>;
|
|
12
|
+
export declare function prepareWorkspaceFile(root: string, requested: string, message: string): Promise<WorkspaceFilePath>;
|
|
13
|
+
//# sourceMappingURL=workspace-path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-path.d.ts","sourceRoot":"","sources":["../src/workspace-path.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAGlE;AAMD,wBAAsB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAO3H;AAED,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAQvH;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAqCvH"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { lstat, mkdir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
export function isPathInside(root, target) {
|
|
4
|
+
const remainder = relative(root, target);
|
|
5
|
+
return remainder === "" || (remainder !== ".." && !remainder.startsWith(`..${sep}`) && !isAbsolute(remainder));
|
|
6
|
+
}
|
|
7
|
+
function assertInside(root, target, message) {
|
|
8
|
+
if (!isPathInside(root, target))
|
|
9
|
+
throw new Error(message);
|
|
10
|
+
}
|
|
11
|
+
export async function resolveExistingWorkspacePath(root, requested, message) {
|
|
12
|
+
const canonicalRoot = await realpath(resolve(root));
|
|
13
|
+
const lexicalTarget = resolve(canonicalRoot, requested);
|
|
14
|
+
assertInside(canonicalRoot, lexicalTarget, message);
|
|
15
|
+
const target = await realpath(lexicalTarget);
|
|
16
|
+
assertInside(canonicalRoot, target, message);
|
|
17
|
+
return { root: canonicalRoot, target, relativePath: relative(canonicalRoot, target) || "." };
|
|
18
|
+
}
|
|
19
|
+
export async function resolveWorkspaceFilePath(root, requested, message) {
|
|
20
|
+
const canonicalRoot = await realpath(resolve(root));
|
|
21
|
+
const lexicalTarget = resolve(canonicalRoot, requested);
|
|
22
|
+
assertInside(canonicalRoot, lexicalTarget, message);
|
|
23
|
+
const canonicalParent = await realpath(dirname(lexicalTarget));
|
|
24
|
+
assertInside(canonicalRoot, canonicalParent, message);
|
|
25
|
+
const target = join(canonicalParent, basename(lexicalTarget));
|
|
26
|
+
return { root: canonicalRoot, target, relativePath: relative(canonicalRoot, target) || "." };
|
|
27
|
+
}
|
|
28
|
+
export async function prepareWorkspaceFile(root, requested, message) {
|
|
29
|
+
const canonicalRoot = await realpath(resolve(root));
|
|
30
|
+
const lexicalTarget = resolve(canonicalRoot, requested);
|
|
31
|
+
assertInside(canonicalRoot, lexicalTarget, message);
|
|
32
|
+
let ancestor = dirname(lexicalTarget);
|
|
33
|
+
const missing = [];
|
|
34
|
+
let canonicalAncestor;
|
|
35
|
+
for (;;) {
|
|
36
|
+
try {
|
|
37
|
+
canonicalAncestor = await realpath(ancestor);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code !== "ENOENT")
|
|
42
|
+
throw error;
|
|
43
|
+
const parent = dirname(ancestor);
|
|
44
|
+
if (parent === ancestor)
|
|
45
|
+
throw error;
|
|
46
|
+
missing.unshift(basename(ancestor));
|
|
47
|
+
ancestor = parent;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
assertInside(canonicalRoot, canonicalAncestor, message);
|
|
51
|
+
if (!(await stat(canonicalAncestor)).isDirectory())
|
|
52
|
+
throw new Error(message);
|
|
53
|
+
const parent = join(canonicalAncestor, ...missing);
|
|
54
|
+
await mkdir(parent, { recursive: true });
|
|
55
|
+
const canonicalParent = await realpath(parent);
|
|
56
|
+
assertInside(canonicalRoot, canonicalParent, message);
|
|
57
|
+
const target = join(canonicalParent, basename(lexicalTarget));
|
|
58
|
+
let exists = false;
|
|
59
|
+
try {
|
|
60
|
+
const metadata = await lstat(target);
|
|
61
|
+
if (metadata.isSymbolicLink() || !metadata.isFile())
|
|
62
|
+
throw new Error(message);
|
|
63
|
+
exists = true;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error.code !== "ENOENT")
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
return { root: canonicalRoot, target, relativePath: relative(canonicalRoot, target), exists };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=workspace-path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-path.js","sourceRoot":"","sources":["../src/workspace-path.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pi-harness/plugin-api",
|
|
3
|
+
"version": "0.1.27",
|
|
4
|
+
"description": "Public contract for authoring Pi Harness plugins: Cordis context services, plugin configuration helpers, and bounded workspace file access",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"cordis",
|
|
8
|
+
"pi-harness",
|
|
9
|
+
"plugin"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/pi-harness/pi-harness/tree/main/packages/plugin-api#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/pi-harness/pi-harness/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/pi-harness/pi-harness.git",
|
|
19
|
+
"directory": "packages/plugin-api"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.ts",
|
|
31
|
+
"import": "./dist/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
41
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
45
|
+
"@deepseek-ai/schemastery": "3.18.1",
|
|
46
|
+
"@earendil-works/pi-ai": "0.84.4",
|
|
47
|
+
"@earendil-works/pi-coding-agent": "0.84.4"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22.19.0"
|
|
51
|
+
}
|
|
52
|
+
}
|