@vtstech/pi-shared 1.2.2 → 1.2.3
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/config-io.js +9 -19
- package/debug.js +1 -2
- package/errors.js +12 -14
- package/format.js +0 -1
- package/model-test-utils.js +14 -15
- package/ollama.js +10 -20
- package/package.json +7 -5
- package/provider-sync.js +0 -1
- package/react-parser.js +12 -13
- package/security.js +45 -78
- package/test-report.js +3 -36
- package/types.js +1 -0
package/config-io.js
CHANGED
|
@@ -1,18 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import * as
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
// shared/debug.ts
|
|
7
|
-
var DEBUG_ENABLED = process.env.PI_EXTENSIONS_DEBUG === "1";
|
|
8
|
-
function debugLog(module, message, ...args) {
|
|
9
|
-
if (!DEBUG_ENABLED) return;
|
|
10
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11
|
-
console.debug(`[pi-ext:${module}] ${timestamp} ${message}`, ...args);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// shared/config-io.ts
|
|
15
|
-
var PI_AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import { debugLog } from "./debug";
|
|
5
|
+
const PI_AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
16
6
|
function readJsonConfig(filePath, defaultValue = {}) {
|
|
17
7
|
try {
|
|
18
8
|
if (fs.existsSync(filePath)) {
|
|
@@ -35,10 +25,10 @@ function writeJsonConfig(filePath, data) {
|
|
|
35
25
|
fs.writeFileSync(filePath, content, "utf-8");
|
|
36
26
|
}
|
|
37
27
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
const SETTINGS_PATH = path.join(PI_AGENT_DIR, "settings.json");
|
|
29
|
+
const SECURITY_PATH = path.join(PI_AGENT_DIR, "security.json");
|
|
30
|
+
const REACT_MODE_PATH = path.join(PI_AGENT_DIR, "react-mode.json");
|
|
31
|
+
const MODEL_TEST_CONFIG_PATH = path.join(PI_AGENT_DIR, "model-test-config.json");
|
|
42
32
|
function readSettings() {
|
|
43
33
|
return readJsonConfig(SETTINGS_PATH);
|
|
44
34
|
}
|
package/debug.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
var DEBUG_ENABLED = process.env.PI_EXTENSIONS_DEBUG === "1";
|
|
1
|
+
const DEBUG_ENABLED = process?.env?.PI_EXTENSIONS_DEBUG === "1";
|
|
3
2
|
function debugLog(module, message, ...args) {
|
|
4
3
|
if (!DEBUG_ENABLED) return;
|
|
5
4
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
package/errors.js
CHANGED
|
@@ -1,51 +1,49 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
|
|
5
|
-
// shared/errors.ts
|
|
6
|
-
var ExtensionError = class extends Error {
|
|
4
|
+
class ExtensionError extends Error {
|
|
7
5
|
constructor(message, code) {
|
|
8
6
|
super(message);
|
|
9
7
|
__publicField(this, "code", code);
|
|
10
8
|
this.name = "ExtensionError";
|
|
11
9
|
}
|
|
12
|
-
}
|
|
13
|
-
|
|
10
|
+
}
|
|
11
|
+
class ConfigError extends ExtensionError {
|
|
14
12
|
constructor(message) {
|
|
15
13
|
super(message, "CONFIG_ERROR");
|
|
16
14
|
this.name = "ConfigError";
|
|
17
15
|
}
|
|
18
|
-
}
|
|
19
|
-
|
|
16
|
+
}
|
|
17
|
+
class ApiError extends ExtensionError {
|
|
20
18
|
constructor(message, statusCode, url) {
|
|
21
19
|
super(message, "API_ERROR");
|
|
22
20
|
__publicField(this, "statusCode", statusCode);
|
|
23
21
|
__publicField(this, "url", url);
|
|
24
22
|
this.name = "ApiError";
|
|
25
23
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
24
|
+
}
|
|
25
|
+
class ExtensionTimeoutError extends ExtensionError {
|
|
28
26
|
constructor(message, timeoutMs) {
|
|
29
27
|
super(message, "TIMEOUT");
|
|
30
28
|
__publicField(this, "timeoutMs", timeoutMs);
|
|
31
29
|
this.name = "ExtensionTimeoutError";
|
|
32
30
|
}
|
|
33
|
-
}
|
|
34
|
-
|
|
31
|
+
}
|
|
32
|
+
class SecurityError extends ExtensionError {
|
|
35
33
|
constructor(message, rule, detail) {
|
|
36
34
|
super(message, "SECURITY_VIOLATION");
|
|
37
35
|
__publicField(this, "rule", rule);
|
|
38
36
|
__publicField(this, "detail", detail);
|
|
39
37
|
this.name = "SecurityError";
|
|
40
38
|
}
|
|
41
|
-
}
|
|
42
|
-
|
|
39
|
+
}
|
|
40
|
+
class ToolError extends ExtensionError {
|
|
43
41
|
constructor(message, toolName) {
|
|
44
42
|
super(message, "TOOL_ERROR");
|
|
45
43
|
__publicField(this, "toolName", toolName);
|
|
46
44
|
this.name = "ToolError";
|
|
47
45
|
}
|
|
48
|
-
}
|
|
46
|
+
}
|
|
49
47
|
export {
|
|
50
48
|
ApiError,
|
|
51
49
|
ConfigError,
|
package/format.js
CHANGED
package/model-test-utils.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
// shared/model-test-utils.ts
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
5
|
-
|
|
4
|
+
const CONFIG = {
|
|
6
5
|
// General API settings
|
|
7
6
|
DEFAULT_TIMEOUT_MS: 999999,
|
|
8
7
|
// ~16.7 minutes — effectively unlimited for slow models
|
|
@@ -41,8 +40,8 @@ var CONFIG = {
|
|
|
41
40
|
TEST_DELAY_MS: 1e4
|
|
42
41
|
// 10 seconds between tests to avoid rate limiting
|
|
43
42
|
};
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const TEST_CONFIG_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
44
|
+
const TEST_CONFIG_PATH = path.join(TEST_CONFIG_DIR, "model-test-config.json");
|
|
46
45
|
function readTestConfig() {
|
|
47
46
|
try {
|
|
48
47
|
if (fs.existsSync(TEST_CONFIG_PATH)) {
|
|
@@ -70,7 +69,7 @@ function getEffectiveConfig() {
|
|
|
70
69
|
TEMPERATURE: userConfig.temperature ?? CONFIG.TEMPERATURE
|
|
71
70
|
};
|
|
72
71
|
}
|
|
73
|
-
|
|
72
|
+
const WEATHER_TOOL_DEFINITION = {
|
|
74
73
|
type: "function",
|
|
75
74
|
function: {
|
|
76
75
|
name: "get_weather",
|
|
@@ -145,9 +144,9 @@ function parseTextToolCall(content) {
|
|
|
145
144
|
const { name: _, ...fnArgs } = rawArgs;
|
|
146
145
|
return { fnName: textToolParsed.name, args: fnArgs };
|
|
147
146
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
const TOOL_SUPPORT_CACHE_DIR = path.join(os.homedir(), ".pi", "agent", "cache");
|
|
148
|
+
const TOOL_SUPPORT_CACHE_PATH = path.join(TOOL_SUPPORT_CACHE_DIR, "tool_support.json");
|
|
149
|
+
let _toolSupportCacheInMemory = null;
|
|
151
150
|
function readToolSupportCache() {
|
|
152
151
|
try {
|
|
153
152
|
if (fs.existsSync(TOOL_SUPPORT_CACHE_PATH)) {
|
|
@@ -182,10 +181,10 @@ function cacheToolSupport(model, support, family) {
|
|
|
182
181
|
_toolSupportCacheInMemory = cache;
|
|
183
182
|
writeToolSupportCache(cache);
|
|
184
183
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
184
|
+
const TEST_HISTORY_DIR = path.join(os.homedir(), ".pi", "agent", "cache");
|
|
185
|
+
const TEST_HISTORY_PATH = path.join(TEST_HISTORY_DIR, "model-test-history.json");
|
|
186
|
+
const MAX_HISTORY_PER_MODEL = 50;
|
|
187
|
+
const MAX_HISTORY_TOTAL = 500;
|
|
189
188
|
function readTestHistory() {
|
|
190
189
|
try {
|
|
191
190
|
if (fs.existsSync(TEST_HISTORY_PATH)) {
|
|
@@ -267,9 +266,9 @@ function detectRegression(model, current) {
|
|
|
267
266
|
}
|
|
268
267
|
return regressions;
|
|
269
268
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
const REASONING_PROMPT = `A snail climbs 3 feet up a wall each day, but slides back 2 feet each night. The wall is 10 feet tall. How many days does it take the snail to reach the top? Think step by step and give the final answer on its own line like: ANSWER: <number>`;
|
|
270
|
+
const TOOL_SYSTEM_PROMPT = "You are a helpful assistant. Use the available tools when needed.";
|
|
271
|
+
const TOOL_USER_PROMPT = "What's the weather like in Paris right now?";
|
|
273
272
|
async function testToolUsageUnified(chatFn, model, options) {
|
|
274
273
|
const tools = options?.tools || [WEATHER_TOOL_DEFINITION];
|
|
275
274
|
const systemPrompt = options?.systemPrompt || TOOL_SYSTEM_PROMPT;
|
package/ollama.js
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
|
-
// shared/ollama.ts
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import os from "node:os";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
console.debug(`[pi-ext:${module}] ${timestamp} ${message}`, ...args);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// shared/ollama.ts
|
|
15
|
-
var EXTENSION_VERSION = "1.2.2";
|
|
16
|
-
var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
17
|
-
var _modelsJsonCache = null;
|
|
18
|
-
var _ollamaBaseUrlCache = null;
|
|
19
|
-
var CACHE_TTL_MS = 2e3;
|
|
4
|
+
import { debugLog } from "./debug";
|
|
5
|
+
const EXTENSION_VERSION = "1.2.3";
|
|
6
|
+
const MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
7
|
+
let _modelsJsonCache = null;
|
|
8
|
+
let _ollamaBaseUrlCache = null;
|
|
9
|
+
const CACHE_TTL_MS = 2e3;
|
|
20
10
|
function getOllamaBaseUrl() {
|
|
21
11
|
const now = Date.now();
|
|
22
12
|
if (_ollamaBaseUrlCache && now - _ollamaBaseUrlCache.ts < CACHE_TTL_MS) return _ollamaBaseUrlCache.data;
|
|
@@ -71,7 +61,7 @@ function writeModelsJson(data) {
|
|
|
71
61
|
_modelsJsonCache = null;
|
|
72
62
|
_ollamaBaseUrlCache = null;
|
|
73
63
|
}
|
|
74
|
-
|
|
64
|
+
let _modelsJsonLock = null;
|
|
75
65
|
async function acquireModelsJsonLock() {
|
|
76
66
|
while (_modelsJsonLock) {
|
|
77
67
|
await _modelsJsonLock;
|
|
@@ -99,7 +89,7 @@ async function readModifyWriteModelsJson(modifier) {
|
|
|
99
89
|
release();
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
|
-
|
|
92
|
+
const DEFAULT_RETRY_OPTIONS = {
|
|
103
93
|
maxRetries: 2,
|
|
104
94
|
baseDelayMs: 1e3,
|
|
105
95
|
maxDelayMs: 1e4,
|
|
@@ -111,7 +101,7 @@ function backoffDelay(attempt, baseDelayMs, maxDelayMs) {
|
|
|
111
101
|
const jitter = delay * 0.25 * (Math.random() * 2 - 1);
|
|
112
102
|
return Math.max(0, Math.round(delay + jitter));
|
|
113
103
|
}
|
|
114
|
-
|
|
104
|
+
const RETRYABLE_ERROR_PATTERNS = [
|
|
115
105
|
"ECONNREFUSED",
|
|
116
106
|
"ECONNRESET",
|
|
117
107
|
"ENOTFOUND",
|
|
@@ -203,7 +193,7 @@ function isReasoningModel(name) {
|
|
|
203
193
|
const lower = name.toLowerCase();
|
|
204
194
|
return lower.includes("deepseek-r1") || lower.includes("qwq") || /\bo1\b/.test(lower) || /\bo3\b/.test(lower) || lower.includes("qwen3") || lower.includes("reasoning") || lower.includes("thinker") || lower.includes("thinking");
|
|
205
195
|
}
|
|
206
|
-
|
|
196
|
+
const BUILTIN_PROVIDERS = {
|
|
207
197
|
openrouter: { api: "openai-completions", baseUrl: "https://openrouter.ai/api/v1", envKey: "OPENROUTER_API_KEY" },
|
|
208
198
|
anthropic: { api: "anthropic-messages", baseUrl: "https://api.anthropic.com/v1", envKey: "ANTHROPIC_API_KEY" },
|
|
209
199
|
google: { api: "gemini", baseUrl: "https://generativelanguage.googleapis.com", envKey: "GOOGLE_API_KEY" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtstech/pi-shared",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
4
4
|
"description": "Shared utilities for Pi Coding Agent extensions",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./config-io": "./config-io.js",
|
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
"./test-report": "./test-report.js",
|
|
16
16
|
"./types": "./types.js"
|
|
17
17
|
},
|
|
18
|
-
"keywords": [
|
|
18
|
+
"keywords": [
|
|
19
|
+
"pi-package",
|
|
20
|
+
"pi",
|
|
21
|
+
"pi-coding-agent",
|
|
22
|
+
"pi-extensions"
|
|
23
|
+
],
|
|
19
24
|
"license": "MIT",
|
|
20
25
|
"access": "public",
|
|
21
26
|
"type": "module",
|
|
@@ -24,8 +29,5 @@
|
|
|
24
29
|
"repository": {
|
|
25
30
|
"type": "git",
|
|
26
31
|
"url": "https://github.com/VTSTech/pi-coding-agent"
|
|
27
|
-
},
|
|
28
|
-
"peerDependencies": {
|
|
29
|
-
"@mariozechner/pi-coding-agent": ">=0.66"
|
|
30
32
|
}
|
|
31
33
|
}
|
package/provider-sync.js
CHANGED
package/react-parser.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// shared/react-parser.ts
|
|
2
1
|
function sanitizeModelJson(text) {
|
|
3
2
|
text = text.replace(/:\s*True\b/g, ": true");
|
|
4
3
|
text = text.replace(/:\s*False\b/g, ": false");
|
|
@@ -11,7 +10,7 @@ function sanitizeModelJson(text) {
|
|
|
11
10
|
text = text.replace(/\\\\\\\\/g, "\\\\");
|
|
12
11
|
return text;
|
|
13
12
|
}
|
|
14
|
-
|
|
13
|
+
const REACT_DIALECTS = [
|
|
15
14
|
{
|
|
16
15
|
name: "react",
|
|
17
16
|
actionTag: "Action:",
|
|
@@ -69,14 +68,14 @@ function buildDialectPatterns(d) {
|
|
|
69
68
|
const finalAnswerRe = fT ? new RegExp(`${fT}\\s*([\\s\\S]*?)$`, "i") : void 0;
|
|
70
69
|
return { thoughtRe, actionRe, actionReSameline, actionReLoose, actionReParen, finalAnswerRe, dialect: d };
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
const ALL_DIALECT_PATTERNS = REACT_DIALECTS.map(buildDialectPatterns);
|
|
72
|
+
const CLASSIC_PATTERNS = ALL_DIALECT_PATTERNS[0];
|
|
73
|
+
const THOUGHT_RE = CLASSIC_PATTERNS.thoughtRe;
|
|
74
|
+
const ACTION_RE = CLASSIC_PATTERNS.actionRe;
|
|
75
|
+
const ACTION_RE_SAMELINE = CLASSIC_PATTERNS.actionReSameline;
|
|
76
|
+
const ACTION_RE_LOOSE = CLASSIC_PATTERNS.actionReLoose;
|
|
77
|
+
const ACTION_RE_PAREN = CLASSIC_PATTERNS.actionReParen;
|
|
78
|
+
const FINAL_ANSWER_RE = CLASSIC_PATTERNS.finalAnswerRe;
|
|
80
79
|
function extractJsonArgs(rawArgs) {
|
|
81
80
|
const start = rawArgs.indexOf("{");
|
|
82
81
|
if (start === -1) return null;
|
|
@@ -262,8 +261,8 @@ function extractToolFromJson(obj) {
|
|
|
262
261
|
if (!name || typeof args !== "object" || args === null) return null;
|
|
263
262
|
return { name, args };
|
|
264
263
|
}
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
const FUZZY_MIN_PREFIX_LENGTH = 4;
|
|
265
|
+
const WORD_MAPPINGS = {
|
|
267
266
|
calculate: ["calculator"],
|
|
268
267
|
calc: ["calculator"],
|
|
269
268
|
math: ["calculator"],
|
|
@@ -315,7 +314,7 @@ function fuzzyMatchToolName(hallucinated, availableTools) {
|
|
|
315
314
|
}
|
|
316
315
|
return null;
|
|
317
316
|
}
|
|
318
|
-
|
|
317
|
+
const ARG_ALIASES = {
|
|
319
318
|
expression: ["expr", "exp", "formula", "calculation", "math"],
|
|
320
319
|
file_path: ["path", "filepath", "file", "filename", "location"],
|
|
321
320
|
content: ["text", "data", "body", "value"],
|
package/security.js
CHANGED
|
@@ -1,59 +1,26 @@
|
|
|
1
|
-
// shared/security.ts
|
|
2
|
-
import * as fs2 from "node:fs";
|
|
3
|
-
import * as path2 from "node:path";
|
|
4
|
-
import os2 from "node:os";
|
|
5
|
-
|
|
6
|
-
// shared/debug.ts
|
|
7
|
-
var DEBUG_ENABLED = process.env.PI_EXTENSIONS_DEBUG === "1";
|
|
8
|
-
function debugLog(module, message, ...args) {
|
|
9
|
-
if (!DEBUG_ENABLED) return;
|
|
10
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
11
|
-
console.debug(`[pi-ext:${module}] ${timestamp} ${message}`, ...args);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// shared/security.ts
|
|
15
|
-
import dns from "node:dns";
|
|
16
|
-
|
|
17
|
-
// shared/config-io.ts
|
|
18
1
|
import * as fs from "node:fs";
|
|
19
2
|
import * as path from "node:path";
|
|
20
3
|
import os from "node:os";
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
fs.renameSync(tmpPath, filePath);
|
|
30
|
-
} catch {
|
|
31
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
var SETTINGS_PATH = path.join(PI_AGENT_DIR, "settings.json");
|
|
35
|
-
var SECURITY_PATH = path.join(PI_AGENT_DIR, "security.json");
|
|
36
|
-
var REACT_MODE_PATH = path.join(PI_AGENT_DIR, "react-mode.json");
|
|
37
|
-
var MODEL_TEST_CONFIG_PATH = path.join(PI_AGENT_DIR, "model-test-config.json");
|
|
38
|
-
|
|
39
|
-
// shared/security.ts
|
|
40
|
-
var SETTINGS_PATH2 = SETTINGS_PATH;
|
|
41
|
-
var SECURITY_CONFIG_PATH = SECURITY_PATH;
|
|
42
|
-
var securityModeCache = null;
|
|
43
|
-
var securityModeCacheTime = 0;
|
|
44
|
-
var SECURITY_CACHE_DURATION_MS = 3e4;
|
|
4
|
+
import { debugLog } from "./debug";
|
|
5
|
+
import dns from "node:dns";
|
|
6
|
+
import { SETTINGS_PATH as _SETTINGS_PATH, SECURITY_PATH, writeJsonConfig } from "./config-io";
|
|
7
|
+
const SETTINGS_PATH = _SETTINGS_PATH;
|
|
8
|
+
const SECURITY_CONFIG_PATH = SECURITY_PATH;
|
|
9
|
+
let securityModeCache = null;
|
|
10
|
+
let securityModeCacheTime = 0;
|
|
11
|
+
const SECURITY_CACHE_DURATION_MS = 3e4;
|
|
45
12
|
function getSecurityMode() {
|
|
46
13
|
const now = Date.now();
|
|
47
14
|
if (securityModeCache && now - securityModeCacheTime < SECURITY_CACHE_DURATION_MS) {
|
|
48
15
|
return securityModeCache;
|
|
49
16
|
}
|
|
50
17
|
try {
|
|
51
|
-
if (!
|
|
18
|
+
if (!fs.existsSync(SECURITY_CONFIG_PATH)) {
|
|
52
19
|
securityModeCache = "max";
|
|
53
20
|
securityModeCacheTime = now;
|
|
54
21
|
return "max";
|
|
55
22
|
}
|
|
56
|
-
const raw =
|
|
23
|
+
const raw = fs.readFileSync(SECURITY_CONFIG_PATH, "utf-8");
|
|
57
24
|
const config = JSON.parse(raw);
|
|
58
25
|
if (config.mode === "basic" || config.mode === "max" || config.mode === "off") {
|
|
59
26
|
securityModeCache = config.mode;
|
|
@@ -74,7 +41,7 @@ function setSecurityMode(mode) {
|
|
|
74
41
|
try {
|
|
75
42
|
const config = { mode, lastUpdated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
76
43
|
writeJsonConfig(SECURITY_CONFIG_PATH, config);
|
|
77
|
-
const verify = JSON.parse(
|
|
44
|
+
const verify = JSON.parse(fs.readFileSync(SECURITY_CONFIG_PATH, "utf-8"));
|
|
78
45
|
if (verify.mode !== mode) {
|
|
79
46
|
debugLog("security", `security config write verification failed: expected ${mode}, got ${verify.mode}`);
|
|
80
47
|
return false;
|
|
@@ -86,7 +53,7 @@ function setSecurityMode(mode) {
|
|
|
86
53
|
return false;
|
|
87
54
|
}
|
|
88
55
|
}
|
|
89
|
-
|
|
56
|
+
const CRITICAL_COMMANDS = /* @__PURE__ */ new Set([
|
|
90
57
|
// Filesystem destruction (irrecoverable)
|
|
91
58
|
"mkfs",
|
|
92
59
|
"dd",
|
|
@@ -138,7 +105,7 @@ var CRITICAL_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
138
105
|
"chown",
|
|
139
106
|
"chmod"
|
|
140
107
|
]);
|
|
141
|
-
|
|
108
|
+
const EXTENDED_COMMANDS = /* @__PURE__ */ new Set([
|
|
142
109
|
// File deletion
|
|
143
110
|
"rm",
|
|
144
111
|
"rmdir",
|
|
@@ -172,11 +139,11 @@ var EXTENDED_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
172
139
|
// Version control
|
|
173
140
|
"git"
|
|
174
141
|
]);
|
|
175
|
-
|
|
142
|
+
const BLOCKED_COMMANDS = /* @__PURE__ */ new Set([
|
|
176
143
|
...CRITICAL_COMMANDS,
|
|
177
144
|
...EXTENDED_COMMANDS
|
|
178
145
|
]);
|
|
179
|
-
|
|
146
|
+
const BLOCKED_URL_ALWAYS = /* @__PURE__ */ new Set([
|
|
180
147
|
// Cloud metadata endpoints
|
|
181
148
|
"169.254.169.254",
|
|
182
149
|
// RFC1918 private ranges
|
|
@@ -205,7 +172,7 @@ var BLOCKED_URL_ALWAYS = /* @__PURE__ */ new Set([
|
|
|
205
172
|
"private.",
|
|
206
173
|
"intranet."
|
|
207
174
|
]);
|
|
208
|
-
|
|
175
|
+
const BLOCKED_URL_MAX_ONLY = /* @__PURE__ */ new Set([
|
|
209
176
|
// Loopback addresses (full 127.0.0.0/8 range)
|
|
210
177
|
"localhost",
|
|
211
178
|
"127.",
|
|
@@ -235,11 +202,11 @@ var BLOCKED_URL_MAX_ONLY = /* @__PURE__ */ new Set([
|
|
|
235
202
|
// Local/internal patterns
|
|
236
203
|
"local."
|
|
237
204
|
]);
|
|
238
|
-
|
|
205
|
+
const BLOCKED_URL_PATTERNS = /* @__PURE__ */ new Set([
|
|
239
206
|
...BLOCKED_URL_ALWAYS,
|
|
240
207
|
...BLOCKED_URL_MAX_ONLY
|
|
241
208
|
]);
|
|
242
|
-
|
|
209
|
+
const CRITICAL_SYSTEM_DIRS = [
|
|
243
210
|
"/etc",
|
|
244
211
|
"/root",
|
|
245
212
|
"/var",
|
|
@@ -261,9 +228,9 @@ function validatePath(filePath, allowedDirs) {
|
|
|
261
228
|
}
|
|
262
229
|
let resolved;
|
|
263
230
|
try {
|
|
264
|
-
resolved =
|
|
231
|
+
resolved = path.resolve(filePath);
|
|
265
232
|
try {
|
|
266
|
-
resolved =
|
|
233
|
+
resolved = fs.realpathSync(resolved);
|
|
267
234
|
} catch {
|
|
268
235
|
}
|
|
269
236
|
} catch {
|
|
@@ -279,9 +246,9 @@ function validatePath(filePath, allowedDirs) {
|
|
|
279
246
|
"/etc/passwd",
|
|
280
247
|
"/.ssh/",
|
|
281
248
|
"/.gnupg/",
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
249
|
+
path.join(os.homedir(), ".ssh"),
|
|
250
|
+
path.join(os.homedir(), ".gnupg"),
|
|
251
|
+
SETTINGS_PATH,
|
|
285
252
|
SECURITY_CONFIG_PATH
|
|
286
253
|
// NOTE: models.json is intentionally excluded from sensitivePaths.
|
|
287
254
|
// Extensions use readModelsJson()/writeModelsJson() from shared/ollama.ts
|
|
@@ -301,7 +268,7 @@ function validatePath(filePath, allowedDirs) {
|
|
|
301
268
|
if (allowedDirs) {
|
|
302
269
|
for (const dir of allowedDirs) {
|
|
303
270
|
try {
|
|
304
|
-
const absDir =
|
|
271
|
+
const absDir = path.resolve(dir);
|
|
305
272
|
if (resolved.startsWith(absDir)) return { valid: true, error: "" };
|
|
306
273
|
} catch {
|
|
307
274
|
}
|
|
@@ -332,10 +299,10 @@ function isPrivateIp(ip) {
|
|
|
332
299
|
}
|
|
333
300
|
async function resolveAndCheckHostname(hostname, blockPrivate = true) {
|
|
334
301
|
try {
|
|
335
|
-
const addresses = await new Promise((
|
|
302
|
+
const addresses = await new Promise((resolve, reject) => {
|
|
336
303
|
dns.lookup(hostname, { all: true }, (err, addresses2) => {
|
|
337
304
|
if (err) reject(err);
|
|
338
|
-
else
|
|
305
|
+
else resolve(addresses2);
|
|
339
306
|
});
|
|
340
307
|
});
|
|
341
308
|
if (!addresses || addresses.length === 0) {
|
|
@@ -411,7 +378,7 @@ function isSafeUrl(url, blockSsrf = true, mode = "max") {
|
|
|
411
378
|
}
|
|
412
379
|
return { safe: true, error: "" };
|
|
413
380
|
}
|
|
414
|
-
|
|
381
|
+
const INJECTION_PATTERNS = [
|
|
415
382
|
// Semicolon chaining to dangerous commands — mode-independent.
|
|
416
383
|
// Unlike && (conditional), ; ALWAYS runs the second command.
|
|
417
384
|
/;\s*(rm|sudo|chmod|chown|mkfs|dd|shred|kill|pkill)\b/i,
|
|
@@ -486,12 +453,12 @@ function sanitizeCommand(command) {
|
|
|
486
453
|
}
|
|
487
454
|
return { isSafe: true, error: "", command };
|
|
488
455
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
456
|
+
const AUDIT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
457
|
+
const AUDIT_LOG_PATH = path.join(AUDIT_DIR, "audit.log");
|
|
458
|
+
const AUDIT_BUFFER_MAX_ENTRIES = 50;
|
|
459
|
+
const AUDIT_FLUSH_INTERVAL_MS = 500;
|
|
460
|
+
let _auditBuffer = [];
|
|
461
|
+
let _auditFlushTimer = null;
|
|
495
462
|
function ensureAuditFlushTimer() {
|
|
496
463
|
if (_auditFlushTimer) return;
|
|
497
464
|
_auditFlushTimer = setInterval(() => {
|
|
@@ -507,11 +474,11 @@ function ensureAuditFlushTimer() {
|
|
|
507
474
|
function flushAuditBuffer() {
|
|
508
475
|
if (_auditBuffer.length === 0) return;
|
|
509
476
|
try {
|
|
510
|
-
if (!
|
|
511
|
-
|
|
477
|
+
if (!fs.existsSync(AUDIT_DIR)) {
|
|
478
|
+
fs.mkdirSync(AUDIT_DIR, { recursive: true });
|
|
512
479
|
}
|
|
513
480
|
const batch = _auditBuffer.join("");
|
|
514
|
-
|
|
481
|
+
fs.appendFileSync(AUDIT_LOG_PATH, batch, "utf-8");
|
|
515
482
|
} catch (err) {
|
|
516
483
|
debugLog("security", "audit buffer flush failure", err);
|
|
517
484
|
}
|
|
@@ -522,12 +489,12 @@ function appendAuditEntry(entry) {
|
|
|
522
489
|
ensureAuditFlushTimer();
|
|
523
490
|
const AUDIT_LOG_MAX_SIZE = 5 * 1024 * 1024;
|
|
524
491
|
try {
|
|
525
|
-
if (
|
|
526
|
-
const stat =
|
|
492
|
+
if (fs.existsSync(AUDIT_LOG_PATH)) {
|
|
493
|
+
const stat = fs.statSync(AUDIT_LOG_PATH);
|
|
527
494
|
if (stat.size > AUDIT_LOG_MAX_SIZE) {
|
|
528
495
|
const entries = readRecentAuditEntries(1e3);
|
|
529
496
|
const content = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
530
|
-
|
|
497
|
+
fs.writeFileSync(AUDIT_LOG_PATH, content, "utf-8");
|
|
531
498
|
}
|
|
532
499
|
}
|
|
533
500
|
} catch (err) {
|
|
@@ -545,10 +512,10 @@ function appendAuditEntry(entry) {
|
|
|
545
512
|
}
|
|
546
513
|
function readRecentAuditEntries(count = 50) {
|
|
547
514
|
try {
|
|
548
|
-
if (!
|
|
549
|
-
const fileSize =
|
|
515
|
+
if (!fs.existsSync(AUDIT_LOG_PATH)) return [];
|
|
516
|
+
const fileSize = fs.statSync(AUDIT_LOG_PATH).size;
|
|
550
517
|
if (fileSize === 0) return [];
|
|
551
|
-
const fd =
|
|
518
|
+
const fd = fs.openSync(AUDIT_LOG_PATH, "r");
|
|
552
519
|
const bufferSize = 8192;
|
|
553
520
|
const buffer = Buffer.alloc(bufferSize);
|
|
554
521
|
const lines = [];
|
|
@@ -557,7 +524,7 @@ function readRecentAuditEntries(count = 50) {
|
|
|
557
524
|
while (pos > 0 && lines.length < count) {
|
|
558
525
|
const readSize = Math.min(bufferSize, pos);
|
|
559
526
|
pos -= readSize;
|
|
560
|
-
|
|
527
|
+
fs.readSync(fd, buffer, 0, readSize, pos);
|
|
561
528
|
const chunk = buffer.slice(0, readSize).toString("utf-8");
|
|
562
529
|
partial = chunk + partial;
|
|
563
530
|
const lineBreak = partial.lastIndexOf("\n");
|
|
@@ -567,7 +534,7 @@ function readRecentAuditEntries(count = 50) {
|
|
|
567
534
|
partial = partial.slice(0, lineBreak);
|
|
568
535
|
}
|
|
569
536
|
}
|
|
570
|
-
|
|
537
|
+
fs.closeSync(fd);
|
|
571
538
|
if (partial.trim() && lines.length < count) {
|
|
572
539
|
lines.unshift(partial);
|
|
573
540
|
}
|
|
@@ -658,7 +625,7 @@ export {
|
|
|
658
625
|
CRITICAL_COMMANDS,
|
|
659
626
|
EXTENDED_COMMANDS,
|
|
660
627
|
SECURITY_CONFIG_PATH,
|
|
661
|
-
|
|
628
|
+
SETTINGS_PATH,
|
|
662
629
|
appendAuditEntry,
|
|
663
630
|
checkBashToolInput,
|
|
664
631
|
checkFileToolInput,
|
package/test-report.js
CHANGED
|
@@ -1,39 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
\u2500\u2500 ${title} ${"\u2500".repeat(Math.max(1, 60 - title.length - 4))}`;
|
|
5
|
-
}
|
|
6
|
-
function ok(msg) {
|
|
7
|
-
return ` \u2705 ${msg}`;
|
|
8
|
-
}
|
|
9
|
-
function fail(msg) {
|
|
10
|
-
return ` \u274C ${msg}`;
|
|
11
|
-
}
|
|
12
|
-
function warn(msg) {
|
|
13
|
-
return ` \u26A0\uFE0F ${msg}`;
|
|
14
|
-
}
|
|
15
|
-
function info(msg) {
|
|
16
|
-
return ` \u2139\uFE0F ${msg}`;
|
|
17
|
-
}
|
|
18
|
-
function msHuman(ms) {
|
|
19
|
-
if (ms < 1e3) return `${ms.toFixed(0)}ms`;
|
|
20
|
-
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
21
|
-
return `${(ms / 6e4).toFixed(1)}m`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// shared/ollama.ts
|
|
25
|
-
import * as path from "node:path";
|
|
26
|
-
import os from "node:os";
|
|
27
|
-
|
|
28
|
-
// shared/debug.ts
|
|
29
|
-
var DEBUG_ENABLED = process.env.PI_EXTENSIONS_DEBUG === "1";
|
|
30
|
-
|
|
31
|
-
// shared/ollama.ts
|
|
32
|
-
var EXTENSION_VERSION = "1.2.2";
|
|
33
|
-
var MODELS_JSON_PATH = path.join(os.homedir(), ".pi", "agent", "models.json");
|
|
34
|
-
|
|
35
|
-
// shared/test-report.ts
|
|
36
|
-
var branding = [
|
|
1
|
+
import { ok, fail, warn, info, section, msHuman } from "./format";
|
|
2
|
+
import { EXTENSION_VERSION } from "./ollama";
|
|
3
|
+
const branding = [
|
|
37
4
|
` \u26A1 Pi Model Benchmark v${EXTENSION_VERSION}`,
|
|
38
5
|
` Written by VTSTech`,
|
|
39
6
|
` GitHub: https://github.com/VTSTech`,
|
package/types.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// types — type definitions are erased at compile time
|