@chengchenccc/oh-my-agent 0.1.1-rc.1
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 +79 -0
- package/dist/cli/args.d.ts +24 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +120 -0
- package/dist/cli/initial-input.d.ts +32 -0
- package/dist/cli/initial-input.d.ts.map +1 -0
- package/dist/cli/initial-input.js +131 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +3 -0
- package/dist/core/__fixtures__/echo-model.d.ts +6 -0
- package/dist/core/__fixtures__/echo-model.d.ts.map +1 -0
- package/dist/core/__fixtures__/echo-model.js +10 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts +2 -0
- package/dist/core/__fixtures__/mcp-echo-server.d.ts.map +1 -0
- package/dist/core/__fixtures__/mcp-echo-server.js +54 -0
- package/dist/core/agent-runtime.d.ts +21 -0
- package/dist/core/agent-runtime.d.ts.map +1 -0
- package/dist/core/agent-runtime.js +14 -0
- package/dist/core/memory/autonomous-memory.d.ts +21 -0
- package/dist/core/memory/autonomous-memory.d.ts.map +1 -0
- package/dist/core/memory/autonomous-memory.js +202 -0
- package/dist/core/persistence/in-memory-session-store.d.ts +3 -0
- package/dist/core/persistence/in-memory-session-store.d.ts.map +1 -0
- package/dist/core/persistence/in-memory-session-store.js +109 -0
- package/dist/core/persistence/session-store.contract.d.ts +5 -0
- package/dist/core/persistence/session-store.contract.d.ts.map +1 -0
- package/dist/core/persistence/session-store.contract.js +183 -0
- package/dist/core/persistence/session-store.d.ts +33 -0
- package/dist/core/persistence/session-store.d.ts.map +1 -0
- package/dist/core/persistence/session-store.js +22 -0
- package/dist/core/persistence/session-tree.d.ts +49 -0
- package/dist/core/persistence/session-tree.d.ts.map +1 -0
- package/dist/core/persistence/session-tree.js +1 -0
- package/dist/core/plugins/plugin-code.d.ts +13 -0
- package/dist/core/plugins/plugin-code.d.ts.map +1 -0
- package/dist/core/plugins/plugin-code.js +62 -0
- package/dist/core/plugins/plugin-marketplace.d.ts +72 -0
- package/dist/core/plugins/plugin-marketplace.d.ts.map +1 -0
- package/dist/core/plugins/plugin-marketplace.js +340 -0
- package/dist/core/plugins/plugin-resolve.d.ts +32 -0
- package/dist/core/plugins/plugin-resolve.d.ts.map +1 -0
- package/dist/core/plugins/plugin-resolve.js +101 -0
- package/dist/core/plugins/plugin-trust.d.ts +24 -0
- package/dist/core/plugins/plugin-trust.d.ts.map +1 -0
- package/dist/core/plugins/plugin-trust.js +89 -0
- package/dist/core/runtime/agent-event.d.ts +98 -0
- package/dist/core/runtime/agent-event.d.ts.map +1 -0
- package/dist/core/runtime/agent-event.js +1 -0
- package/dist/core/runtime/agent-loop.d.ts +142 -0
- package/dist/core/runtime/agent-loop.d.ts.map +1 -0
- package/dist/core/runtime/agent-loop.js +1008 -0
- package/dist/core/runtime/approval.d.ts +23 -0
- package/dist/core/runtime/approval.d.ts.map +1 -0
- package/dist/core/runtime/approval.js +25 -0
- package/dist/core/runtime/compaction.d.ts +22 -0
- package/dist/core/runtime/compaction.d.ts.map +1 -0
- package/dist/core/runtime/compaction.js +90 -0
- package/dist/core/runtime/context-estimate.d.ts +42 -0
- package/dist/core/runtime/context-estimate.d.ts.map +1 -0
- package/dist/core/runtime/context-estimate.js +34 -0
- package/dist/core/runtime/create-runtime.d.ts +81 -0
- package/dist/core/runtime/create-runtime.d.ts.map +1 -0
- package/dist/core/runtime/create-runtime.js +250 -0
- package/dist/core/runtime/fake-provider.d.ts +3 -0
- package/dist/core/runtime/fake-provider.d.ts.map +1 -0
- package/dist/core/runtime/fake-provider.js +87 -0
- package/dist/core/runtime/logger.d.ts +13 -0
- package/dist/core/runtime/logger.d.ts.map +1 -0
- package/dist/core/runtime/logger.js +38 -0
- package/dist/core/runtime/loop-input.d.ts +39 -0
- package/dist/core/runtime/loop-input.d.ts.map +1 -0
- package/dist/core/runtime/loop-input.js +38 -0
- package/dist/core/runtime/message-cache.d.ts +16 -0
- package/dist/core/runtime/message-cache.d.ts.map +1 -0
- package/dist/core/runtime/message-cache.js +23 -0
- package/dist/core/runtime/model-catalog.d.ts +10 -0
- package/dist/core/runtime/model-catalog.d.ts.map +1 -0
- package/dist/core/runtime/model-catalog.js +14 -0
- package/dist/core/runtime/plugin-runtime.d.ts +41 -0
- package/dist/core/runtime/plugin-runtime.d.ts.map +1 -0
- package/dist/core/runtime/plugin-runtime.js +1 -0
- package/dist/core/runtime/plugin.d.ts +86 -0
- package/dist/core/runtime/plugin.d.ts.map +1 -0
- package/dist/core/runtime/plugin.js +23 -0
- package/dist/core/runtime/process-tree.d.ts +12 -0
- package/dist/core/runtime/process-tree.d.ts.map +1 -0
- package/dist/core/runtime/process-tree.js +40 -0
- package/dist/core/runtime/prompt.d.ts +23 -0
- package/dist/core/runtime/prompt.d.ts.map +1 -0
- package/dist/core/runtime/prompt.js +29 -0
- package/dist/core/runtime/prompts.d.ts +16 -0
- package/dist/core/runtime/prompts.d.ts.map +1 -0
- package/dist/core/runtime/prompts.js +36 -0
- package/dist/core/runtime/retry.d.ts +20 -0
- package/dist/core/runtime/retry.d.ts.map +1 -0
- package/dist/core/runtime/retry.js +60 -0
- package/dist/core/runtime/run-runtime.d.ts +93 -0
- package/dist/core/runtime/run-runtime.d.ts.map +1 -0
- package/dist/core/runtime/run-runtime.js +664 -0
- package/dist/core/runtime/runtime-catalog.d.ts +12 -0
- package/dist/core/runtime/runtime-catalog.d.ts.map +1 -0
- package/dist/core/runtime/runtime-catalog.js +89 -0
- package/dist/core/runtime/stream-rules.d.ts +9 -0
- package/dist/core/runtime/stream-rules.d.ts.map +1 -0
- package/dist/core/runtime/stream-rules.js +44 -0
- package/dist/core/runtime/title.d.ts +7 -0
- package/dist/core/runtime/title.d.ts.map +1 -0
- package/dist/core/runtime/title.js +114 -0
- package/dist/core/runtime/todo.d.ts +16 -0
- package/dist/core/runtime/todo.d.ts.map +1 -0
- package/dist/core/runtime/todo.js +40 -0
- package/dist/core/runtime/tool-filter.d.ts +12 -0
- package/dist/core/runtime/tool-filter.d.ts.map +1 -0
- package/dist/core/runtime/tool-filter.js +23 -0
- package/dist/core/runtime/tool-pruning.d.ts +29 -0
- package/dist/core/runtime/tool-pruning.d.ts.map +1 -0
- package/dist/core/runtime/tool-pruning.js +105 -0
- package/dist/core/session/input-history.d.ts +8 -0
- package/dist/core/session/input-history.d.ts.map +1 -0
- package/dist/core/session/input-history.js +41 -0
- package/dist/core/session/session-file.d.ts +84 -0
- package/dist/core/session/session-file.d.ts.map +1 -0
- package/dist/core/session/session-file.js +392 -0
- package/dist/core/session/session-loop.d.ts +33 -0
- package/dist/core/session/session-loop.d.ts.map +1 -0
- package/dist/core/session/session-loop.js +30 -0
- package/dist/core/settings/project-settings.d.ts +46 -0
- package/dist/core/settings/project-settings.d.ts.map +1 -0
- package/dist/core/settings/project-settings.js +85 -0
- package/dist/core/settings/workspace-context.d.ts +10 -0
- package/dist/core/settings/workspace-context.d.ts.map +1 -0
- package/dist/core/settings/workspace-context.js +48 -0
- package/dist/core/tools/agent-fs-like.d.ts +15 -0
- package/dist/core/tools/agent-fs-like.d.ts.map +1 -0
- package/dist/core/tools/agent-fs-like.js +4 -0
- package/dist/core/tools/ask-question.d.ts +5 -0
- package/dist/core/tools/ask-question.d.ts.map +1 -0
- package/dist/core/tools/ask-question.js +51 -0
- package/dist/core/tools/bash.d.ts +5 -0
- package/dist/core/tools/bash.d.ts.map +1 -0
- package/dist/core/tools/bash.js +109 -0
- package/dist/core/tools/eval.d.ts +10 -0
- package/dist/core/tools/eval.d.ts.map +1 -0
- package/dist/core/tools/eval.js +83 -0
- package/dist/core/tools/file-tools.d.ts +18 -0
- package/dist/core/tools/file-tools.d.ts.map +1 -0
- package/dist/core/tools/file-tools.js +249 -0
- package/dist/core/tools/glob.d.ts +5 -0
- package/dist/core/tools/glob.d.ts.map +1 -0
- package/dist/core/tools/glob.js +54 -0
- package/dist/core/tools/grep.d.ts +5 -0
- package/dist/core/tools/grep.d.ts.map +1 -0
- package/dist/core/tools/grep.js +61 -0
- package/dist/core/tools/index.d.ts +14 -0
- package/dist/core/tools/index.d.ts.map +1 -0
- package/dist/core/tools/index.js +14 -0
- package/dist/core/tools/ls-tree.d.ts +10 -0
- package/dist/core/tools/ls-tree.d.ts.map +1 -0
- package/dist/core/tools/ls-tree.js +177 -0
- package/dist/core/tools/mcp-mount.d.ts +63 -0
- package/dist/core/tools/mcp-mount.d.ts.map +1 -0
- package/dist/core/tools/mcp-mount.js +287 -0
- package/dist/core/tools/read-image.d.ts +6 -0
- package/dist/core/tools/read-image.d.ts.map +1 -0
- package/dist/core/tools/read-image.js +98 -0
- package/dist/core/tools/skill.d.ts +11 -0
- package/dist/core/tools/skill.d.ts.map +1 -0
- package/dist/core/tools/skill.js +65 -0
- package/dist/core/tools/skills.d.ts +10 -0
- package/dist/core/tools/skills.d.ts.map +1 -0
- package/dist/core/tools/skills.js +116 -0
- package/dist/core/tools/todo-store.d.ts +8 -0
- package/dist/core/tools/todo-store.d.ts.map +1 -0
- package/dist/core/tools/todo-store.js +100 -0
- package/dist/core/tools/todo.d.ts +8 -0
- package/dist/core/tools/todo.d.ts.map +1 -0
- package/dist/core/tools/todo.js +91 -0
- package/dist/core/tools/url-guard.d.ts +12 -0
- package/dist/core/tools/url-guard.d.ts.map +1 -0
- package/dist/core/tools/url-guard.js +58 -0
- package/dist/core/tools/web-ports-std.d.ts +6 -0
- package/dist/core/tools/web-ports-std.d.ts.map +1 -0
- package/dist/core/tools/web-ports-std.js +145 -0
- package/dist/core/tools/web-ports.d.ts +56 -0
- package/dist/core/tools/web-ports.d.ts.map +1 -0
- package/dist/core/tools/web-ports.js +62 -0
- package/dist/core/tools/workspace-sandbox.d.ts +20 -0
- package/dist/core/tools/workspace-sandbox.d.ts.map +1 -0
- package/dist/core/tools/workspace-sandbox.js +84 -0
- package/dist/core/workflow/subagent-registry.d.ts +40 -0
- package/dist/core/workflow/subagent-registry.d.ts.map +1 -0
- package/dist/core/workflow/subagent-registry.js +118 -0
- package/dist/core/workflow/workflow-evaluator.d.ts +23 -0
- package/dist/core/workflow/workflow-evaluator.d.ts.map +1 -0
- package/dist/core/workflow/workflow-evaluator.js +21 -0
- package/dist/core/workflow/workflow-executor.d.ts +108 -0
- package/dist/core/workflow/workflow-executor.d.ts.map +1 -0
- package/dist/core/workflow/workflow-executor.js +549 -0
- package/dist/core/workflow/workflow-tools.d.ts +50 -0
- package/dist/core/workflow/workflow-tools.d.ts.map +1 -0
- package/dist/core/workflow/workflow-tools.js +217 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/main.d.ts +9 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +97 -0
- package/dist/modes/json-mode.d.ts +5 -0
- package/dist/modes/json-mode.d.ts.map +1 -0
- package/dist/modes/json-mode.js +46 -0
- package/dist/modes/print-mode.d.ts +20 -0
- package/dist/modes/print-mode.d.ts.map +1 -0
- package/dist/modes/print-mode.js +82 -0
- package/dist/modes/rpc/jsonl.d.ts +23 -0
- package/dist/modes/rpc/jsonl.d.ts.map +1 -0
- package/dist/modes/rpc/jsonl.js +88 -0
- package/dist/modes/rpc/rpc-mode.d.ts +18 -0
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-mode.js +326 -0
- package/dist/modes/tui/settings-overlay.d.ts +23 -0
- package/dist/modes/tui/settings-overlay.d.ts.map +1 -0
- package/dist/modes/tui/settings-overlay.js +136 -0
- package/dist/modes/tui/tui-commands-session.d.ts +3 -0
- package/dist/modes/tui/tui-commands-session.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands-session.js +191 -0
- package/dist/modes/tui/tui-commands.d.ts +52 -0
- package/dist/modes/tui/tui-commands.d.ts.map +1 -0
- package/dist/modes/tui/tui-commands.js +373 -0
- package/dist/modes/tui/tui-components.d.ts +81 -0
- package/dist/modes/tui/tui-components.d.ts.map +1 -0
- package/dist/modes/tui/tui-components.js +248 -0
- package/dist/modes/tui/tui-format.d.ts +65 -0
- package/dist/modes/tui/tui-format.d.ts.map +1 -0
- package/dist/modes/tui/tui-format.js +246 -0
- package/dist/modes/tui/tui-frame-provider.d.ts +16 -0
- package/dist/modes/tui/tui-frame-provider.d.ts.map +1 -0
- package/dist/modes/tui/tui-frame-provider.js +31 -0
- package/dist/modes/tui/tui-interactive.d.ts +16 -0
- package/dist/modes/tui/tui-interactive.d.ts.map +1 -0
- package/dist/modes/tui/tui-interactive.js +123 -0
- package/dist/modes/tui/tui-io.d.ts +12 -0
- package/dist/modes/tui/tui-io.d.ts.map +1 -0
- package/dist/modes/tui/tui-io.js +620 -0
- package/dist/modes/tui/tui-mode.d.ts +120 -0
- package/dist/modes/tui/tui-mode.d.ts.map +1 -0
- package/dist/modes/tui/tui-mode.js +391 -0
- package/dist/modes/tui/tui-render.d.ts +54 -0
- package/dist/modes/tui/tui-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-render.js +399 -0
- package/dist/modes/tui/tui-slash.d.ts +12 -0
- package/dist/modes/tui/tui-slash.d.ts.map +1 -0
- package/dist/modes/tui/tui-slash.js +64 -0
- package/dist/modes/tui/tui-tool-render.d.ts +6 -0
- package/dist/modes/tui/tui-tool-render.d.ts.map +1 -0
- package/dist/modes/tui/tui-tool-render.js +79 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts +25 -0
- package/dist/modes/tui/tui-transcript-reconciler.d.ts.map +1 -0
- package/dist/modes/tui/tui-transcript-reconciler.js +97 -0
- package/dist/modes/tui/view-state.d.ts +67 -0
- package/dist/modes/tui/view-state.d.ts.map +1 -0
- package/dist/modes/tui/view-state.js +353 -0
- package/dist/prompts/agents/subagent.md +26 -0
- package/dist/prompts/system/base.md +20 -0
- package/dist/prompts/system/memory.md +23 -0
- package/dist/prompts/system/safety.md +12 -0
- package/dist/protocol/index.d.ts +5 -0
- package/dist/protocol/index.d.ts.map +1 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/mapping.d.ts +25 -0
- package/dist/protocol/mapping.d.ts.map +1 -0
- package/dist/protocol/mapping.js +115 -0
- package/dist/protocol/transport.d.ts +1396 -0
- package/dist/protocol/transport.d.ts.map +1 -0
- package/dist/protocol/transport.js +170 -0
- package/package.json +49 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { WorkspaceSandbox } from "./workspace-sandbox.js";
|
|
3
|
+
const MEDIA_TYPES = {
|
|
4
|
+
".png": "image/png",
|
|
5
|
+
".jpg": "image/jpeg",
|
|
6
|
+
".jpeg": "image/jpeg",
|
|
7
|
+
".gif": "image/gif",
|
|
8
|
+
".webp": "image/webp",
|
|
9
|
+
};
|
|
10
|
+
/** Magic-byte sniffing: extension lies often enough to reject valid files. */
|
|
11
|
+
function sniffMediaType(bytes) {
|
|
12
|
+
if (bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47)
|
|
13
|
+
return "image/png";
|
|
14
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff)
|
|
15
|
+
return "image/jpeg";
|
|
16
|
+
if (bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46)
|
|
17
|
+
return "image/gif";
|
|
18
|
+
const riff = String.fromCharCode(...bytes.slice(0, 4));
|
|
19
|
+
const webp = String.fromCharCode(...bytes.slice(8, 12));
|
|
20
|
+
if (riff === "RIFF" && webp === "WEBP")
|
|
21
|
+
return "image/webp";
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
/** Max image bytes returned to the model (base64 inflates ~1.33x). */
|
|
25
|
+
const MAX_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
26
|
+
/** read_image: return a workspace image as a vision tool result block. */
|
|
27
|
+
export function createReadImageTool(opts) {
|
|
28
|
+
const { cwd } = opts;
|
|
29
|
+
const sandbox = new WorkspaceSandbox(cwd);
|
|
30
|
+
return {
|
|
31
|
+
name: "read_image",
|
|
32
|
+
description: "Read an image file (png/jpeg/gif/webp) from the workspace and attach it for vision analysis. " +
|
|
33
|
+
"Use this instead of `read` when the goal is to understand an image (screenshots, diagrams, photos).",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
path: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Path to the image file, relative to workspace root",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ["path"],
|
|
43
|
+
},
|
|
44
|
+
async execute(input, _signal) {
|
|
45
|
+
const args = input;
|
|
46
|
+
const raw = args.path;
|
|
47
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
48
|
+
return { content: "Error: path is required", isError: true };
|
|
49
|
+
}
|
|
50
|
+
let abs;
|
|
51
|
+
try {
|
|
52
|
+
// validate() resolves + realpath-checks, catching sibling-prefix
|
|
53
|
+
// paths and symlinks pointing outside the workspace.
|
|
54
|
+
abs = sandbox.validate(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return { content: `Error: path escapes workspace: ${raw}`, isError: true };
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const st = statSync(abs);
|
|
61
|
+
if (!st.isFile())
|
|
62
|
+
return { content: `Error: not a file: ${raw}`, isError: true };
|
|
63
|
+
if (st.size > MAX_IMAGE_BYTES) {
|
|
64
|
+
return {
|
|
65
|
+
content: `Error: image too large (${st.size} bytes > 5MB)`,
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const bytes = new Uint8Array(readFileSync(abs));
|
|
70
|
+
const ext = abs.slice(abs.lastIndexOf(".")).toLowerCase();
|
|
71
|
+
const mediaType = sniffMediaType(bytes) ?? MEDIA_TYPES[ext];
|
|
72
|
+
if (!mediaType) {
|
|
73
|
+
return { content: `Error: unsupported image format: ${raw}`, isError: true };
|
|
74
|
+
}
|
|
75
|
+
let binary = "";
|
|
76
|
+
for (const byte of bytes)
|
|
77
|
+
binary += String.fromCharCode(byte);
|
|
78
|
+
return {
|
|
79
|
+
content: `[image ${raw} attached for vision]`,
|
|
80
|
+
mediaType,
|
|
81
|
+
images: [
|
|
82
|
+
{
|
|
83
|
+
type: "image",
|
|
84
|
+
mediaType: mediaType,
|
|
85
|
+
base64: btoa(binary),
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return {
|
|
92
|
+
content: `Error: read_image failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
93
|
+
isError: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Plugin } from "../agent-runtime.js";
|
|
2
|
+
export interface SkillOptions {
|
|
3
|
+
readonly roots: readonly string[];
|
|
4
|
+
}
|
|
5
|
+
/** Create the oma skill module: Meta index + skill_load tool.
|
|
6
|
+
* Absorbed from @chengchenccc/plugin-progressive-skill; the shared index
|
|
7
|
+
* builder now lives in ./skills.ts (tools-common merged into oma). Skill loading is
|
|
8
|
+
* progressive by definition: the index stays in context, the body loads
|
|
9
|
+
* on demand. */
|
|
10
|
+
export declare function createSkill(opts: SkillOptions): Plugin;
|
|
11
|
+
//# sourceMappingURL=skill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../../src/core/tools/skill.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAuB,MAAM,EAAc,MAAM,qBAAqB,CAAC;AAGnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAWD;;;;iBAIiB;AACjB,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAmDtD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { buildSkillIndex } from "./index.js";
|
|
4
|
+
function isWithinRoot(root, target) {
|
|
5
|
+
try {
|
|
6
|
+
const real = realpathSync(target);
|
|
7
|
+
return real === root || real.startsWith(`${root}/`);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/** Create the oma skill module: Meta index + skill_load tool.
|
|
14
|
+
* Absorbed from @chengchenccc/plugin-progressive-skill; the shared index
|
|
15
|
+
* builder now lives in ./skills.ts (tools-common merged into oma). Skill loading is
|
|
16
|
+
* progressive by definition: the index stays in context, the body loads
|
|
17
|
+
* on demand. */
|
|
18
|
+
export function createSkill(opts) {
|
|
19
|
+
const index = buildSkillIndex(opts.roots);
|
|
20
|
+
const metaProvider = {
|
|
21
|
+
name: "Skills",
|
|
22
|
+
render() {
|
|
23
|
+
if (index.length === 0)
|
|
24
|
+
return "No skills available.";
|
|
25
|
+
return index.map((s) => `- **${s.name}**: ${s.description}`).join("\n");
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
const skillLoadTool = {
|
|
29
|
+
name: "skill_load",
|
|
30
|
+
description: "Load the full body of a skill by name. Only the name/description is in the index; use this to read the actual instructions. Relative paths in the body resolve against the returned `dir`.",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: { name: { type: "string", description: "Skill name from the index" } },
|
|
34
|
+
required: ["name"],
|
|
35
|
+
},
|
|
36
|
+
async execute(args) {
|
|
37
|
+
const name = args.name;
|
|
38
|
+
if (typeof name !== "string")
|
|
39
|
+
return { error: "Skill name must be a string" };
|
|
40
|
+
const entry = index.find((s) => s.name === name);
|
|
41
|
+
if (!entry)
|
|
42
|
+
return { error: `Skill "${name}" not found` };
|
|
43
|
+
const fullPath = resolve(entry.root, entry.relativePath);
|
|
44
|
+
if (!isWithinRoot(entry.root, fullPath)) {
|
|
45
|
+
return { error: "Path escape detected" };
|
|
46
|
+
}
|
|
47
|
+
if (!existsSync(fullPath))
|
|
48
|
+
return { error: `Skill file not found` };
|
|
49
|
+
const content = readFileSync(fullPath, "utf8");
|
|
50
|
+
const body = content.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
51
|
+
const skillDir = fullPath.slice(0, fullPath.lastIndexOf("/"));
|
|
52
|
+
return {
|
|
53
|
+
name: entry.name,
|
|
54
|
+
dir: skillDir,
|
|
55
|
+
hint: "Resolve relative paths and scripts against `dir`.",
|
|
56
|
+
body: body.replaceAll("${SKILL_DIR}", skillDir),
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
name: "progressive-skill",
|
|
62
|
+
tools: [skillLoadTool],
|
|
63
|
+
meta: [metaProvider],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SkillIndexEntry {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly description: string;
|
|
4
|
+
readonly root: string;
|
|
5
|
+
readonly relativePath: string;
|
|
6
|
+
}
|
|
7
|
+
/** Scan configured roots for SKILL.md files, parse frontmatter for
|
|
8
|
+
* name + description. Deterministic order by root then name. */
|
|
9
|
+
export declare function buildSkillIndex(roots: readonly string[]): SkillIndexEntry[];
|
|
10
|
+
//# sourceMappingURL=skills.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../../src/core/tools/skills.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AA2BD;iEACiE;AACjE,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,EAAE,CAgB3E"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
function isWithinRoot(root, target) {
|
|
4
|
+
try {
|
|
5
|
+
const real = realpathSync(target);
|
|
6
|
+
return real === root || real.startsWith(`${root}/`);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Canonicalize a skill root: resolve symlinks so realpath(target) comparisons
|
|
13
|
+
* work on platforms where the root itself is a symlink (e.g. macOS /tmp →
|
|
14
|
+
* /private/tmp). Returns null if the root does not exist or is not a dir. */
|
|
15
|
+
function canonicalRoot(root) {
|
|
16
|
+
try {
|
|
17
|
+
const real = realpathSync(root);
|
|
18
|
+
return statSync(real).isDirectory() ? real : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Safety bound: symlink containment + total entry count, not directory
|
|
25
|
+
* depth — deep but bounded trees are legitimate skill packs. */
|
|
26
|
+
const MAX_ENTRIES = 1000;
|
|
27
|
+
/** Scan configured roots for SKILL.md files, parse frontmatter for
|
|
28
|
+
* name + description. Deterministic order by root then name. */
|
|
29
|
+
export function buildSkillIndex(roots) {
|
|
30
|
+
// Roots are ordered by precedence (earliest = highest priority for the
|
|
31
|
+
// Product Skill Pack contract). A later root overrides an earlier one for
|
|
32
|
+
// the same skill name; the final index keeps exactly one entry per name.
|
|
33
|
+
const byName = new Map();
|
|
34
|
+
for (const root of roots) {
|
|
35
|
+
const canonical = canonicalRoot(root);
|
|
36
|
+
if (!canonical)
|
|
37
|
+
continue;
|
|
38
|
+
const scanned = [];
|
|
39
|
+
scanDir(canonical, canonical, scanned);
|
|
40
|
+
for (const entry of scanned) {
|
|
41
|
+
byName.set(entry.name, entry);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Deterministic Meta order: sort by name.
|
|
45
|
+
return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
46
|
+
}
|
|
47
|
+
function scanDir(root, currentDir, entries) {
|
|
48
|
+
let items;
|
|
49
|
+
try {
|
|
50
|
+
items = readdirSync(currentDir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const item of items) {
|
|
56
|
+
if (entries.length >= MAX_ENTRIES) {
|
|
57
|
+
throw new Error("skill pack contains too many skills (limit 1000)");
|
|
58
|
+
}
|
|
59
|
+
const fullPath = join(currentDir, item);
|
|
60
|
+
if (!isWithinRoot(root, fullPath))
|
|
61
|
+
continue;
|
|
62
|
+
if (item === "SKILL.md") {
|
|
63
|
+
const parsed = parseFrontmatter(fullPath);
|
|
64
|
+
if (parsed) {
|
|
65
|
+
entries.push({
|
|
66
|
+
name: parsed.name,
|
|
67
|
+
description: parsed.description,
|
|
68
|
+
root,
|
|
69
|
+
relativePath: fullPath.slice(root.length + 1),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
if (statSync(fullPath).isDirectory())
|
|
76
|
+
scanDir(root, fullPath, entries);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
/* not a dir */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function parseFrontmatter(path) {
|
|
84
|
+
try {
|
|
85
|
+
const content = readFileSync(path, "utf8");
|
|
86
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
87
|
+
if (!match)
|
|
88
|
+
return null;
|
|
89
|
+
const frontmatter = match[1] ?? "";
|
|
90
|
+
const name = frontmatter.match(/name:\s*(.+)/)?.[1]?.trim();
|
|
91
|
+
if (!name)
|
|
92
|
+
return null;
|
|
93
|
+
return { name, description: parseDescription(frontmatter) };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Minimal YAML folded-block support for `description: >` / `description: |`:
|
|
100
|
+
* join the following indented lines. Plain single-line descriptions are
|
|
101
|
+
* returned unchanged. (No YAML parser dependency for two fields.) */
|
|
102
|
+
function parseDescription(frontmatter) {
|
|
103
|
+
const line = frontmatter.match(/^description:\s*(.*)$/m)?.[1] ?? "";
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
if (trimmed !== ">" && trimmed !== "|")
|
|
106
|
+
return trimmed;
|
|
107
|
+
const lines = frontmatter.split("\n");
|
|
108
|
+
const index = lines.findIndex((l) => l.startsWith("description:"));
|
|
109
|
+
const body = [];
|
|
110
|
+
for (const l of lines.slice(index + 1)) {
|
|
111
|
+
if (!/^\s+/.test(l))
|
|
112
|
+
break;
|
|
113
|
+
body.push(l.trim());
|
|
114
|
+
}
|
|
115
|
+
return body.join(" ");
|
|
116
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { SessionStore, TodoItem } from "../agent-runtime.js";
|
|
2
|
+
export declare function readTodoFile(workspaceRoot: string): readonly TodoItem[];
|
|
3
|
+
export declare function writeTodoFile(workspaceRoot: string, items: readonly TodoItem[]): void;
|
|
4
|
+
/** SessionStore shim that satisfies the todo plugin's read/write contract,
|
|
5
|
+
* but persists todo entries to the workspace file instead of the per-Run
|
|
6
|
+
* in-memory branch. Every other SessionStore method is a no-op. */
|
|
7
|
+
export declare function createFileTodoStore(workspaceRoot: string): SessionStore;
|
|
8
|
+
//# sourceMappingURL=todo-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo-store.d.ts","sourceRoot":"","sources":["../../../src/core/tools/todo-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAGV,YAAY,EACZ,QAAQ,EACT,MAAM,qBAAqB,CAAC;AAqB7B,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,QAAQ,EAAE,CAUvE;AAED,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,QAAQ,EAAE,GAAG,IAAI,CAIrF;AAED;;oEAEoE;AACpE,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,YAAY,CAyDvE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Standalone oma's local todo store: a single `.oma/todo.json` in the
|
|
4
|
+
* workspace, so todo_write persists across TUI runs (unlike the per-Run
|
|
5
|
+
* in-memory SessionStore that backend product tools use). Backend-invoked
|
|
6
|
+
* RPC mode gets todo via the backend-injected MCP, never this file. */
|
|
7
|
+
const TODO_REL_PATH = ".oma/todo.json";
|
|
8
|
+
function isTodoItem(value) {
|
|
9
|
+
return (typeof value === "object" &&
|
|
10
|
+
value !== null &&
|
|
11
|
+
"id" in value &&
|
|
12
|
+
typeof value.id === "string" &&
|
|
13
|
+
"text" in value &&
|
|
14
|
+
typeof value.text === "string" &&
|
|
15
|
+
"status" in value &&
|
|
16
|
+
typeof value.status === "string");
|
|
17
|
+
}
|
|
18
|
+
export function readTodoFile(workspaceRoot) {
|
|
19
|
+
const path = join(workspaceRoot, TODO_REL_PATH);
|
|
20
|
+
if (!existsSync(path))
|
|
21
|
+
return [];
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
24
|
+
if (!Array.isArray(parsed.items))
|
|
25
|
+
return [];
|
|
26
|
+
return parsed.items.filter(isTodoItem);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function writeTodoFile(workspaceRoot, items) {
|
|
33
|
+
const path = join(workspaceRoot, TODO_REL_PATH);
|
|
34
|
+
mkdirSync(join(workspaceRoot, ".oma"), { recursive: true });
|
|
35
|
+
writeFileSync(path, `${JSON.stringify({ items }, null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
/** SessionStore shim that satisfies the todo plugin's read/write contract,
|
|
38
|
+
* but persists todo entries to the workspace file instead of the per-Run
|
|
39
|
+
* in-memory branch. Every other SessionStore method is a no-op. */
|
|
40
|
+
export function createFileTodoStore(workspaceRoot) {
|
|
41
|
+
return {
|
|
42
|
+
async create() {
|
|
43
|
+
// Nothing to create; the file is written lazily on first todo_write.
|
|
44
|
+
},
|
|
45
|
+
async open() {
|
|
46
|
+
const snapshot = {
|
|
47
|
+
metadata: {
|
|
48
|
+
sessionId: "",
|
|
49
|
+
backendKind: "oma",
|
|
50
|
+
workspaceRoot,
|
|
51
|
+
leafEntryId: null,
|
|
52
|
+
createdAt: 0,
|
|
53
|
+
updatedAt: 0,
|
|
54
|
+
},
|
|
55
|
+
entries: [],
|
|
56
|
+
operations: [],
|
|
57
|
+
};
|
|
58
|
+
return snapshot;
|
|
59
|
+
},
|
|
60
|
+
async delete() {
|
|
61
|
+
// todo.json is shared across sessions in a workspace; /delete stays local.
|
|
62
|
+
},
|
|
63
|
+
async appendBatch(_sessionId, input) {
|
|
64
|
+
for (const entry of input.entries) {
|
|
65
|
+
if (entry.type !== "todo")
|
|
66
|
+
continue;
|
|
67
|
+
if (!("state" in entry) || typeof entry.state !== "object" || entry.state === null)
|
|
68
|
+
continue;
|
|
69
|
+
const state = entry.state;
|
|
70
|
+
if (!("items" in state) || !Array.isArray(state.items))
|
|
71
|
+
continue;
|
|
72
|
+
const items = state.items.filter(isTodoItem);
|
|
73
|
+
writeTodoFile(workspaceRoot, items);
|
|
74
|
+
}
|
|
75
|
+
return { appendedIds: input.entries.map((_, i) => `todo-${i}`) };
|
|
76
|
+
},
|
|
77
|
+
async readBranch() {
|
|
78
|
+
const items = readTodoFile(workspaceRoot);
|
|
79
|
+
if (items.length === 0)
|
|
80
|
+
return [];
|
|
81
|
+
const entry = {
|
|
82
|
+
type: "todo",
|
|
83
|
+
entryId: "todo-file",
|
|
84
|
+
parentId: null,
|
|
85
|
+
state: { items },
|
|
86
|
+
createdAt: Date.now(),
|
|
87
|
+
};
|
|
88
|
+
return [entry];
|
|
89
|
+
},
|
|
90
|
+
async moveLeaf() {
|
|
91
|
+
// No leaf operations on a file-backed todo store.
|
|
92
|
+
},
|
|
93
|
+
async findByProductEntryIds() {
|
|
94
|
+
return [];
|
|
95
|
+
},
|
|
96
|
+
async close() {
|
|
97
|
+
// No resources to release.
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Plugin, PluginTool, SessionStore } from "../agent-runtime.js";
|
|
2
|
+
export interface TodoPluginOptions {
|
|
3
|
+
readonly sessionId: string;
|
|
4
|
+
readonly store: SessionStore;
|
|
5
|
+
}
|
|
6
|
+
export declare function createTodo(opts: TodoPluginOptions): Plugin;
|
|
7
|
+
export declare function createTodoReadTool(opts: TodoPluginOptions): PluginTool;
|
|
8
|
+
//# sourceMappingURL=todo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"todo.d.ts","sourceRoot":"","sources":["../../../src/core/tools/todo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAY,MAAM,qBAAqB,CAAC;AAGtF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAC;CAC9B;AA8DD,wBAAgB,UAAU,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAsB1D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,UAAU,CAUtE"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { readTodo, writeTodo } from "../agent-runtime.js";
|
|
2
|
+
function isTodoItemShape(value) {
|
|
3
|
+
if (typeof value !== "object" || value === null)
|
|
4
|
+
return false;
|
|
5
|
+
return ("id" in value &&
|
|
6
|
+
typeof value.id === "string" &&
|
|
7
|
+
"text" in value &&
|
|
8
|
+
typeof value.text === "string" &&
|
|
9
|
+
"status" in value &&
|
|
10
|
+
typeof value.status === "string");
|
|
11
|
+
}
|
|
12
|
+
function toTodoItem(item) {
|
|
13
|
+
const status = item.status;
|
|
14
|
+
if (status !== "pending" &&
|
|
15
|
+
status !== "in_progress" &&
|
|
16
|
+
status !== "done" &&
|
|
17
|
+
status !== "cancelled") {
|
|
18
|
+
return { id: item.id, text: item.text, status: "pending" };
|
|
19
|
+
}
|
|
20
|
+
return { id: item.id, text: item.text, status };
|
|
21
|
+
}
|
|
22
|
+
function createTodoWriteTool(opts) {
|
|
23
|
+
return {
|
|
24
|
+
name: "todo_write",
|
|
25
|
+
description: "Update the task list. Provide the full desired state.",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
items: {
|
|
30
|
+
type: "array",
|
|
31
|
+
items: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
id: { type: "string" },
|
|
35
|
+
text: { type: "string" },
|
|
36
|
+
status: { type: "string", enum: ["pending", "in_progress", "done", "cancelled"] },
|
|
37
|
+
},
|
|
38
|
+
required: ["id", "text", "status"],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ["items"],
|
|
43
|
+
},
|
|
44
|
+
async execute(args) {
|
|
45
|
+
const rawItems = args.items;
|
|
46
|
+
if (!Array.isArray(rawItems))
|
|
47
|
+
return { error: "items must be an array" };
|
|
48
|
+
const typed = rawItems.filter(isTodoItemShape);
|
|
49
|
+
const items = typed.map(toTodoItem);
|
|
50
|
+
await writeTodo(opts.store, opts.sessionId, { items });
|
|
51
|
+
return { items };
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function createTodo(opts) {
|
|
56
|
+
return {
|
|
57
|
+
name: "todo",
|
|
58
|
+
tools: [createTodoWriteTool(opts)],
|
|
59
|
+
hooks: {
|
|
60
|
+
afterTool(toolName, result) {
|
|
61
|
+
if (toolName !== "todo_write")
|
|
62
|
+
return undefined;
|
|
63
|
+
if (typeof result !== "object" || result === null)
|
|
64
|
+
return undefined;
|
|
65
|
+
if (!("items" in result) || !Array.isArray(result.items))
|
|
66
|
+
return undefined;
|
|
67
|
+
const items = result.items.filter(isTodoItemShape).map(toTodoItem);
|
|
68
|
+
return { type: "todo_update", items };
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
meta: [
|
|
72
|
+
{
|
|
73
|
+
name: "Todo",
|
|
74
|
+
render() {
|
|
75
|
+
return "Use todo_write to track tasks. State persists across sessions.";
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function createTodoReadTool(opts) {
|
|
82
|
+
return {
|
|
83
|
+
name: "todo_read",
|
|
84
|
+
description: "Read the current task list.",
|
|
85
|
+
inputSchema: { type: "object", properties: {} },
|
|
86
|
+
async execute() {
|
|
87
|
+
const state = await readTodo(opts.store, opts.sessionId);
|
|
88
|
+
return { items: state.items };
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class UrlGuardError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
/** Validate URL is safe to fetch. Throws UrlGuardError if not. */
|
|
5
|
+
export declare function assertSafeUrl(rawUrl: string): URL;
|
|
6
|
+
/** Max redirect count for fetch safety */
|
|
7
|
+
export declare const MAX_REDIRECTS = 5;
|
|
8
|
+
/** Max response bytes before cancellation */
|
|
9
|
+
export declare const MAX_RESPONSE_BYTES = 20000;
|
|
10
|
+
/** Fetch timeout in ms */
|
|
11
|
+
export declare const FETCH_TIMEOUT_MS = 10000;
|
|
12
|
+
//# sourceMappingURL=url-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url-guard.d.ts","sourceRoot":"","sources":["../../../src/core/tools/url-guard.ts"],"names":[],"mappings":"AA+BA,qBAAa,aAAc,SAAQ,KAAK;gBAC1B,OAAO,EAAE,MAAM;CAI5B;AAED,kEAAkE;AAClE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAiBjD;AAED,0CAA0C;AAC1C,eAAO,MAAM,aAAa,IAAI,CAAC;AAC/B,6CAA6C;AAC7C,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC,0BAA0B;AAC1B,eAAO,MAAM,gBAAgB,QAAS,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const BLOCKED_HOSTS = new Set(["localhost", "127.0.0.1", "0.0.0.0", "[::1]", "169.254.169.254"]);
|
|
2
|
+
const BLOCKED_PREFIXES = [
|
|
3
|
+
"127.",
|
|
4
|
+
"10.",
|
|
5
|
+
"192.168.",
|
|
6
|
+
"172.16.",
|
|
7
|
+
"172.17.",
|
|
8
|
+
"172.18.",
|
|
9
|
+
"172.19.",
|
|
10
|
+
"172.20.",
|
|
11
|
+
"172.21.",
|
|
12
|
+
"172.22.",
|
|
13
|
+
"172.23.",
|
|
14
|
+
"172.24.",
|
|
15
|
+
"172.25.",
|
|
16
|
+
"172.26.",
|
|
17
|
+
"172.27.",
|
|
18
|
+
"172.28.",
|
|
19
|
+
"172.29.",
|
|
20
|
+
"172.30.",
|
|
21
|
+
"172.31.",
|
|
22
|
+
];
|
|
23
|
+
function isPrivateIP(hostname) {
|
|
24
|
+
if (BLOCKED_HOSTS.has(hostname))
|
|
25
|
+
return true;
|
|
26
|
+
if (hostname.startsWith("["))
|
|
27
|
+
return hostname === "[::1]" || hostname.startsWith("[fc") || hostname.startsWith("[fd");
|
|
28
|
+
return BLOCKED_PREFIXES.some((p) => hostname.startsWith(p));
|
|
29
|
+
}
|
|
30
|
+
export class UrlGuardError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "UrlGuardError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Validate URL is safe to fetch. Throws UrlGuardError if not. */
|
|
37
|
+
export function assertSafeUrl(rawUrl) {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = new URL(rawUrl);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new UrlGuardError(`Invalid URL: ${rawUrl}`);
|
|
44
|
+
}
|
|
45
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
46
|
+
throw new UrlGuardError(`Blocked protocol: ${parsed.protocol}`);
|
|
47
|
+
}
|
|
48
|
+
if (isPrivateIP(parsed.hostname)) {
|
|
49
|
+
throw new UrlGuardError(`Blocked host: ${parsed.hostname}`);
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
/** Max redirect count for fetch safety */
|
|
54
|
+
export const MAX_REDIRECTS = 5;
|
|
55
|
+
/** Max response bytes before cancellation */
|
|
56
|
+
export const MAX_RESPONSE_BYTES = 20_000;
|
|
57
|
+
/** Fetch timeout in ms */
|
|
58
|
+
export const FETCH_TIMEOUT_MS = 10_000;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WebFetchPort, WebSearchPort } from "./web-ports.js";
|
|
2
|
+
/** Std fetch port: global fetch, manual redirect walk (each hop re-validated
|
|
3
|
+
* against the url guard), timeout + size caps, HTML→text. */
|
|
4
|
+
export declare function createStdWebFetchPort(): WebFetchPort;
|
|
5
|
+
export declare function createDdgWebSearchPort(): WebSearchPort;
|
|
6
|
+
//# sourceMappingURL=web-ports-std.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-ports-std.d.ts","sourceRoot":"","sources":["../../../src/core/tools/web-ports-std.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AA8BlE;8DAC8D;AAC9D,wBAAgB,qBAAqB,IAAI,YAAY,CAqCpD;AA8DD,wBAAgB,sBAAsB,IAAI,aAAa,CAmBtD"}
|