@oyasmi/pipiclaw 0.5.5 → 0.5.6
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 +35 -0
- package/dist/agent/channel-runner.d.ts +1 -0
- package/dist/agent/channel-runner.js +3 -0
- package/dist/agent/runner-factory.d.ts +2 -0
- package/dist/agent/runner-factory.js +6 -0
- package/dist/agent/types.d.ts +1 -0
- package/dist/memory/lifecycle.d.ts +2 -1
- package/dist/memory/lifecycle.js +19 -1
- package/dist/paths.js +1 -1
- package/dist/runtime/bootstrap.d.ts +22 -1
- package/dist/runtime/bootstrap.js +72 -26
- package/dist/security/command-guard.d.ts +16 -0
- package/dist/security/command-guard.js +447 -0
- package/dist/security/config.d.ts +4 -0
- package/dist/security/config.js +82 -0
- package/dist/security/logger.d.ts +2 -0
- package/dist/security/logger.js +18 -0
- package/dist/security/path-guard.d.ts +2 -0
- package/dist/security/path-guard.js +237 -0
- package/dist/security/types.d.ts +66 -0
- package/dist/security/types.js +1 -0
- package/dist/subagents/tool.d.ts +2 -0
- package/dist/subagents/tool.js +31 -7
- package/dist/tools/attach.d.ts +7 -1
- package/dist/tools/attach.js +36 -1
- package/dist/tools/bash.d.ts +4 -0
- package/dist/tools/bash.js +38 -0
- package/dist/tools/edit.d.ts +7 -1
- package/dist/tools/edit.js +42 -2
- package/dist/tools/index.d.ts +7 -1
- package/dist/tools/index.js +29 -3
- package/dist/tools/read.d.ts +7 -1
- package/dist/tools/read.js +36 -1
- package/dist/tools/write-content.d.ts +5 -0
- package/dist/tools/write-content.js +32 -11
- package/dist/tools/write.d.ts +7 -1
- package/dist/tools/write.js +10 -3
- package/package.json +2 -1
package/dist/tools/index.js
CHANGED
|
@@ -1,13 +1,38 @@
|
|
|
1
|
+
import { APP_HOME_DIR } from "../paths.js";
|
|
2
|
+
import { loadSecurityConfig } from "../security/config.js";
|
|
1
3
|
import { createSubAgentTool } from "../subagents/tool.js";
|
|
2
4
|
import { createBashTool } from "./bash.js";
|
|
3
5
|
import { createEditTool } from "./edit.js";
|
|
4
6
|
import { createReadTool } from "./read.js";
|
|
5
7
|
import { createWriteTool } from "./write.js";
|
|
6
|
-
export function createPipiclawBaseTools(executor) {
|
|
7
|
-
|
|
8
|
+
export function createPipiclawBaseTools(executor, options = {}) {
|
|
9
|
+
const hasSecurityOptions = options.securityConfig || options.securityContext || options.channelId;
|
|
10
|
+
const toolOptions = hasSecurityOptions
|
|
11
|
+
? {
|
|
12
|
+
securityConfig: options.securityConfig,
|
|
13
|
+
securityContext: options.securityContext,
|
|
14
|
+
channelId: options.channelId,
|
|
15
|
+
}
|
|
16
|
+
: undefined;
|
|
17
|
+
return [
|
|
18
|
+
createReadTool(executor, toolOptions),
|
|
19
|
+
createBashTool(executor, toolOptions),
|
|
20
|
+
createEditTool(executor, toolOptions),
|
|
21
|
+
createWriteTool(executor, toolOptions),
|
|
22
|
+
];
|
|
8
23
|
}
|
|
9
24
|
export function createPipiclawTools(options) {
|
|
10
|
-
const
|
|
25
|
+
const securityConfig = loadSecurityConfig(APP_HOME_DIR);
|
|
26
|
+
const securityContext = {
|
|
27
|
+
workspaceDir: options.workspaceDir,
|
|
28
|
+
workspacePath: options.workspacePath,
|
|
29
|
+
cwd: process.cwd(),
|
|
30
|
+
};
|
|
31
|
+
const baseTools = createPipiclawBaseTools(options.executor, {
|
|
32
|
+
securityConfig,
|
|
33
|
+
securityContext,
|
|
34
|
+
channelId: options.channelId,
|
|
35
|
+
});
|
|
11
36
|
return [
|
|
12
37
|
...baseTools,
|
|
13
38
|
createSubAgentTool({
|
|
@@ -19,6 +44,7 @@ export function createPipiclawTools(options) {
|
|
|
19
44
|
channelDir: options.channelDir,
|
|
20
45
|
getSubAgentDiscovery: options.getSubAgentDiscovery,
|
|
21
46
|
getMemoryRecallSettings: options.getMemoryRecallSettings,
|
|
47
|
+
securityConfig,
|
|
22
48
|
runtimeContext: {
|
|
23
49
|
workspacePath: options.workspacePath,
|
|
24
50
|
channelId: options.channelId,
|
package/dist/tools/read.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import type { Executor } from "../sandbox.js";
|
|
3
|
+
import type { SecurityConfig, SecurityRuntimeContext } from "../security/types.js";
|
|
3
4
|
declare const readSchema: import("@sinclair/typebox").TObject<{
|
|
4
5
|
label: import("@sinclair/typebox").TString;
|
|
5
6
|
path: import("@sinclair/typebox").TString;
|
|
6
7
|
offset: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
7
8
|
limit: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TNumber>;
|
|
8
9
|
}>;
|
|
9
|
-
export
|
|
10
|
+
export interface ReadToolOptions {
|
|
11
|
+
securityConfig?: SecurityConfig;
|
|
12
|
+
securityContext?: SecurityRuntimeContext;
|
|
13
|
+
channelId?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function createReadTool(executor: Executor, options?: ReadToolOptions): AgentTool<typeof readSchema>;
|
|
10
16
|
export {};
|
package/dist/tools/read.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
2
|
import { extname } from "path";
|
|
3
|
+
import { DEFAULT_SECURITY_CONFIG } from "../security/config.js";
|
|
4
|
+
import { logSecurityEvent } from "../security/logger.js";
|
|
5
|
+
import { guardPath } from "../security/path-guard.js";
|
|
3
6
|
import { shellEscape } from "../shared/shell-escape.js";
|
|
4
7
|
import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead } from "./truncate.js";
|
|
5
8
|
/**
|
|
@@ -25,13 +28,45 @@ const readSchema = Type.Object({
|
|
|
25
28
|
offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
|
|
26
29
|
limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
|
|
27
30
|
});
|
|
28
|
-
|
|
31
|
+
function formatPathBlockMessage(resolvedPath, category, reason) {
|
|
32
|
+
const lines = [`Path blocked${category ? ` [${category}]` : ""}`];
|
|
33
|
+
if (reason) {
|
|
34
|
+
lines.push(`Reason: ${reason}`);
|
|
35
|
+
}
|
|
36
|
+
if (resolvedPath) {
|
|
37
|
+
lines.push(`Resolved path: ${resolvedPath}`);
|
|
38
|
+
}
|
|
39
|
+
return lines.join("\n");
|
|
40
|
+
}
|
|
41
|
+
export function createReadTool(executor, options = {}) {
|
|
42
|
+
const securityConfig = options.securityConfig ?? DEFAULT_SECURITY_CONFIG;
|
|
43
|
+
const securityContext = options.securityContext ?? {
|
|
44
|
+
workspaceDir: process.cwd(),
|
|
45
|
+
workspacePath: process.cwd(),
|
|
46
|
+
cwd: process.cwd(),
|
|
47
|
+
};
|
|
29
48
|
return {
|
|
30
49
|
name: "read",
|
|
31
50
|
label: "read",
|
|
32
51
|
description: `Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, output is truncated to ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Use offset/limit for large files.`,
|
|
33
52
|
parameters: readSchema,
|
|
34
53
|
execute: async (_toolCallId, { path, offset, limit }, signal) => {
|
|
54
|
+
if (securityConfig.enabled && securityConfig.pathGuard.enabled) {
|
|
55
|
+
const guardResult = guardPath(path, "read", { ...securityContext, config: securityConfig.pathGuard });
|
|
56
|
+
if (!guardResult.allowed) {
|
|
57
|
+
logSecurityEvent(securityContext.workspaceDir, securityConfig, {
|
|
58
|
+
type: "path",
|
|
59
|
+
tool: "read",
|
|
60
|
+
channelId: options.channelId,
|
|
61
|
+
rawPath: path,
|
|
62
|
+
operation: "read",
|
|
63
|
+
resolvedPath: guardResult.resolvedPath,
|
|
64
|
+
category: guardResult.category,
|
|
65
|
+
reason: guardResult.reason,
|
|
66
|
+
});
|
|
67
|
+
throw new Error(formatPathBlockMessage(guardResult.resolvedPath, guardResult.category, guardResult.reason));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
35
70
|
const mimeType = isImageFile(path);
|
|
36
71
|
if (mimeType) {
|
|
37
72
|
// Read as image (binary) - use base64
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { Executor } from "../sandbox.js";
|
|
2
|
+
import type { SecurityConfig, SecurityRuntimeContext } from "../security/types.js";
|
|
2
3
|
export declare function writeContent(executor: Executor, path: string, content: string, signal: AbortSignal | undefined, options?: {
|
|
3
4
|
createParentDir?: boolean;
|
|
5
|
+
securityConfig?: SecurityConfig;
|
|
6
|
+
securityContext?: SecurityRuntimeContext;
|
|
7
|
+
channelId?: string;
|
|
8
|
+
toolName?: string;
|
|
4
9
|
}): Promise<void>;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
+
import { DEFAULT_SECURITY_CONFIG } from "../security/config.js";
|
|
2
|
+
import { logSecurityEvent } from "../security/logger.js";
|
|
3
|
+
import { guardPath } from "../security/path-guard.js";
|
|
1
4
|
import { shellEscape } from "../shared/shell-escape.js";
|
|
2
|
-
const INLINE_WRITE_MAX_BYTES = 64 * 1024;
|
|
3
5
|
function getDir(path) {
|
|
4
6
|
return path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : ".";
|
|
5
7
|
}
|
|
6
|
-
function isInlineSafe(content) {
|
|
7
|
-
return Buffer.byteLength(content, "utf-8") <= INLINE_WRITE_MAX_BYTES;
|
|
8
|
-
}
|
|
9
8
|
function ensureSuccess(result, path) {
|
|
10
9
|
if (result.code !== 0) {
|
|
11
10
|
throw new Error(result.stderr || `Failed to write file: ${path}`);
|
|
@@ -13,14 +12,36 @@ function ensureSuccess(result, path) {
|
|
|
13
12
|
}
|
|
14
13
|
export async function writeContent(executor, path, content, signal, options) {
|
|
15
14
|
const createParentDir = options?.createParentDir ?? false;
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
const securityConfig = options?.securityConfig ?? DEFAULT_SECURITY_CONFIG;
|
|
16
|
+
const securityContext = options?.securityContext ?? {
|
|
17
|
+
workspaceDir: process.cwd(),
|
|
18
|
+
workspacePath: process.cwd(),
|
|
19
|
+
cwd: process.cwd(),
|
|
20
|
+
};
|
|
21
|
+
if (securityConfig.enabled && securityConfig.pathGuard.enabled) {
|
|
22
|
+
const guardResult = guardPath(path, "write", { ...securityContext, config: securityConfig.pathGuard });
|
|
23
|
+
if (!guardResult.allowed) {
|
|
24
|
+
logSecurityEvent(securityContext.workspaceDir, securityConfig, {
|
|
25
|
+
type: "path",
|
|
26
|
+
tool: options?.toolName ?? "write",
|
|
27
|
+
channelId: options?.channelId,
|
|
28
|
+
rawPath: path,
|
|
29
|
+
operation: "write",
|
|
30
|
+
resolvedPath: guardResult.resolvedPath,
|
|
31
|
+
category: guardResult.category,
|
|
32
|
+
reason: guardResult.reason,
|
|
33
|
+
});
|
|
34
|
+
const lines = [`Path blocked${guardResult.category ? ` [${guardResult.category}]` : ""}`];
|
|
35
|
+
if (guardResult.reason) {
|
|
36
|
+
lines.push(`Reason: ${guardResult.reason}`);
|
|
37
|
+
}
|
|
38
|
+
if (guardResult.resolvedPath) {
|
|
39
|
+
lines.push(`Resolved path: ${guardResult.resolvedPath}`);
|
|
40
|
+
}
|
|
41
|
+
throw new Error(lines.join("\n"));
|
|
42
|
+
}
|
|
23
43
|
}
|
|
44
|
+
const dirPrefix = createParentDir ? `mkdir -p ${shellEscape(getDir(path))} && ` : "";
|
|
24
45
|
const result = await executor.exec(`${dirPrefix}cat > ${shellEscape(path)}`, {
|
|
25
46
|
signal,
|
|
26
47
|
stdin: content,
|
package/dist/tools/write.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import type { AgentTool } from "@mariozechner/pi-agent-core";
|
|
2
2
|
import type { Executor } from "../sandbox.js";
|
|
3
|
+
import type { SecurityConfig, SecurityRuntimeContext } from "../security/types.js";
|
|
3
4
|
declare const writeSchema: import("@sinclair/typebox").TObject<{
|
|
4
5
|
label: import("@sinclair/typebox").TString;
|
|
5
6
|
path: import("@sinclair/typebox").TString;
|
|
6
7
|
content: import("@sinclair/typebox").TString;
|
|
7
8
|
}>;
|
|
8
|
-
export
|
|
9
|
+
export interface WriteToolOptions {
|
|
10
|
+
securityConfig?: SecurityConfig;
|
|
11
|
+
securityContext?: SecurityRuntimeContext;
|
|
12
|
+
channelId?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function createWriteTool(executor: Executor, options?: WriteToolOptions): AgentTool<typeof writeSchema>;
|
|
9
15
|
export {};
|
package/dist/tools/write.js
CHANGED
|
@@ -5,16 +5,23 @@ const writeSchema = Type.Object({
|
|
|
5
5
|
path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
|
|
6
6
|
content: Type.String({ description: "Content to write to the file" }),
|
|
7
7
|
});
|
|
8
|
-
export function createWriteTool(executor) {
|
|
8
|
+
export function createWriteTool(executor, options = {}) {
|
|
9
9
|
return {
|
|
10
10
|
name: "write",
|
|
11
11
|
label: "write",
|
|
12
12
|
description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
|
|
13
13
|
parameters: writeSchema,
|
|
14
14
|
execute: async (_toolCallId, { path, content }, signal) => {
|
|
15
|
-
await writeContent(executor, path, content, signal, {
|
|
15
|
+
await writeContent(executor, path, content, signal, {
|
|
16
|
+
createParentDir: true,
|
|
17
|
+
securityConfig: options.securityConfig,
|
|
18
|
+
securityContext: options.securityContext,
|
|
19
|
+
channelId: options.channelId,
|
|
20
|
+
toolName: "write",
|
|
21
|
+
});
|
|
22
|
+
const bytesWritten = Buffer.byteLength(content, "utf-8");
|
|
16
23
|
return {
|
|
17
|
-
content: [{ type: "text", text: `Successfully wrote ${
|
|
24
|
+
content: [{ type: "text", text: `Successfully wrote ${bytesWritten} bytes to ${path}` }],
|
|
18
25
|
details: undefined,
|
|
19
26
|
};
|
|
20
27
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oyasmi/pipiclaw",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "An AI assistant runtime for coding and team workflows, with DingTalk AI Cards, sub-agents, memory, and scheduled events.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"lint": "biome check .",
|
|
28
28
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
29
29
|
"test": "vitest --run",
|
|
30
|
+
"test:e2e": "vitest --run --config vitest.config.e2e.ts",
|
|
30
31
|
"test:coverage": "vitest --run --coverage",
|
|
31
32
|
"check": "npm run lint && npm run typecheck && npm run test",
|
|
32
33
|
"prepublishOnly": "npm run clean && npm run build && npm run check"
|