@stackmemoryai/stackmemory 1.2.1 → 1.2.4
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/dist/src/cli/codex-sm.js +6 -8
- package/dist/src/cli/commands/config.js +0 -81
- package/dist/src/cli/commands/context-rehydrate.js +133 -47
- package/dist/src/cli/commands/db.js +35 -8
- package/dist/src/cli/commands/handoff.js +1 -1
- package/dist/src/cli/commands/linear.js +9 -0
- package/dist/src/cli/commands/ralph.js +2 -2
- package/dist/src/cli/commands/setup.js +2 -2
- package/dist/src/cli/commands/signup.js +3 -1
- package/dist/src/cli/commands/skills.js +123 -1
- package/dist/src/cli/commands/storage-tier.js +26 -8
- package/dist/src/cli/index.js +1 -57
- package/dist/src/core/config/feature-flags.js +0 -4
- package/dist/src/core/context/dual-stack-manager.js +10 -3
- package/dist/src/core/context/frame-database.js +32 -0
- package/dist/src/core/context/frame-handoff-manager.js +2 -2
- package/dist/src/core/context/{refactored-frame-manager.js → frame-manager.js} +3 -3
- package/dist/src/core/context/index.js +2 -2
- package/dist/src/core/database/sqlite-adapter.js +161 -1
- package/dist/src/core/digest/frame-digest-integration.js +1 -1
- package/dist/src/core/digest/index.js +1 -1
- package/dist/src/core/execution/parallel-executor.js +5 -1
- package/dist/src/core/projects/project-isolation.js +18 -4
- package/dist/src/core/security/index.js +2 -0
- package/dist/src/core/security/input-sanitizer.js +23 -0
- package/dist/src/core/utils/update-checker.js +10 -6
- package/dist/src/daemon/daemon-config.js +2 -1
- package/dist/src/daemon/services/auto-save-service.js +121 -0
- package/dist/src/daemon/services/maintenance-service.js +76 -1
- package/dist/src/features/sweep/prompt-builder.js +2 -2
- package/dist/src/hooks/graphiti-hooks.js +149 -0
- package/dist/src/hooks/session-summary.js +30 -0
- package/dist/src/integrations/graphiti/linear-graphiti-bridge.js +115 -0
- package/dist/src/integrations/greptile/client.js +101 -0
- package/dist/src/integrations/greptile/config.js +14 -0
- package/dist/src/integrations/greptile/index.js +11 -0
- package/dist/src/integrations/greptile/types.js +4 -0
- package/dist/src/integrations/linear/config.js +3 -1
- package/dist/src/integrations/linear/sync.js +18 -5
- package/dist/src/integrations/linear/webhook.js +16 -0
- package/dist/src/integrations/mcp/handlers/code-execution-handlers.js +33 -7
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +397 -0
- package/dist/src/integrations/mcp/handlers/greptile-handlers.js +456 -0
- package/dist/src/integrations/mcp/handlers/index.js +55 -1
- package/dist/src/integrations/mcp/handlers/task-handlers.js +55 -12
- package/dist/src/integrations/mcp/handlers/team-handlers.js +211 -0
- package/dist/src/integrations/mcp/handlers/trace-handlers.js +25 -9
- package/dist/src/integrations/mcp/index.js +2 -2
- package/dist/src/integrations/mcp/refactored-server.js +31 -10
- package/dist/src/integrations/mcp/server.js +27 -0
- package/dist/src/integrations/mcp/tool-definitions.js +215 -1
- package/dist/src/integrations/ralph/context/context-budget-manager.js +10 -2
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +54 -22
- package/dist/src/integrations/ralph/learning/pattern-learner.js +59 -24
- package/dist/src/integrations/ralph/orchestration/multi-loop-orchestrator.js +81 -35
- package/dist/src/integrations/ralph/patterns/compounding-engineering-pattern.js +12 -4
- package/dist/src/integrations/ralph/patterns/extended-coherence-sessions.js +32 -9
- package/dist/src/integrations/ralph/swarm/git-workflow-manager.js +25 -8
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +17 -5
- package/dist/src/integrations/ralph/visualization/ralph-debugger.js +73 -22
- package/dist/src/skills/claude-skills.js +46 -103
- package/dist/src/skills/parallel-agent-skill.js +514 -0
- package/dist/src/utils/hook-installer.js +8 -0
- package/package.json +5 -5
- package/scripts/gepa/.before-optimize.md +140 -159
- package/scripts/gepa/config.json +7 -1
- package/scripts/gepa/evals/fixtures/api-endpoint.ts +31 -0
- package/scripts/gepa/evals/fixtures/brittle-integration.ts +38 -0
- package/scripts/gepa/evals/fixtures/fts5-triggers.sql +23 -0
- package/scripts/gepa/evals/fixtures/leaky-service.ts +70 -0
- package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +39 -0
- package/scripts/gepa/evals/fixtures/pr-diff.txt +24 -0
- package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +34 -0
- package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +42 -0
- package/scripts/gepa/evals/stackmemory-tasks.jsonl +8 -0
- package/scripts/gepa/generations/gen-000/baseline.md +172 -159
- package/scripts/gepa/generations/gen-001/baseline.md +172 -159
- package/scripts/gepa/generations/gen-001/variant-a.md +156 -146
- package/scripts/gepa/generations/gen-001/variant-b.md +199 -170
- package/scripts/gepa/generations/gen-001/variant-c.md +127 -46
- package/scripts/gepa/generations/gen-001/variant-d.md +160 -107
- package/scripts/gepa/hooks/reflect.js +44 -5
- package/scripts/gepa/optimize.js +281 -39
- package/scripts/gepa/results/eval-1-baseline.json +187 -10
- package/scripts/gepa/results/eval-1-variant-a.json +188 -11
- package/scripts/gepa/results/eval-1-variant-b.json +188 -11
- package/scripts/gepa/results/eval-1-variant-c.json +168 -11
- package/scripts/gepa/results/eval-1-variant-d.json +169 -12
- package/scripts/gepa/state.json +18 -18
- package/scripts/install-claude-hooks-auto.js +8 -0
- package/templates/claude-hooks/cord-trace.js +225 -0
- package/dist/src/core/config/storage-config.js +0 -114
- package/dist/src/core/storage/chromadb-adapter.js +0 -379
- package/dist/src/integrations/claude-code/enhanced-pre-clear-hooks.js +0 -458
- package/dist/src/integrations/ralph/coordination/enhanced-coordination.js +0 -409
- package/dist/src/skills/repo-ingestion-skill.js +0 -631
- package/templates/claude-hooks/chromadb-wrapper +0 -21
- /package/dist/src/core/context/{enhanced-rehydration.js → rehydration.js} +0 -0
- /package/dist/src/core/digest/{enhanced-hybrid-digest.js → hybrid-digest.js} +0 -0
- /package/dist/src/core/session/{enhanced-handoff.js → handoff.js} +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { logger } from "../../core/monitoring/logger.js";
|
|
6
|
+
import { GraphitiClient } from "./client.js";
|
|
7
|
+
class LinearGraphitiBridge {
|
|
8
|
+
client;
|
|
9
|
+
constructor(config = {}) {
|
|
10
|
+
this.client = new GraphitiClient(config);
|
|
11
|
+
}
|
|
12
|
+
async processWebhook(payload) {
|
|
13
|
+
const { action, data } = payload;
|
|
14
|
+
const now = Date.now();
|
|
15
|
+
try {
|
|
16
|
+
const episode = {
|
|
17
|
+
type: `linear_issue_${action}`,
|
|
18
|
+
content: {
|
|
19
|
+
identifier: data.identifier,
|
|
20
|
+
title: data.title,
|
|
21
|
+
action,
|
|
22
|
+
state: data.state?.name,
|
|
23
|
+
priority: data.priority,
|
|
24
|
+
assignee: data.assignee?.name
|
|
25
|
+
},
|
|
26
|
+
timestamp: now,
|
|
27
|
+
source: "linear"
|
|
28
|
+
};
|
|
29
|
+
await this.client.upsertEpisode(episode);
|
|
30
|
+
if (action === "remove") return;
|
|
31
|
+
const entities = [
|
|
32
|
+
{
|
|
33
|
+
type: "Issue",
|
|
34
|
+
name: data.identifier,
|
|
35
|
+
summary: data.title,
|
|
36
|
+
properties: {
|
|
37
|
+
linearId: data.id,
|
|
38
|
+
state: data.state?.name,
|
|
39
|
+
priority: data.priority
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
if (data.assignee) {
|
|
44
|
+
entities.push({
|
|
45
|
+
type: "Person",
|
|
46
|
+
name: data.assignee.name,
|
|
47
|
+
properties: {
|
|
48
|
+
linearId: data.assignee.id,
|
|
49
|
+
email: data.assignee.email
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (data.team) {
|
|
54
|
+
entities.push({
|
|
55
|
+
type: "Team",
|
|
56
|
+
name: data.team.name,
|
|
57
|
+
properties: { linearId: data.team.id, key: data.team.key }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (data.labels?.length) {
|
|
61
|
+
for (const label of data.labels) {
|
|
62
|
+
entities.push({
|
|
63
|
+
type: "Label",
|
|
64
|
+
name: label.name,
|
|
65
|
+
properties: { linearId: label.id, color: label.color }
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const entityResult = await this.client.upsertEntities(entities);
|
|
70
|
+
const issueId = entityResult.ids[0];
|
|
71
|
+
const relations = [];
|
|
72
|
+
let idx = 1;
|
|
73
|
+
if (data.assignee) {
|
|
74
|
+
relations.push({
|
|
75
|
+
fromId: issueId,
|
|
76
|
+
toId: entityResult.ids[idx],
|
|
77
|
+
type: "ASSIGNED_TO",
|
|
78
|
+
validFrom: now
|
|
79
|
+
});
|
|
80
|
+
idx++;
|
|
81
|
+
}
|
|
82
|
+
if (data.team) {
|
|
83
|
+
relations.push({
|
|
84
|
+
fromId: issueId,
|
|
85
|
+
toId: entityResult.ids[idx],
|
|
86
|
+
type: "BELONGS_TO",
|
|
87
|
+
validFrom: now
|
|
88
|
+
});
|
|
89
|
+
idx++;
|
|
90
|
+
}
|
|
91
|
+
if (data.labels?.length) {
|
|
92
|
+
for (let i = 0; i < data.labels.length; i++) {
|
|
93
|
+
relations.push({
|
|
94
|
+
fromId: issueId,
|
|
95
|
+
toId: entityResult.ids[idx + i],
|
|
96
|
+
type: "HAS_LABEL",
|
|
97
|
+
validFrom: now
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (relations.length > 0) {
|
|
102
|
+
await this.client.upsertRelations(relations);
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logger.debug("Linear-Graphiti bridge error", {
|
|
106
|
+
action,
|
|
107
|
+
identifier: data.identifier,
|
|
108
|
+
error: error instanceof Error ? error.message : String(error)
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export {
|
|
114
|
+
LinearGraphitiBridge
|
|
115
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
7
|
+
import { DEFAULT_GREPTILE_CONFIG } from "./config.js";
|
|
8
|
+
class GreptileClientError extends Error {
|
|
9
|
+
constructor(message, code) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.name = "GreptileClientError";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
class GreptileClient {
|
|
16
|
+
config;
|
|
17
|
+
client = null;
|
|
18
|
+
transport = null;
|
|
19
|
+
connecting = null;
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.config = { ...DEFAULT_GREPTILE_CONFIG, ...config };
|
|
22
|
+
if (!this.config.enabled || !this.config.apiKey) {
|
|
23
|
+
throw new GreptileClientError(
|
|
24
|
+
"Greptile integration disabled (GREPTILE_API_KEY not set)",
|
|
25
|
+
"DISABLED"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async ensureConnected() {
|
|
30
|
+
if (this.client) return this.client;
|
|
31
|
+
if (this.connecting) {
|
|
32
|
+
await this.connecting;
|
|
33
|
+
return this.client;
|
|
34
|
+
}
|
|
35
|
+
this.connecting = this.connect();
|
|
36
|
+
try {
|
|
37
|
+
await this.connecting;
|
|
38
|
+
return this.client;
|
|
39
|
+
} finally {
|
|
40
|
+
this.connecting = null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async connect() {
|
|
44
|
+
const transport = new StreamableHTTPClientTransport(
|
|
45
|
+
new URL(this.config.mcpEndpoint),
|
|
46
|
+
{
|
|
47
|
+
requestInit: {
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${this.config.apiKey}`
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
reconnectionOptions: {
|
|
53
|
+
maxRetries: this.config.maxRetries,
|
|
54
|
+
initialReconnectionDelay: 1e3,
|
|
55
|
+
reconnectionDelayGrowFactor: 1.5,
|
|
56
|
+
maxReconnectionDelay: 1e4
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
const client = new Client(
|
|
61
|
+
{ name: "stackmemory-greptile", version: "1.0.0" },
|
|
62
|
+
{ capabilities: {} }
|
|
63
|
+
);
|
|
64
|
+
transport.onclose = () => {
|
|
65
|
+
this.client = null;
|
|
66
|
+
this.transport = null;
|
|
67
|
+
};
|
|
68
|
+
await client.connect(transport);
|
|
69
|
+
this.client = client;
|
|
70
|
+
this.transport = transport;
|
|
71
|
+
}
|
|
72
|
+
async callTool(name, args = {}) {
|
|
73
|
+
const client = await this.ensureConnected();
|
|
74
|
+
const result = await client.callTool({ name, arguments: args });
|
|
75
|
+
if (result.content && Array.isArray(result.content)) {
|
|
76
|
+
const textParts = result.content.filter(
|
|
77
|
+
(c) => c.type === "text" && typeof c.text === "string"
|
|
78
|
+
).map((c) => c.text);
|
|
79
|
+
if (textParts.length === 0) return result;
|
|
80
|
+
const combined = textParts.join("\n");
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(combined);
|
|
83
|
+
} catch {
|
|
84
|
+
return combined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
async disconnect() {
|
|
90
|
+
if (this.transport) {
|
|
91
|
+
await this.transport.close();
|
|
92
|
+
}
|
|
93
|
+
this.client = null;
|
|
94
|
+
this.transport = null;
|
|
95
|
+
this.connecting = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
GreptileClient,
|
|
100
|
+
GreptileClientError
|
|
101
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
const DEFAULT_GREPTILE_CONFIG = {
|
|
6
|
+
enabled: !!process.env.GREPTILE_API_KEY,
|
|
7
|
+
mcpEndpoint: process.env.GREPTILE_MCP_ENDPOINT || "https://api.greptile.com/mcp",
|
|
8
|
+
apiKey: process.env.GREPTILE_API_KEY || "",
|
|
9
|
+
timeoutMs: 15e3,
|
|
10
|
+
maxRetries: 2
|
|
11
|
+
};
|
|
12
|
+
export {
|
|
13
|
+
DEFAULT_GREPTILE_CONFIG
|
|
14
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { DEFAULT_GREPTILE_CONFIG } from "./config.js";
|
|
6
|
+
import { GreptileClient, GreptileClientError } from "./client.js";
|
|
7
|
+
export {
|
|
8
|
+
DEFAULT_GREPTILE_CONFIG,
|
|
9
|
+
GreptileClient,
|
|
10
|
+
GreptileClientError
|
|
11
|
+
};
|
|
@@ -62,7 +62,9 @@ class LinearConfigManager {
|
|
|
62
62
|
* Get default configuration with project isolation
|
|
63
63
|
*/
|
|
64
64
|
getDefaultConfig() {
|
|
65
|
-
const projectId = this.isolationManager.getProjectIdentification(
|
|
65
|
+
const projectId = this.isolationManager.getProjectIdentification(
|
|
66
|
+
this.projectRoot
|
|
67
|
+
);
|
|
66
68
|
return {
|
|
67
69
|
enabled: true,
|
|
68
70
|
interval: 5,
|
|
@@ -5,7 +5,6 @@ const __dirname = __pathDirname(__filename);
|
|
|
5
5
|
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
6
6
|
import { join } from "path";
|
|
7
7
|
import { logger } from "../../core/monitoring/logger.js";
|
|
8
|
-
import { IntegrationError, ErrorCode } from "../../core/errors/index.js";
|
|
9
8
|
import { LinearClient } from "./client.js";
|
|
10
9
|
class LinearSyncEngine {
|
|
11
10
|
taskStore;
|
|
@@ -15,6 +14,7 @@ class LinearSyncEngine {
|
|
|
15
14
|
mappings = /* @__PURE__ */ new Map();
|
|
16
15
|
projectRoot;
|
|
17
16
|
mappingsPath;
|
|
17
|
+
_isConfigured = true;
|
|
18
18
|
constructor(taskStore, authManager, config, projectRoot) {
|
|
19
19
|
this.taskStore = taskStore;
|
|
20
20
|
this.authManager = authManager;
|
|
@@ -33,10 +33,9 @@ class LinearSyncEngine {
|
|
|
33
33
|
} else {
|
|
34
34
|
const tokens = this.authManager.loadTokens();
|
|
35
35
|
if (!tokens) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
);
|
|
36
|
+
logger.info("Linear API key not configured \u2014 skipping Linear sync");
|
|
37
|
+
this._isConfigured = false;
|
|
38
|
+
return;
|
|
40
39
|
}
|
|
41
40
|
this.linearClient = new LinearClient({
|
|
42
41
|
apiKey: tokens.accessToken,
|
|
@@ -49,6 +48,12 @@ class LinearSyncEngine {
|
|
|
49
48
|
}
|
|
50
49
|
this.loadMappings();
|
|
51
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if Linear credentials are available
|
|
53
|
+
*/
|
|
54
|
+
get isConfigured() {
|
|
55
|
+
return this._isConfigured;
|
|
56
|
+
}
|
|
52
57
|
/**
|
|
53
58
|
* Update sync configuration
|
|
54
59
|
*/
|
|
@@ -59,6 +64,14 @@ class LinearSyncEngine {
|
|
|
59
64
|
* Perform bi-directional sync
|
|
60
65
|
*/
|
|
61
66
|
async sync() {
|
|
67
|
+
if (!this._isConfigured) {
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
synced: { toLinear: 0, fromLinear: 0, updated: 0 },
|
|
71
|
+
conflicts: [],
|
|
72
|
+
errors: []
|
|
73
|
+
};
|
|
74
|
+
}
|
|
62
75
|
if (!this.config.enabled) {
|
|
63
76
|
return {
|
|
64
77
|
success: false,
|
|
@@ -22,6 +22,7 @@ function getOptionalEnv(key) {
|
|
|
22
22
|
class LinearWebhookHandler {
|
|
23
23
|
syncEngine;
|
|
24
24
|
taskStore;
|
|
25
|
+
graphitiBridge;
|
|
25
26
|
webhookSecret;
|
|
26
27
|
constructor(webhookSecret) {
|
|
27
28
|
this.webhookSecret = webhookSecret || process.env["LINEAR_WEBHOOK_SECRET"];
|
|
@@ -38,6 +39,12 @@ class LinearWebhookHandler {
|
|
|
38
39
|
setTaskStore(taskStore) {
|
|
39
40
|
this.taskStore = taskStore;
|
|
40
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Set the Graphiti bridge for knowledge graph sync
|
|
44
|
+
*/
|
|
45
|
+
setGraphitiBridge(bridge) {
|
|
46
|
+
this.graphitiBridge = bridge;
|
|
47
|
+
}
|
|
41
48
|
/**
|
|
42
49
|
* Verify webhook signature
|
|
43
50
|
*/
|
|
@@ -104,6 +111,15 @@ class LinearWebhookHandler {
|
|
|
104
111
|
default:
|
|
105
112
|
logger.warn(`Unknown webhook action: ${payload.action}`);
|
|
106
113
|
}
|
|
114
|
+
if (this.graphitiBridge) {
|
|
115
|
+
this.graphitiBridge.processWebhook(payload).catch((err) => {
|
|
116
|
+
logger.debug("Linear-Graphiti bridge error", {
|
|
117
|
+
action: payload.action,
|
|
118
|
+
identifier: payload.data.identifier,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err)
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
107
123
|
}
|
|
108
124
|
/**
|
|
109
125
|
* Handle issue created in Linear
|
|
@@ -27,7 +27,12 @@ class CodeExecutionHandler {
|
|
|
27
27
|
* Execute code in a controlled environment
|
|
28
28
|
*/
|
|
29
29
|
async executeCode(params) {
|
|
30
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
language,
|
|
32
|
+
code,
|
|
33
|
+
workingDirectory,
|
|
34
|
+
timeout = EXECUTION_TIMEOUT
|
|
35
|
+
} = params;
|
|
31
36
|
if (!this.allowedLanguages.includes(language.toLowerCase())) {
|
|
32
37
|
return {
|
|
33
38
|
success: false,
|
|
@@ -43,7 +48,12 @@ class CodeExecutionHandler {
|
|
|
43
48
|
);
|
|
44
49
|
try {
|
|
45
50
|
await fs.writeFile(tempFile, code, "utf-8");
|
|
46
|
-
const result = await this.runCode(
|
|
51
|
+
const result = await this.runCode(
|
|
52
|
+
language,
|
|
53
|
+
tempFile,
|
|
54
|
+
workingDirectory || this.sandboxDir,
|
|
55
|
+
timeout
|
|
56
|
+
);
|
|
47
57
|
await fs.unlink(tempFile).catch(() => {
|
|
48
58
|
});
|
|
49
59
|
return result;
|
|
@@ -98,8 +108,15 @@ class CodeExecutionHandler {
|
|
|
98
108
|
clearTimeout(timeoutId);
|
|
99
109
|
let outputFile;
|
|
100
110
|
if (truncated) {
|
|
101
|
-
outputFile = join(
|
|
102
|
-
|
|
111
|
+
outputFile = join(
|
|
112
|
+
this.sandboxDir,
|
|
113
|
+
`output_${randomBytes(8).toString("hex")}.txt`
|
|
114
|
+
);
|
|
115
|
+
await fs.writeFile(
|
|
116
|
+
outputFile,
|
|
117
|
+
stdout + "\n---STDERR---\n" + stderr,
|
|
118
|
+
"utf-8"
|
|
119
|
+
).catch(() => {
|
|
103
120
|
});
|
|
104
121
|
}
|
|
105
122
|
resolve({
|
|
@@ -175,12 +192,21 @@ class CodeExecutionHandler {
|
|
|
175
192
|
const warnings = [];
|
|
176
193
|
const dangerousPatterns = [
|
|
177
194
|
{ pattern: /import\s+os/i, message: "Importing os module detected" },
|
|
178
|
-
{
|
|
195
|
+
{
|
|
196
|
+
pattern: /import\s+subprocess/i,
|
|
197
|
+
message: "Subprocess module detected"
|
|
198
|
+
},
|
|
179
199
|
{ pattern: /exec\s*\(/i, message: "exec() function detected" },
|
|
180
200
|
{ pattern: /eval\s*\(/i, message: "eval() function detected" },
|
|
181
201
|
{ pattern: /__import__/i, message: "__import__ detected" },
|
|
182
|
-
{
|
|
183
|
-
|
|
202
|
+
{
|
|
203
|
+
pattern: /open\s*\([^)]*['"]\//i,
|
|
204
|
+
message: "Absolute path file access detected"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
pattern: /require\s*\([^)]*child_process/i,
|
|
208
|
+
message: "child_process module detected"
|
|
209
|
+
},
|
|
184
210
|
{ pattern: /require\s*\([^)]*fs/i, message: "fs module access detected" }
|
|
185
211
|
];
|
|
186
212
|
for (const { pattern, message } of dangerousPatterns) {
|