@juspay/neurolink 9.51.4 → 9.53.0
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/CHANGELOG.md +12 -0
- package/README.md +19 -0
- package/dist/agent/directTools.d.ts +2 -2
- package/dist/auth/errors.d.ts +1 -1
- package/dist/auth/middleware/AuthMiddleware.d.ts +1 -1
- package/dist/auth/providers/BaseAuthProvider.d.ts +1 -1
- package/dist/autoresearch/config.d.ts +11 -0
- package/dist/autoresearch/config.js +108 -0
- package/dist/autoresearch/errors.d.ts +40 -0
- package/dist/autoresearch/errors.js +20 -0
- package/dist/autoresearch/index.d.ts +23 -0
- package/dist/autoresearch/index.js +34 -0
- package/dist/autoresearch/phasePolicy.d.ts +9 -0
- package/dist/autoresearch/phasePolicy.js +69 -0
- package/dist/autoresearch/promptCompiler.d.ts +15 -0
- package/dist/autoresearch/promptCompiler.js +120 -0
- package/dist/autoresearch/repoPolicy.d.ts +32 -0
- package/dist/autoresearch/repoPolicy.js +128 -0
- package/dist/autoresearch/resultRecorder.d.ts +20 -0
- package/dist/autoresearch/resultRecorder.js +130 -0
- package/dist/autoresearch/runner.d.ts +10 -0
- package/dist/autoresearch/runner.js +102 -0
- package/dist/autoresearch/stateStore.d.ts +12 -0
- package/dist/autoresearch/stateStore.js +163 -0
- package/dist/autoresearch/summaryParser.d.ts +16 -0
- package/dist/autoresearch/summaryParser.js +94 -0
- package/dist/autoresearch/tools.d.ts +257 -0
- package/dist/autoresearch/tools.js +617 -0
- package/dist/autoresearch/worker.d.ts +71 -0
- package/dist/autoresearch/worker.js +417 -0
- package/dist/browser/neurolink.min.js +340 -324
- package/dist/cli/commands/autoresearch.d.ts +41 -0
- package/dist/cli/commands/autoresearch.js +487 -0
- package/dist/cli/commands/config.d.ts +1 -1
- package/dist/cli/commands/task.d.ts +2 -0
- package/dist/cli/commands/task.js +32 -3
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/parser.js +4 -1
- package/dist/core/baseProvider.js +18 -0
- package/dist/core/factory.d.ts +2 -2
- package/dist/core/factory.js +4 -4
- package/dist/evaluation/errors/EvaluationError.d.ts +1 -1
- package/dist/factories/providerFactory.d.ts +4 -4
- package/dist/factories/providerFactory.js +20 -7
- package/dist/factories/providerRegistry.d.ts +5 -0
- package/dist/factories/providerRegistry.js +45 -26
- package/dist/lib/agent/directTools.d.ts +2 -2
- package/dist/lib/auth/errors.d.ts +1 -1
- package/dist/lib/auth/middleware/AuthMiddleware.d.ts +1 -1
- package/dist/lib/auth/providers/BaseAuthProvider.d.ts +1 -1
- package/dist/lib/autoresearch/config.d.ts +11 -0
- package/dist/lib/autoresearch/config.js +109 -0
- package/dist/lib/autoresearch/errors.d.ts +40 -0
- package/dist/lib/autoresearch/errors.js +21 -0
- package/dist/lib/autoresearch/index.d.ts +23 -0
- package/dist/lib/autoresearch/index.js +35 -0
- package/dist/lib/autoresearch/phasePolicy.d.ts +9 -0
- package/dist/lib/autoresearch/phasePolicy.js +70 -0
- package/dist/lib/autoresearch/promptCompiler.d.ts +15 -0
- package/dist/lib/autoresearch/promptCompiler.js +121 -0
- package/dist/lib/autoresearch/repoPolicy.d.ts +32 -0
- package/dist/lib/autoresearch/repoPolicy.js +129 -0
- package/dist/lib/autoresearch/resultRecorder.d.ts +20 -0
- package/dist/lib/autoresearch/resultRecorder.js +131 -0
- package/dist/lib/autoresearch/runner.d.ts +10 -0
- package/dist/lib/autoresearch/runner.js +103 -0
- package/dist/lib/autoresearch/stateStore.d.ts +12 -0
- package/dist/lib/autoresearch/stateStore.js +164 -0
- package/dist/lib/autoresearch/summaryParser.d.ts +16 -0
- package/dist/lib/autoresearch/summaryParser.js +95 -0
- package/dist/lib/autoresearch/tools.d.ts +257 -0
- package/dist/lib/autoresearch/tools.js +618 -0
- package/dist/lib/autoresearch/worker.d.ts +71 -0
- package/dist/lib/autoresearch/worker.js +418 -0
- package/dist/lib/core/baseProvider.js +18 -0
- package/dist/lib/core/factory.d.ts +2 -2
- package/dist/lib/core/factory.js +4 -4
- package/dist/lib/evaluation/errors/EvaluationError.d.ts +1 -1
- package/dist/lib/factories/providerFactory.d.ts +4 -4
- package/dist/lib/factories/providerFactory.js +20 -7
- package/dist/lib/factories/providerRegistry.d.ts +5 -0
- package/dist/lib/factories/providerRegistry.js +45 -26
- package/dist/lib/files/fileTools.d.ts +1 -1
- package/dist/lib/neurolink.d.ts +21 -0
- package/dist/lib/neurolink.js +91 -8
- package/dist/lib/providers/amazonBedrock.d.ts +6 -1
- package/dist/lib/providers/amazonBedrock.js +14 -2
- package/dist/lib/providers/amazonSagemaker.d.ts +7 -1
- package/dist/lib/providers/amazonSagemaker.js +21 -3
- package/dist/lib/providers/anthropic.d.ts +4 -1
- package/dist/lib/providers/anthropic.js +18 -5
- package/dist/lib/providers/azureOpenai.d.ts +2 -1
- package/dist/lib/providers/azureOpenai.js +10 -5
- package/dist/lib/providers/googleAiStudio.d.ts +4 -1
- package/dist/lib/providers/googleAiStudio.js +6 -7
- package/dist/lib/providers/googleVertex.d.ts +3 -1
- package/dist/lib/providers/googleVertex.js +96 -17
- package/dist/lib/providers/huggingFace.d.ts +2 -1
- package/dist/lib/providers/huggingFace.js +4 -4
- package/dist/lib/providers/litellm.d.ts +5 -1
- package/dist/lib/providers/litellm.js +16 -11
- package/dist/lib/providers/mistral.d.ts +2 -1
- package/dist/lib/providers/mistral.js +2 -2
- package/dist/lib/providers/ollama.d.ts +3 -1
- package/dist/lib/providers/ollama.js +2 -2
- package/dist/lib/providers/openAI.d.ts +5 -1
- package/dist/lib/providers/openAI.js +15 -5
- package/dist/lib/providers/openRouter.d.ts +5 -1
- package/dist/lib/providers/openRouter.js +19 -7
- package/dist/lib/providers/openaiCompatible.d.ts +4 -1
- package/dist/lib/providers/openaiCompatible.js +18 -4
- package/dist/lib/tasks/autoresearchTaskExecutor.d.ts +32 -0
- package/dist/lib/tasks/autoresearchTaskExecutor.js +303 -0
- package/dist/lib/tasks/errors.d.ts +3 -1
- package/dist/lib/tasks/errors.js +1 -0
- package/dist/lib/tasks/taskExecutor.d.ts +4 -2
- package/dist/lib/tasks/taskExecutor.js +8 -1
- package/dist/lib/tasks/taskManager.js +27 -3
- package/dist/lib/tasks/tools/taskTools.d.ts +1 -1
- package/dist/lib/telemetry/attributes.d.ts +15 -0
- package/dist/lib/telemetry/attributes.js +16 -0
- package/dist/lib/telemetry/tracers.d.ts +1 -0
- package/dist/lib/telemetry/tracers.js +1 -0
- package/dist/lib/types/autoresearchTypes.d.ts +194 -0
- package/dist/lib/types/autoresearchTypes.js +18 -0
- package/dist/lib/types/common.d.ts +11 -0
- package/dist/lib/types/configTypes.d.ts +7 -0
- package/dist/lib/types/generateTypes.d.ts +13 -0
- package/dist/lib/types/index.d.ts +16 -14
- package/dist/lib/types/index.js +21 -17
- package/dist/lib/types/providers.d.ts +75 -0
- package/dist/lib/types/streamTypes.d.ts +7 -1
- package/dist/lib/types/taskTypes.d.ts +38 -0
- package/dist/lib/workflow/config.d.ts +3 -3
- package/dist/neurolink.d.ts +21 -0
- package/dist/neurolink.js +91 -8
- package/dist/providers/amazonBedrock.d.ts +6 -1
- package/dist/providers/amazonBedrock.js +14 -2
- package/dist/providers/amazonSagemaker.d.ts +7 -1
- package/dist/providers/amazonSagemaker.js +21 -3
- package/dist/providers/anthropic.d.ts +4 -1
- package/dist/providers/anthropic.js +18 -5
- package/dist/providers/azureOpenai.d.ts +2 -1
- package/dist/providers/azureOpenai.js +10 -5
- package/dist/providers/googleAiStudio.d.ts +4 -1
- package/dist/providers/googleAiStudio.js +6 -7
- package/dist/providers/googleVertex.d.ts +3 -1
- package/dist/providers/googleVertex.js +96 -17
- package/dist/providers/huggingFace.d.ts +2 -1
- package/dist/providers/huggingFace.js +4 -4
- package/dist/providers/litellm.d.ts +5 -1
- package/dist/providers/litellm.js +16 -11
- package/dist/providers/mistral.d.ts +2 -1
- package/dist/providers/mistral.js +2 -2
- package/dist/providers/ollama.d.ts +3 -1
- package/dist/providers/ollama.js +2 -2
- package/dist/providers/openAI.d.ts +5 -1
- package/dist/providers/openAI.js +15 -5
- package/dist/providers/openRouter.d.ts +5 -1
- package/dist/providers/openRouter.js +19 -7
- package/dist/providers/openaiCompatible.d.ts +4 -1
- package/dist/providers/openaiCompatible.js +18 -4
- package/dist/rag/errors/RAGError.d.ts +1 -1
- package/dist/tasks/autoresearchTaskExecutor.d.ts +32 -0
- package/dist/tasks/autoresearchTaskExecutor.js +302 -0
- package/dist/tasks/errors.d.ts +3 -1
- package/dist/tasks/errors.js +1 -0
- package/dist/tasks/taskExecutor.d.ts +4 -2
- package/dist/tasks/taskExecutor.js +8 -1
- package/dist/tasks/taskManager.js +27 -3
- package/dist/tasks/tools/taskTools.d.ts +1 -1
- package/dist/telemetry/attributes.d.ts +15 -0
- package/dist/telemetry/attributes.js +16 -0
- package/dist/telemetry/tracers.d.ts +1 -0
- package/dist/telemetry/tracers.js +1 -0
- package/dist/types/autoresearchTypes.d.ts +194 -0
- package/dist/types/autoresearchTypes.js +17 -0
- package/dist/types/common.d.ts +11 -0
- package/dist/types/configTypes.d.ts +7 -0
- package/dist/types/generateTypes.d.ts +13 -0
- package/dist/types/index.d.ts +16 -14
- package/dist/types/index.js +21 -17
- package/dist/types/providers.d.ts +75 -0
- package/dist/types/streamTypes.d.ts +7 -1
- package/dist/types/taskTypes.d.ts +38 -0
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [9.53.0](https://github.com/juspay/neurolink/compare/v9.52.0...v9.53.0) (2026-04-12)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **(autoresearch):** add autonomous experiment engine with scheduling and docs ([22be8c8](https://github.com/juspay/neurolink/commit/22be8c857940eeb7c42e8581483d1244ef890158))
|
|
6
|
+
|
|
7
|
+
## [9.52.0](https://github.com/juspay/neurolink/compare/v9.51.4...v9.52.0) (2026-04-12)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **(credentials):** add per-request and per-instance credential support for all providers ([edd07bb](https://github.com/juspay/neurolink/commit/edd07bb5475d4ef33a9ce29915913ebcd8a6904b))
|
|
12
|
+
|
|
1
13
|
## [9.51.4](https://github.com/juspay/neurolink/compare/v9.51.3...v9.51.4) (2026-04-12)
|
|
2
14
|
|
|
3
15
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ Extracted from production systems at Juspay and battle-tested at enterprise scal
|
|
|
42
42
|
|
|
43
43
|
| Feature | Version | Description | Guide |
|
|
44
44
|
| ----------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
|
45
|
+
| **AutoResearch** | v9.17.0 | Autonomous AI experiment engine: proposes code changes, runs experiments, evaluates metrics, keeps improvements — unattended for hours. | [AutoResearch Guide](docs/features/autoresearch.md) |
|
|
45
46
|
| **MCP Enhancements** | v9.16.0 | Advanced MCP features: tool routing, result caching, request batching, annotations, elicitation, custom server base, multi-server management | [MCP Enhancements Guide](docs/features/mcp-enhancements.md) |
|
|
46
47
|
| **Memory** | v9.12.0 | Per-user condensed memory that persists across conversations. LLM-powered condensation with S3, Redis, or SQLite backends. | [Memory Guide](docs/features/memory.md) |
|
|
47
48
|
| **Context Window Management** | v9.2.0 | 4-stage compaction pipeline with auto-detection, budget gate at 80% usage, per-provider token estimation | [Context Compaction Guide](docs/features/context-compaction.md) |
|
|
@@ -55,6 +56,7 @@ Extracted from production systems at Juspay and battle-tested at enterprise scal
|
|
|
55
56
|
| **Image Generation with Gemini** | v8.31.0 | Native image generation using Gemini 2.0 Flash Experimental (`imagen-3.0-generate-002`). High-quality image synthesis directly from Google AI. | [Image Generation Guide](docs/image-generation-streaming.md) |
|
|
56
57
|
| **HTTP/Streamable HTTP Transport** | v8.29.0 | Connect to remote MCP servers via HTTP with authentication headers, automatic retry with exponential backoff, and configurable rate limiting. | [HTTP Transport Guide](docs/mcp-http-transport.md) |
|
|
57
58
|
|
|
59
|
+
- **AutoResearch** – Autonomous AI experiment engine inspired by Karpathy's autoresearch. Phase-gated tool access, git-backed safety, deterministic metric evaluation, and TaskManager integration for continuous unattended research. 12 research tools, 10 typed events, 9 CLI subcommands. → [AutoResearch Guide](docs/features/autoresearch.md)
|
|
58
60
|
- **Memory** – Per-user condensed memory that persists across all conversations. Automatically retrieves and stores memory on each `generate()`/`stream()` call. Supports S3, Redis, and SQLite storage with LLM-powered condensation. → [Memory Guide](docs/features/memory.md)
|
|
59
61
|
- **External TracerProvider Support** – Integrate NeuroLink with applications that already have OpenTelemetry instrumentation. Supports auto-detection and manual configuration. → [Observability Guide](docs/features/observability.md)
|
|
60
62
|
- **Claude Proxy Telemetry** – Bootstrap a local OpenObserve + OTEL collector stack with `neurolink proxy telemetry setup`, import the maintained NeuroLink Proxy Observability dashboard, and inspect proxy logs, traces, metrics, cache reuse, and routing behavior. → [Claude Proxy Guide](docs/features/claude-proxy.md) | [Proxy Observability Guide](docs/features/claude-proxy-observability.md)
|
|
@@ -90,6 +92,23 @@ const image = await neurolink.generate({
|
|
|
90
92
|
});
|
|
91
93
|
console.log(image.imageOutput?.base64); // Base64-encoded image
|
|
92
94
|
|
|
95
|
+
// AutoResearch — autonomous experiment loop (v9.17.0)
|
|
96
|
+
import { resolveConfig, ResearchWorker } from "@juspay/neurolink/autoresearch";
|
|
97
|
+
|
|
98
|
+
const config = resolveConfig({
|
|
99
|
+
repoPath: "/path/to/repo",
|
|
100
|
+
mutablePaths: ["train.py"],
|
|
101
|
+
runCommand: "python3 train.py",
|
|
102
|
+
metric: {
|
|
103
|
+
name: "val_bpb",
|
|
104
|
+
direction: "lower",
|
|
105
|
+
pattern: "^val_bpb:\\s+([\\d.]+)",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const worker = new ResearchWorker(config);
|
|
109
|
+
await worker.initialize("experiment-1");
|
|
110
|
+
const result = await worker.runExperimentCycle("Try lower learning rate");
|
|
111
|
+
|
|
93
112
|
// HTTP Transport for Remote MCP (v8.29.0)
|
|
94
113
|
await neurolink.addExternalMCPServer("remote-tools", {
|
|
95
114
|
transport: "http",
|
|
@@ -101,7 +101,7 @@ export declare const directAgentTools: {
|
|
|
101
101
|
writeFile: import("ai").Tool<{
|
|
102
102
|
path: string;
|
|
103
103
|
content: string;
|
|
104
|
-
mode: "
|
|
104
|
+
mode: "append" | "create" | "overwrite";
|
|
105
105
|
}, {
|
|
106
106
|
success: boolean;
|
|
107
107
|
error: string;
|
|
@@ -112,7 +112,7 @@ export declare const directAgentTools: {
|
|
|
112
112
|
} | {
|
|
113
113
|
success: boolean;
|
|
114
114
|
path: string;
|
|
115
|
-
mode: "
|
|
115
|
+
mode: "append" | "create" | "overwrite";
|
|
116
116
|
size: number;
|
|
117
117
|
written: number;
|
|
118
118
|
error?: undefined;
|
package/dist/auth/errors.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare const AuthError: {
|
|
|
55
55
|
readonly JWKS_FETCH_FAILED: "AUTH-070";
|
|
56
56
|
readonly JWKS_KEY_NOT_FOUND: "AUTH-071";
|
|
57
57
|
};
|
|
58
|
-
create: (code: "SESSION_NOT_FOUND" | "INVALID_TOKEN" | "EXPIRED_TOKEN" | "MISSING_TOKEN" | "TOKEN_DECODE_FAILED" | "INVALID_SIGNATURE" | "SESSION_EXPIRED" | "SESSION_REVOKED" | "INSUFFICIENT_PERMISSIONS" | "INSUFFICIENT_ROLES" | "ACCESS_DENIED" | "USER_NOT_FOUND" | "USER_DISABLED" | "EMAIL_NOT_VERIFIED" | "MFA_REQUIRED" | "
|
|
58
|
+
create: (code: "SESSION_NOT_FOUND" | "PROVIDER_ERROR" | "CONFIGURATION_ERROR" | "RATE_LIMITED" | "INVALID_TOKEN" | "EXPIRED_TOKEN" | "MISSING_TOKEN" | "TOKEN_DECODE_FAILED" | "INVALID_SIGNATURE" | "SESSION_EXPIRED" | "SESSION_REVOKED" | "INSUFFICIENT_PERMISSIONS" | "INSUFFICIENT_ROLES" | "ACCESS_DENIED" | "USER_NOT_FOUND" | "USER_DISABLED" | "EMAIL_NOT_VERIFIED" | "MFA_REQUIRED" | "PROVIDER_NOT_FOUND" | "PROVIDER_INIT_FAILED" | "CREATION_FAILED" | "REGISTRATION_FAILED" | "DUPLICATE_REGISTRATION" | "MIDDLEWARE_ERROR" | "JWKS_FETCH_FAILED" | "JWKS_KEY_NOT_FOUND", message: string, options?: {
|
|
59
59
|
retryable?: boolean;
|
|
60
60
|
details?: Record<string, unknown>;
|
|
61
61
|
cause?: Error;
|
|
@@ -31,7 +31,7 @@ export declare const AuthMiddlewareError: {
|
|
|
31
31
|
readonly PROVIDER_ERROR: "AUTH_MIDDLEWARE-005";
|
|
32
32
|
readonly CONFIGURATION_ERROR: "AUTH_MIDDLEWARE-006";
|
|
33
33
|
};
|
|
34
|
-
create: (code: "
|
|
34
|
+
create: (code: "PROVIDER_ERROR" | "CONFIGURATION_ERROR" | "INVALID_TOKEN" | "MISSING_TOKEN" | "FORBIDDEN" | "UNAUTHORIZED", message: string, options?: {
|
|
35
35
|
retryable?: boolean;
|
|
36
36
|
details?: Record<string, unknown>;
|
|
37
37
|
cause?: Error;
|
|
@@ -44,7 +44,7 @@ export declare const AuthProviderError: {
|
|
|
44
44
|
readonly JWKS_FETCH_FAILED: "AUTH-070";
|
|
45
45
|
readonly JWKS_KEY_NOT_FOUND: "AUTH-071";
|
|
46
46
|
};
|
|
47
|
-
create: (code: "SESSION_NOT_FOUND" | "INVALID_TOKEN" | "EXPIRED_TOKEN" | "MISSING_TOKEN" | "TOKEN_DECODE_FAILED" | "INVALID_SIGNATURE" | "SESSION_EXPIRED" | "SESSION_REVOKED" | "INSUFFICIENT_PERMISSIONS" | "INSUFFICIENT_ROLES" | "ACCESS_DENIED" | "USER_NOT_FOUND" | "USER_DISABLED" | "EMAIL_NOT_VERIFIED" | "MFA_REQUIRED" | "
|
|
47
|
+
create: (code: "SESSION_NOT_FOUND" | "PROVIDER_ERROR" | "CONFIGURATION_ERROR" | "RATE_LIMITED" | "INVALID_TOKEN" | "EXPIRED_TOKEN" | "MISSING_TOKEN" | "TOKEN_DECODE_FAILED" | "INVALID_SIGNATURE" | "SESSION_EXPIRED" | "SESSION_REVOKED" | "INSUFFICIENT_PERMISSIONS" | "INSUFFICIENT_ROLES" | "ACCESS_DENIED" | "USER_NOT_FOUND" | "USER_DISABLED" | "EMAIL_NOT_VERIFIED" | "MFA_REQUIRED" | "PROVIDER_NOT_FOUND" | "PROVIDER_INIT_FAILED" | "CREATION_FAILED" | "REGISTRATION_FAILED" | "DUPLICATE_REGISTRATION" | "MIDDLEWARE_ERROR" | "JWKS_FETCH_FAILED" | "JWKS_KEY_NOT_FOUND", message: string, options?: {
|
|
48
48
|
retryable?: boolean;
|
|
49
49
|
details?: Record<string, unknown>;
|
|
50
50
|
cause?: Error;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research configuration validation and resolution.
|
|
3
|
+
*/
|
|
4
|
+
import type { ResearchConfig } from "../types/autoresearchTypes.js";
|
|
5
|
+
export declare function resolveConfig(partial: Partial<ResearchConfig> & {
|
|
6
|
+
repoPath: string;
|
|
7
|
+
mutablePaths: string[];
|
|
8
|
+
runCommand: string;
|
|
9
|
+
metric: ResearchConfig["metric"];
|
|
10
|
+
}): ResearchConfig;
|
|
11
|
+
export declare function validateConfig(config: ResearchConfig): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Research configuration validation and resolution.
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, statSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
7
|
+
import { AutoresearchError } from "./errors.js";
|
|
8
|
+
import { AUTORESEARCH_DEFAULTS } from "../types/autoresearchTypes.js";
|
|
9
|
+
export function resolveConfig(partial) {
|
|
10
|
+
const config = {
|
|
11
|
+
repoPath: path.resolve(partial.repoPath),
|
|
12
|
+
programPath: partial.programPath ?? AUTORESEARCH_DEFAULTS.programPath,
|
|
13
|
+
mutablePaths: partial.mutablePaths,
|
|
14
|
+
immutablePaths: partial.immutablePaths ?? [],
|
|
15
|
+
resultsPath: partial.resultsPath ?? AUTORESEARCH_DEFAULTS.resultsPath,
|
|
16
|
+
statePath: partial.statePath ?? AUTORESEARCH_DEFAULTS.statePath,
|
|
17
|
+
runCommand: partial.runCommand,
|
|
18
|
+
logPath: partial.logPath ?? AUTORESEARCH_DEFAULTS.logPath,
|
|
19
|
+
metric: partial.metric,
|
|
20
|
+
memoryMetric: partial.memoryMetric,
|
|
21
|
+
timeoutMs: partial.timeoutMs ?? AUTORESEARCH_DEFAULTS.timeoutMs,
|
|
22
|
+
branchPrefix: partial.branchPrefix ?? AUTORESEARCH_DEFAULTS.branchPrefix,
|
|
23
|
+
provider: partial.provider,
|
|
24
|
+
model: partial.model,
|
|
25
|
+
maxExperiments: partial.maxExperiments,
|
|
26
|
+
thinkingLevel: partial.thinkingLevel ?? AUTORESEARCH_DEFAULTS.thinkingLevel,
|
|
27
|
+
};
|
|
28
|
+
return config;
|
|
29
|
+
}
|
|
30
|
+
export function validateConfig(config) {
|
|
31
|
+
// Verify repoPath exists and is a directory
|
|
32
|
+
if (!existsSync(config.repoPath) ||
|
|
33
|
+
!statSync(config.repoPath).isDirectory()) {
|
|
34
|
+
throw AutoresearchError.create("CONFIG_INVALID", `repoPath does not exist or is not a directory: ${config.repoPath}`);
|
|
35
|
+
}
|
|
36
|
+
// Verify it's a git repo
|
|
37
|
+
if (!existsSync(path.join(config.repoPath, ".git"))) {
|
|
38
|
+
throw AutoresearchError.create("REPO_NOT_FOUND", `repoPath is not a git repository: ${config.repoPath}`);
|
|
39
|
+
}
|
|
40
|
+
// Verify mutablePaths is non-empty
|
|
41
|
+
if (config.mutablePaths.length === 0) {
|
|
42
|
+
throw AutoresearchError.create("CONFIG_INVALID", "mutablePaths must contain at least one path");
|
|
43
|
+
}
|
|
44
|
+
// Verify mutable and immutable don't overlap (normalize paths first)
|
|
45
|
+
const normMutable = config.mutablePaths.map((p) => path.normalize(p).replace(/\/+$/, ""));
|
|
46
|
+
const normImmutable = config.immutablePaths.map((p) => path.normalize(p).replace(/\/+$/, ""));
|
|
47
|
+
const overlap = normMutable.filter((p) => normImmutable.includes(p));
|
|
48
|
+
if (overlap.length > 0) {
|
|
49
|
+
throw AutoresearchError.create("CONFIG_INVALID", `mutablePaths and immutablePaths overlap: ${overlap.join(", ")}`);
|
|
50
|
+
}
|
|
51
|
+
// Verify metric pattern — length limit and basic ReDoS safety
|
|
52
|
+
const MAX_PATTERN_LENGTH = 200;
|
|
53
|
+
if (config.metric.pattern.length > MAX_PATTERN_LENGTH) {
|
|
54
|
+
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern exceeds ${MAX_PATTERN_LENGTH} characters: ${config.metric.pattern.slice(0, 50)}...`);
|
|
55
|
+
}
|
|
56
|
+
// Reject patterns with obvious catastrophic backtracking (nested quantifiers)
|
|
57
|
+
if (/(\+|\*|\{)\s*(\+|\*|\{)/.test(config.metric.pattern) ||
|
|
58
|
+
/\(\?[^)]*\)\+\+/.test(config.metric.pattern)) {
|
|
59
|
+
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern contains nested quantifiers (potential ReDoS): ${config.metric.pattern}`);
|
|
60
|
+
}
|
|
61
|
+
// Verify metric pattern is valid regex with exactly one capture group
|
|
62
|
+
try {
|
|
63
|
+
const _regex = new RegExp(config.metric.pattern);
|
|
64
|
+
const match = new RegExp(config.metric.pattern + "|").exec("");
|
|
65
|
+
const groupCount = (match?.length ?? 1) - 1;
|
|
66
|
+
if (groupCount !== 1) {
|
|
67
|
+
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern must have exactly one capture group, found ${groupCount}: ${config.metric.pattern}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof Error &&
|
|
72
|
+
"code" in error &&
|
|
73
|
+
typeof error.code === "string") {
|
|
74
|
+
throw error; // Rethrow structured AutoresearchError
|
|
75
|
+
}
|
|
76
|
+
throw AutoresearchError.create("CONFIG_INVALID", `metric.pattern is not a valid regex: ${config.metric.pattern}`);
|
|
77
|
+
}
|
|
78
|
+
// Validate memoryMetric.pattern with the same checks if present
|
|
79
|
+
if (config.memoryMetric) {
|
|
80
|
+
if (config.memoryMetric.pattern.length > MAX_PATTERN_LENGTH) {
|
|
81
|
+
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern exceeds ${MAX_PATTERN_LENGTH} characters`);
|
|
82
|
+
}
|
|
83
|
+
if (/(\+|\*|\{)\s*(\+|\*|\{)/.test(config.memoryMetric.pattern) ||
|
|
84
|
+
/\(\?[^)]*\)\+\+/.test(config.memoryMetric.pattern)) {
|
|
85
|
+
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern contains nested quantifiers (potential ReDoS)`);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const _memRegex = new RegExp(config.memoryMetric.pattern);
|
|
89
|
+
const memMatch = new RegExp(config.memoryMetric.pattern + "|").exec("");
|
|
90
|
+
const memGroupCount = (memMatch?.length ?? 1) - 1;
|
|
91
|
+
if (memGroupCount !== 1) {
|
|
92
|
+
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern must have exactly one capture group, found ${memGroupCount}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof Error &&
|
|
97
|
+
"code" in error &&
|
|
98
|
+
typeof error.code === "string") {
|
|
99
|
+
throw error; // Rethrow structured AutoresearchError
|
|
100
|
+
}
|
|
101
|
+
throw AutoresearchError.create("CONFIG_INVALID", `memoryMetric.pattern is not a valid regex: ${config.memoryMetric.pattern}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
logger.info("[Autoresearch] Config validated", {
|
|
105
|
+
repoPath: config.repoPath,
|
|
106
|
+
mutablePaths: config.mutablePaths,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error definitions for the NeuroLink AutoResearch system.
|
|
3
|
+
*/
|
|
4
|
+
export declare const AutoresearchErrorCodes: {
|
|
5
|
+
readonly CONFIG_INVALID: "AUTORESEARCH-001";
|
|
6
|
+
readonly STATE_CORRUPT: "AUTORESEARCH-002";
|
|
7
|
+
readonly STATE_NOT_FOUND: "AUTORESEARCH-003";
|
|
8
|
+
readonly REPO_NOT_FOUND: "AUTORESEARCH-004";
|
|
9
|
+
readonly BRANCH_ERROR: "AUTORESEARCH-005";
|
|
10
|
+
readonly PATH_VIOLATION: "AUTORESEARCH-006";
|
|
11
|
+
readonly COMMIT_REJECTED: "AUTORESEARCH-007";
|
|
12
|
+
readonly EXPERIMENT_TIMEOUT: "AUTORESEARCH-008";
|
|
13
|
+
readonly EXPERIMENT_FAILED: "AUTORESEARCH-009";
|
|
14
|
+
readonly PARSE_FAILED: "AUTORESEARCH-010";
|
|
15
|
+
readonly RESULTS_WRITE_FAILED: "AUTORESEARCH-011";
|
|
16
|
+
readonly REVERT_FAILED: "AUTORESEARCH-012";
|
|
17
|
+
readonly WORKER_NOT_INITIALIZED: "AUTORESEARCH-013";
|
|
18
|
+
};
|
|
19
|
+
export declare const AutoresearchError: {
|
|
20
|
+
codes: {
|
|
21
|
+
readonly CONFIG_INVALID: "AUTORESEARCH-001";
|
|
22
|
+
readonly STATE_CORRUPT: "AUTORESEARCH-002";
|
|
23
|
+
readonly STATE_NOT_FOUND: "AUTORESEARCH-003";
|
|
24
|
+
readonly REPO_NOT_FOUND: "AUTORESEARCH-004";
|
|
25
|
+
readonly BRANCH_ERROR: "AUTORESEARCH-005";
|
|
26
|
+
readonly PATH_VIOLATION: "AUTORESEARCH-006";
|
|
27
|
+
readonly COMMIT_REJECTED: "AUTORESEARCH-007";
|
|
28
|
+
readonly EXPERIMENT_TIMEOUT: "AUTORESEARCH-008";
|
|
29
|
+
readonly EXPERIMENT_FAILED: "AUTORESEARCH-009";
|
|
30
|
+
readonly PARSE_FAILED: "AUTORESEARCH-010";
|
|
31
|
+
readonly RESULTS_WRITE_FAILED: "AUTORESEARCH-011";
|
|
32
|
+
readonly REVERT_FAILED: "AUTORESEARCH-012";
|
|
33
|
+
readonly WORKER_NOT_INITIALIZED: "AUTORESEARCH-013";
|
|
34
|
+
};
|
|
35
|
+
create: (code: "CONFIG_INVALID" | "STATE_CORRUPT" | "STATE_NOT_FOUND" | "REPO_NOT_FOUND" | "BRANCH_ERROR" | "PATH_VIOLATION" | "COMMIT_REJECTED" | "EXPERIMENT_TIMEOUT" | "EXPERIMENT_FAILED" | "PARSE_FAILED" | "RESULTS_WRITE_FAILED" | "REVERT_FAILED" | "WORKER_NOT_INITIALIZED", message: string, options?: {
|
|
36
|
+
retryable?: boolean;
|
|
37
|
+
details?: Record<string, unknown>;
|
|
38
|
+
cause?: Error;
|
|
39
|
+
} | undefined) => import("../index.js").NeuroLinkFeatureError;
|
|
40
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error definitions for the NeuroLink AutoResearch system.
|
|
3
|
+
*/
|
|
4
|
+
import { createErrorFactory } from "../core/infrastructure/baseError.js";
|
|
5
|
+
export const AutoresearchErrorCodes = {
|
|
6
|
+
CONFIG_INVALID: "AUTORESEARCH-001",
|
|
7
|
+
STATE_CORRUPT: "AUTORESEARCH-002",
|
|
8
|
+
STATE_NOT_FOUND: "AUTORESEARCH-003",
|
|
9
|
+
REPO_NOT_FOUND: "AUTORESEARCH-004",
|
|
10
|
+
BRANCH_ERROR: "AUTORESEARCH-005",
|
|
11
|
+
PATH_VIOLATION: "AUTORESEARCH-006",
|
|
12
|
+
COMMIT_REJECTED: "AUTORESEARCH-007",
|
|
13
|
+
EXPERIMENT_TIMEOUT: "AUTORESEARCH-008",
|
|
14
|
+
EXPERIMENT_FAILED: "AUTORESEARCH-009",
|
|
15
|
+
PARSE_FAILED: "AUTORESEARCH-010",
|
|
16
|
+
RESULTS_WRITE_FAILED: "AUTORESEARCH-011",
|
|
17
|
+
REVERT_FAILED: "AUTORESEARCH-012",
|
|
18
|
+
WORKER_NOT_INITIALIZED: "AUTORESEARCH-013",
|
|
19
|
+
};
|
|
20
|
+
export const AutoresearchError = createErrorFactory("Autoresearch", AutoresearchErrorCodes);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuroLink AutoResearch — Autonomous Experiment Engine
|
|
3
|
+
*
|
|
4
|
+
* An autonomous experiment loop that proposes code changes, executes
|
|
5
|
+
* experiments, evaluates results against a deterministic metric, and
|
|
6
|
+
* keeps or discards each change — running unattended for hours.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { resolveConfig, validateConfig, ResearchStateStore, RepoPolicy, ExperimentRunner, ResultRecorder } from "@juspay/neurolink/autoresearch";
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export { resolveConfig, validateConfig } from "./config.js";
|
|
14
|
+
export { ResearchStateStore } from "./stateStore.js";
|
|
15
|
+
export { RepoPolicy } from "./repoPolicy.js";
|
|
16
|
+
export { ExperimentRunner } from "./runner.js";
|
|
17
|
+
export { ResultRecorder } from "./resultRecorder.js";
|
|
18
|
+
export { parseExperimentSummary } from "./summaryParser.js";
|
|
19
|
+
export { getPhaseToolPolicy, getAllResearchToolNames } from "./phasePolicy.js";
|
|
20
|
+
export { PromptCompiler } from "./promptCompiler.js";
|
|
21
|
+
export { createResearchTools } from "./tools.js";
|
|
22
|
+
export { ResearchWorker } from "./worker.js";
|
|
23
|
+
export { AutoresearchError, AutoresearchErrorCodes } from "./errors.js";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuroLink AutoResearch — Autonomous Experiment Engine
|
|
3
|
+
*
|
|
4
|
+
* An autonomous experiment loop that proposes code changes, executes
|
|
5
|
+
* experiments, evaluates results against a deterministic metric, and
|
|
6
|
+
* keeps or discards each change — running unattended for hours.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { resolveConfig, validateConfig, ResearchStateStore, RepoPolicy, ExperimentRunner, ResultRecorder } from "@juspay/neurolink/autoresearch";
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
// Configuration
|
|
14
|
+
export { resolveConfig, validateConfig } from "./config.js";
|
|
15
|
+
// State management
|
|
16
|
+
export { ResearchStateStore } from "./stateStore.js";
|
|
17
|
+
// Policy enforcement
|
|
18
|
+
export { RepoPolicy } from "./repoPolicy.js";
|
|
19
|
+
// Experiment execution
|
|
20
|
+
export { ExperimentRunner } from "./runner.js";
|
|
21
|
+
// Result tracking
|
|
22
|
+
export { ResultRecorder } from "./resultRecorder.js";
|
|
23
|
+
// Log parsing
|
|
24
|
+
export { parseExperimentSummary } from "./summaryParser.js";
|
|
25
|
+
// Phase policy
|
|
26
|
+
export { getPhaseToolPolicy, getAllResearchToolNames } from "./phasePolicy.js";
|
|
27
|
+
// Prompt compilation
|
|
28
|
+
export { PromptCompiler } from "./promptCompiler.js";
|
|
29
|
+
// Research tools
|
|
30
|
+
export { createResearchTools } from "./tools.js";
|
|
31
|
+
// Worker orchestrator
|
|
32
|
+
export { ResearchWorker } from "./worker.js";
|
|
33
|
+
// Errors
|
|
34
|
+
export { AutoresearchError, AutoresearchErrorCodes } from "./errors.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-based tool access policy for the autoresearch experiment loop.
|
|
3
|
+
*/
|
|
4
|
+
import type { ExperimentPhase, PhaseToolPolicy } from "../types/autoresearchTypes.js";
|
|
5
|
+
export declare function getPhaseToolPolicy(phase: ExperimentPhase): PhaseToolPolicy;
|
|
6
|
+
/**
|
|
7
|
+
* Returns all research tool names across all phases.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getAllResearchToolNames(): string[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase-based tool access policy for the autoresearch experiment loop.
|
|
3
|
+
*/
|
|
4
|
+
const PHASE_POLICIES = {
|
|
5
|
+
bootstrap: {
|
|
6
|
+
activeTools: [
|
|
7
|
+
"research_get_context",
|
|
8
|
+
"research_read_file",
|
|
9
|
+
"research_checkpoint",
|
|
10
|
+
],
|
|
11
|
+
forcedTool: "research_get_context",
|
|
12
|
+
},
|
|
13
|
+
baseline: {
|
|
14
|
+
activeTools: [
|
|
15
|
+
"research_run_experiment",
|
|
16
|
+
"research_parse_log",
|
|
17
|
+
"research_record",
|
|
18
|
+
"research_accept",
|
|
19
|
+
"research_checkpoint",
|
|
20
|
+
],
|
|
21
|
+
forcedTool: "research_run_experiment",
|
|
22
|
+
},
|
|
23
|
+
propose: {
|
|
24
|
+
activeTools: ["research_get_context", "research_read_file"],
|
|
25
|
+
forcedTool: "research_get_context",
|
|
26
|
+
},
|
|
27
|
+
edit: {
|
|
28
|
+
activeTools: [
|
|
29
|
+
"research_read_file",
|
|
30
|
+
"research_write_candidate",
|
|
31
|
+
"research_diff",
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
commit: {
|
|
35
|
+
activeTools: ["research_commit_candidate"],
|
|
36
|
+
forcedTool: "research_commit_candidate",
|
|
37
|
+
},
|
|
38
|
+
run: {
|
|
39
|
+
activeTools: ["research_run_experiment"],
|
|
40
|
+
forcedTool: "research_run_experiment",
|
|
41
|
+
},
|
|
42
|
+
evaluate: {
|
|
43
|
+
activeTools: ["research_parse_log", "research_inspect_failure"],
|
|
44
|
+
forcedTool: "research_parse_log",
|
|
45
|
+
},
|
|
46
|
+
record: {
|
|
47
|
+
activeTools: ["research_record", "research_checkpoint"],
|
|
48
|
+
forcedTool: "research_record",
|
|
49
|
+
},
|
|
50
|
+
accept_or_revert: {
|
|
51
|
+
activeTools: ["research_accept", "research_revert", "research_checkpoint"],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
export function getPhaseToolPolicy(phase) {
|
|
55
|
+
const p = PHASE_POLICIES[phase];
|
|
56
|
+
return { activeTools: [...p.activeTools], forcedTool: p.forcedTool };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns all research tool names across all phases.
|
|
60
|
+
*/
|
|
61
|
+
export function getAllResearchToolNames() {
|
|
62
|
+
const names = new Set();
|
|
63
|
+
for (const policy of Object.values(PHASE_POLICIES)) {
|
|
64
|
+
for (const tool of policy.activeTools) {
|
|
65
|
+
names.add(tool);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return [...names];
|
|
69
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt compilation for the autoresearch worker.
|
|
3
|
+
*
|
|
4
|
+
* Reads program.md and combines it with current state and recent
|
|
5
|
+
* results to build the system prompt and per-cycle prompt.
|
|
6
|
+
*/
|
|
7
|
+
import type { ResearchConfig, ResearchState, ExperimentRecord } from "../types/autoresearchTypes.js";
|
|
8
|
+
export declare class PromptCompiler {
|
|
9
|
+
private config;
|
|
10
|
+
constructor(config: ResearchConfig);
|
|
11
|
+
/** Reads program.md and builds the system prompt */
|
|
12
|
+
buildSystemPrompt(): Promise<string>;
|
|
13
|
+
/** Builds the per-cycle prompt with current state + recent results */
|
|
14
|
+
buildCyclePrompt(state: ResearchState, recentResults: ExperimentRecord[]): Promise<string>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt compilation for the autoresearch worker.
|
|
3
|
+
*
|
|
4
|
+
* Reads program.md and combines it with current state and recent
|
|
5
|
+
* results to build the system prompt and per-cycle prompt.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, statSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { logger } from "../utils/logger.js";
|
|
10
|
+
import { getPhaseToolPolicy } from "./phasePolicy.js";
|
|
11
|
+
export class PromptCompiler {
|
|
12
|
+
config;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
}
|
|
16
|
+
/** Reads program.md and builds the system prompt */
|
|
17
|
+
async buildSystemPrompt() {
|
|
18
|
+
const MAX_PROGRAM_BYTES = 1_048_576; // 1 MB
|
|
19
|
+
let programContent = "";
|
|
20
|
+
try {
|
|
21
|
+
const programPath = path.join(this.config.repoPath, this.config.programPath);
|
|
22
|
+
const stat = statSync(programPath);
|
|
23
|
+
if (stat.size > MAX_PROGRAM_BYTES) {
|
|
24
|
+
logger.warn("[Autoresearch] program.md exceeds 1MB, truncating");
|
|
25
|
+
programContent = readFileSync(programPath, "utf-8").slice(0, MAX_PROGRAM_BYTES);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
programContent = readFileSync(programPath, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
logger.warn("[Autoresearch] program.md not found, using minimal system prompt");
|
|
33
|
+
}
|
|
34
|
+
const toolList = [
|
|
35
|
+
"research_get_context — Get current research state (branch, metric, recent results)",
|
|
36
|
+
"research_read_file — Read a file from the repo (must be in allowed paths)",
|
|
37
|
+
"research_write_candidate — Write to a mutable file (only allowed paths)",
|
|
38
|
+
"research_diff — Show git diff for mutable files",
|
|
39
|
+
"research_commit_candidate — Commit staged mutable files",
|
|
40
|
+
"research_run_experiment — Run the experiment command with timeout",
|
|
41
|
+
"research_parse_log — Parse the experiment log for metrics",
|
|
42
|
+
"research_record — Record experiment result (keep/discard/crash/timeout)",
|
|
43
|
+
"research_accept — Accept candidate (update best metric, advance branch)",
|
|
44
|
+
"research_revert — Revert to last accepted commit",
|
|
45
|
+
"research_inspect_failure — Read last 50 lines of run.log for crash diagnosis",
|
|
46
|
+
"research_checkpoint — Save current state to disk",
|
|
47
|
+
];
|
|
48
|
+
return [
|
|
49
|
+
"You are an autonomous research agent running experiments in a loop.",
|
|
50
|
+
"",
|
|
51
|
+
programContent ? "## Research Program" : "",
|
|
52
|
+
programContent,
|
|
53
|
+
"",
|
|
54
|
+
"## Available Tools",
|
|
55
|
+
...toolList.map((t) => `- ${t}`),
|
|
56
|
+
"",
|
|
57
|
+
"## Constraints",
|
|
58
|
+
`- You may ONLY modify these files: ${this.config.mutablePaths.join(", ")}`,
|
|
59
|
+
`- You must NEVER modify: ${this.config.immutablePaths.join(", ")}`,
|
|
60
|
+
`- The primary metric is: ${this.config.metric.name} (${this.config.metric.direction} is better)`,
|
|
61
|
+
`- Experiment timeout: ${Math.round(this.config.timeoutMs / 1000)} seconds`,
|
|
62
|
+
"",
|
|
63
|
+
"## Workflow",
|
|
64
|
+
"1. Call research_get_context to understand current state",
|
|
65
|
+
"2. Read relevant files to understand the code",
|
|
66
|
+
"3. Propose and implement a single experiment change",
|
|
67
|
+
"4. Commit the change",
|
|
68
|
+
"5. Run the experiment",
|
|
69
|
+
"6. Parse the results",
|
|
70
|
+
"7. Record the outcome",
|
|
71
|
+
"8. Accept (if improved) or revert (if not)",
|
|
72
|
+
"",
|
|
73
|
+
"NEVER STOP. Continue proposing experiments indefinitely.",
|
|
74
|
+
"Prefer simplicity — deleting code is better than adding complexity.",
|
|
75
|
+
]
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join("\n");
|
|
78
|
+
}
|
|
79
|
+
/** Builds the per-cycle prompt with current state + recent results */
|
|
80
|
+
async buildCyclePrompt(state, recentResults) {
|
|
81
|
+
const parts = [
|
|
82
|
+
`## Current Research State`,
|
|
83
|
+
`- Branch: ${state.branch}`,
|
|
84
|
+
`- Run count: ${state.runCount}`,
|
|
85
|
+
`- Keep count: ${state.keepCount}`,
|
|
86
|
+
`- Best ${this.config.metric.name}: ${state.bestMetric ?? "no baseline yet"}`,
|
|
87
|
+
`- Last status: ${state.lastStatus ?? "none"}`,
|
|
88
|
+
`- Current phase: ${state.currentPhase}`,
|
|
89
|
+
`- Accepted commit: ${state.acceptedCommit ?? "none"}`,
|
|
90
|
+
];
|
|
91
|
+
if (recentResults.length > 0) {
|
|
92
|
+
parts.push("");
|
|
93
|
+
parts.push("## Recent Results (last 10)");
|
|
94
|
+
parts.push(`commit\t${this.config.metric.name}\tstatus\tdescription`);
|
|
95
|
+
for (const r of recentResults.slice(-10)) {
|
|
96
|
+
const metricStr = r.metric !== null && Number.isFinite(r.metric)
|
|
97
|
+
? r.metric.toFixed(6)
|
|
98
|
+
: "N/A";
|
|
99
|
+
parts.push(`${r.commit}\t${metricStr}\t${r.status}\t${r.description}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
parts.push("");
|
|
103
|
+
// Phase-aware first action suggestion (avoid biasing accept_or_revert toward accept)
|
|
104
|
+
const phasePolicy = getPhaseToolPolicy(state.currentPhase);
|
|
105
|
+
let firstAction;
|
|
106
|
+
if (phasePolicy.forcedTool) {
|
|
107
|
+
firstAction = phasePolicy.forcedTool;
|
|
108
|
+
}
|
|
109
|
+
else if (state.currentPhase === "accept_or_revert") {
|
|
110
|
+
// Don't bias toward accept or revert — the deterministic policy decides.
|
|
111
|
+
// Use checkpoint (neutral) since accept/revert are both in activeTools.
|
|
112
|
+
firstAction = "research_checkpoint";
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
firstAction = phasePolicy.activeTools[0] || "research_get_context";
|
|
116
|
+
}
|
|
117
|
+
parts.push(`Continue the experiment loop. Start by calling ${firstAction}.`);
|
|
118
|
+
return parts.join("\n");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repository policy enforcement for autoresearch.
|
|
3
|
+
*
|
|
4
|
+
* Controls which files can be read/written and validates
|
|
5
|
+
* git operations against the research branch.
|
|
6
|
+
*/
|
|
7
|
+
import type { ResearchConfig } from "../types/autoresearchTypes.js";
|
|
8
|
+
export declare class RepoPolicy {
|
|
9
|
+
private config;
|
|
10
|
+
private resolvedMutablePaths;
|
|
11
|
+
private resolvedImmutablePaths;
|
|
12
|
+
constructor(config: ResearchConfig);
|
|
13
|
+
/** Returns true if resolved path is inside repoPath (handles prefix collision) */
|
|
14
|
+
private isInsideRepo;
|
|
15
|
+
/** Returns true if path is within mutablePaths and NOT in immutablePaths */
|
|
16
|
+
isWriteAllowed(filePath: string): boolean;
|
|
17
|
+
/** Returns true if path is in immutablePaths */
|
|
18
|
+
isProtected(filePath: string): boolean;
|
|
19
|
+
/** Returns true if path is readable (mutable, immutable, or program path) */
|
|
20
|
+
isReadAllowed(filePath: string): boolean;
|
|
21
|
+
/** Validates staged files are all in mutablePaths and on the right branch */
|
|
22
|
+
validateCommit(expectedBranch: string): Promise<{
|
|
23
|
+
valid: boolean;
|
|
24
|
+
violations: string[];
|
|
25
|
+
}>;
|
|
26
|
+
/** Returns list of staged file paths. Throws on git failure. */
|
|
27
|
+
getStagedFiles(): Promise<string[]>;
|
|
28
|
+
/** Returns current git branch */
|
|
29
|
+
getCurrentBranch(): string;
|
|
30
|
+
/** Returns short commit hash */
|
|
31
|
+
getHeadCommit(): string;
|
|
32
|
+
}
|